mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-01 16:58:10 +00:00
Merge remote-tracking branch 'origin/master' into 3636.doc-toc-reorg
This commit is contained in:
commit
40f4c0ea85
@ -15,82 +15,69 @@ workflows:
|
|||||||
ci:
|
ci:
|
||||||
jobs:
|
jobs:
|
||||||
# Start with jobs testing various platforms.
|
# Start with jobs testing various platforms.
|
||||||
|
- "debian-9":
|
||||||
# Every job that pulls a Docker image from Docker Hub needs to provide
|
{}
|
||||||
# credentials for that pull operation to avoid being subjected to
|
|
||||||
# unauthenticated pull limits shared across all of CircleCI. Use this
|
|
||||||
# first job to define a yaml anchor that can be used to supply a
|
|
||||||
# CircleCI job context which makes Docker Hub credentials available in
|
|
||||||
# the environment.
|
|
||||||
#
|
|
||||||
# Contexts are managed in the CircleCI web interface:
|
|
||||||
#
|
|
||||||
# https://app.circleci.com/settings/organization/github/tahoe-lafs/contexts
|
|
||||||
- "debian-9": &DOCKERHUB_CONTEXT
|
|
||||||
context: "dockerhub-auth"
|
|
||||||
|
|
||||||
- "debian-10":
|
- "debian-10":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
|
||||||
requires:
|
requires:
|
||||||
- "debian-9"
|
- "debian-9"
|
||||||
|
|
||||||
- "ubuntu-20-04":
|
- "ubuntu-20-04":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
- "ubuntu-18-04":
|
- "ubuntu-18-04":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
|
||||||
requires:
|
requires:
|
||||||
- "ubuntu-20-04"
|
- "ubuntu-20-04"
|
||||||
- "ubuntu-16-04":
|
- "ubuntu-16-04":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
|
||||||
requires:
|
requires:
|
||||||
- "ubuntu-20-04"
|
- "ubuntu-20-04"
|
||||||
|
|
||||||
- "fedora-29":
|
- "fedora-29":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
- "fedora-28":
|
- "fedora-28":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
|
||||||
requires:
|
requires:
|
||||||
- "fedora-29"
|
- "fedora-29"
|
||||||
|
|
||||||
- "centos-8":
|
- "centos-8":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
- "nixos-19-09":
|
- "nixos-19-09":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
|
- "nixos-21-05":
|
||||||
|
{}
|
||||||
|
|
||||||
# Test against PyPy 2.7
|
# Test against PyPy 2.7
|
||||||
- "pypy27-buster":
|
- "pypy27-buster":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
# Just one Python 3.6 configuration while the port is in-progress.
|
# Just one Python 3.6 configuration while the port is in-progress.
|
||||||
- "python36":
|
- "python36":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
# Other assorted tasks and configurations
|
# Other assorted tasks and configurations
|
||||||
- "lint":
|
- "lint":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
- "codechecks3":
|
||||||
|
{}
|
||||||
- "pyinstaller":
|
- "pyinstaller":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
- "deprecations":
|
- "deprecations":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
- "c-locale":
|
- "c-locale":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
# Any locale other than C or UTF-8.
|
# Any locale other than C or UTF-8.
|
||||||
- "another-locale":
|
- "another-locale":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
- "integration":
|
- "integration":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
|
||||||
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-9"
|
- "debian-9"
|
||||||
|
|
||||||
- "typechecks":
|
- "typechecks":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
- "docs":
|
- "docs":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
{}
|
||||||
|
|
||||||
images:
|
images:
|
||||||
# Build the Docker images used by the ci jobs. This makes the ci jobs
|
# Build the Docker images used by the ci jobs. This makes the ci jobs
|
||||||
@ -105,8 +92,16 @@ workflows:
|
|||||||
- "master"
|
- "master"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- "build-image-debian-10":
|
# Every job that pushes a Docker image from Docker Hub needs to provide
|
||||||
<<: *DOCKERHUB_CONTEXT
|
# credentials. Use this first job to define a yaml anchor that can be
|
||||||
|
# used to supply a CircleCI job context which makes Docker Hub
|
||||||
|
# credentials available in the environment.
|
||||||
|
#
|
||||||
|
# Contexts are managed in the CircleCI web interface:
|
||||||
|
#
|
||||||
|
# https://app.circleci.com/settings/organization/github/tahoe-lafs/contexts
|
||||||
|
- "build-image-debian-10": &DOCKERHUB_CONTEXT
|
||||||
|
context: "dockerhub-auth"
|
||||||
- "build-image-debian-9":
|
- "build-image-debian-9":
|
||||||
<<: *DOCKERHUB_CONTEXT
|
<<: *DOCKERHUB_CONTEXT
|
||||||
- "build-image-ubuntu-16-04":
|
- "build-image-ubuntu-16-04":
|
||||||
@ -168,6 +163,24 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
~/.local/bin/tox -e codechecks
|
~/.local/bin/tox -e codechecks
|
||||||
|
|
||||||
|
codechecks3:
|
||||||
|
docker:
|
||||||
|
- <<: *DOCKERHUB_AUTH
|
||||||
|
image: "circleci/python:3"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- "checkout"
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: "Install tox"
|
||||||
|
command: |
|
||||||
|
pip install --user tox
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: "Static-ish code checks"
|
||||||
|
command: |
|
||||||
|
~/.local/bin/tox -e codechecks3
|
||||||
|
|
||||||
pyinstaller:
|
pyinstaller:
|
||||||
docker:
|
docker:
|
||||||
- <<: *DOCKERHUB_AUTH
|
- <<: *DOCKERHUB_AUTH
|
||||||
@ -261,6 +274,11 @@ jobs:
|
|||||||
# in the project source checkout.
|
# in the project source checkout.
|
||||||
path: "/tmp/project/_trial_temp/test.log"
|
path: "/tmp/project/_trial_temp/test.log"
|
||||||
|
|
||||||
|
- store_artifacts: &STORE_ELIOT_LOG
|
||||||
|
# Despite passing --workdir /tmp to tox above, it still runs trial
|
||||||
|
# in the project source checkout.
|
||||||
|
path: "/tmp/project/eliot.log"
|
||||||
|
|
||||||
- store_artifacts: &STORE_OTHER_ARTIFACTS
|
- store_artifacts: &STORE_OTHER_ARTIFACTS
|
||||||
# Store any other artifacts, too. This is handy to allow other jobs
|
# Store any other artifacts, too. This is handy to allow other jobs
|
||||||
# sharing most of the definition of this one to be able to
|
# sharing most of the definition of this one to be able to
|
||||||
@ -403,6 +421,7 @@ jobs:
|
|||||||
- run: *RUN_TESTS
|
- run: *RUN_TESTS
|
||||||
- store_test_results: *STORE_TEST_RESULTS
|
- store_test_results: *STORE_TEST_RESULTS
|
||||||
- store_artifacts: *STORE_TEST_LOG
|
- store_artifacts: *STORE_TEST_LOG
|
||||||
|
- store_artifacts: *STORE_ELIOT_LOG
|
||||||
- store_artifacts: *STORE_OTHER_ARTIFACTS
|
- store_artifacts: *STORE_OTHER_ARTIFACTS
|
||||||
- run: *SUBMIT_COVERAGE
|
- run: *SUBMIT_COVERAGE
|
||||||
|
|
||||||
@ -422,8 +441,7 @@ jobs:
|
|||||||
image: "tahoelafsci/fedora:29-py"
|
image: "tahoelafsci/fedora:29-py"
|
||||||
user: "nobody"
|
user: "nobody"
|
||||||
|
|
||||||
|
nixos-19-09: &NIXOS
|
||||||
nixos-19-09:
|
|
||||||
docker:
|
docker:
|
||||||
# Run in a highly Nix-capable environment.
|
# Run in a highly Nix-capable environment.
|
||||||
- <<: *DOCKERHUB_AUTH
|
- <<: *DOCKERHUB_AUTH
|
||||||
@ -431,6 +449,7 @@ jobs:
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.09-small.tar.gz"
|
NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-19.09-small.tar.gz"
|
||||||
|
SOURCE: "nix/"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- "checkout"
|
- "checkout"
|
||||||
@ -447,7 +466,17 @@ jobs:
|
|||||||
# build a couple simple little dependencies that don't take
|
# build a couple simple little dependencies that don't take
|
||||||
# advantage of multiple cores and we get a little speedup by doing
|
# advantage of multiple cores and we get a little speedup by doing
|
||||||
# them in parallel.
|
# them in parallel.
|
||||||
nix-build --cores 3 --max-jobs 2 nix/
|
nix-build --cores 3 --max-jobs 2 "$SOURCE"
|
||||||
|
|
||||||
|
nixos-21-05:
|
||||||
|
<<: *NIXOS
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# Note this doesn't look more similar to the 19.09 NIX_PATH URL because
|
||||||
|
# there was some internal shuffling by the NixOS project about how they
|
||||||
|
# publish stable revisions.
|
||||||
|
NIX_PATH: "nixpkgs=https://github.com/NixOS/nixpkgs/archive/d32b07e6df276d78e3640eb43882b80c9b2b3459.tar.gz"
|
||||||
|
SOURCE: "nix/py3.nix"
|
||||||
|
|
||||||
typechecks:
|
typechecks:
|
||||||
docker:
|
docker:
|
||||||
|
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
- 3.9
|
- 3.9
|
||||||
include:
|
include:
|
||||||
# On macOS don't bother with 3.6-3.8, just to get faster builds.
|
# On macOS don't bother with 3.6-3.8, just to get faster builds.
|
||||||
- os: macos-latest
|
- os: macos-10.15
|
||||||
python-version: 2.7
|
python-version: 2.7
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
@ -76,13 +76,18 @@ jobs:
|
|||||||
- name: Run tox for corresponding Python version
|
- name: Run tox for corresponding Python version
|
||||||
run: python -m tox
|
run: python -m tox
|
||||||
|
|
||||||
- name: Upload eliot.log in case of failure
|
- name: Upload eliot.log
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
if: failure()
|
|
||||||
with:
|
with:
|
||||||
name: eliot.log
|
name: eliot.log
|
||||||
path: eliot.log
|
path: eliot.log
|
||||||
|
|
||||||
|
- name: Upload trial log
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: test.log
|
||||||
|
path: _trial_temp/test.log
|
||||||
|
|
||||||
# Upload this job's coverage data to Coveralls. While there is a GitHub
|
# Upload this job's coverage data to Coveralls. While there is a GitHub
|
||||||
# Action for this, as of Jan 2021 it does not support Python coverage
|
# Action for this, as of Jan 2021 it does not support Python coverage
|
||||||
# files - only lcov files. Therefore, we use coveralls-python, the
|
# files - only lcov files. Therefore, we use coveralls-python, the
|
||||||
@ -136,7 +141,7 @@ jobs:
|
|||||||
# See notes about parallel builds on GitHub Actions at
|
# See notes about parallel builds on GitHub Actions at
|
||||||
# https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html
|
# https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html
|
||||||
finish-coverage-report:
|
finish-coverage-report:
|
||||||
needs:
|
needs:
|
||||||
- "coverage"
|
- "coverage"
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
container: "python:3-slim"
|
container: "python:3-slim"
|
||||||
@ -163,7 +168,7 @@ jobs:
|
|||||||
- 3.9
|
- 3.9
|
||||||
include:
|
include:
|
||||||
# On macOS don't bother with 3.6, just to get faster builds.
|
# On macOS don't bother with 3.6, just to get faster builds.
|
||||||
- os: macos-latest
|
- os: macos-10.15
|
||||||
python-version: 2.7
|
python-version: 2.7
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
@ -173,12 +178,12 @@ jobs:
|
|||||||
- name: Install Tor [Ubuntu]
|
- name: Install Tor [Ubuntu]
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: sudo apt install tor
|
run: sudo apt install tor
|
||||||
|
|
||||||
# TODO: See https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3744.
|
# TODO: See https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3744.
|
||||||
# We have to use an older version of Tor for running integration
|
# We have to use an older version of Tor for running integration
|
||||||
# tests on macOS.
|
# tests on macOS.
|
||||||
- name: Install Tor [macOS, ${{ matrix.python-version }} ]
|
- name: Install Tor [macOS, ${{ matrix.python-version }} ]
|
||||||
if: ${{ matrix.os == 'macos-latest' }}
|
if: ${{ contains(matrix.os, 'macos') }}
|
||||||
run: |
|
run: |
|
||||||
brew extract --version 0.4.5.8 tor homebrew/cask
|
brew extract --version 0.4.5.8 tor homebrew/cask
|
||||||
brew install tor@0.4.5.8
|
brew install tor@0.4.5.8
|
||||||
@ -242,7 +247,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- macos-latest
|
- macos-10.15
|
||||||
- windows-latest
|
- windows-latest
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
python-version:
|
python-version:
|
||||||
|
2
NEWS.rst
2
NEWS.rst
@ -1188,7 +1188,7 @@ Precautions when Upgrading
|
|||||||
.. _`#1915`: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/1915
|
.. _`#1915`: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/1915
|
||||||
.. _`#1926`: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/1926
|
.. _`#1926`: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/1926
|
||||||
.. _`message to the tahoe-dev mailing list`:
|
.. _`message to the tahoe-dev mailing list`:
|
||||||
https://tahoe-lafs.org/pipermail/tahoe-dev/2013-March/008096.html
|
https://lists.tahoe-lafs.org/pipermail/tahoe-dev/2013-March/008079.html
|
||||||
|
|
||||||
|
|
||||||
Release 1.9.2 (2012-07-03)
|
Release 1.9.2 (2012-07-03)
|
||||||
|
18
README.rst
18
README.rst
@ -1,5 +1,5 @@
|
|||||||
======================================
|
======================================
|
||||||
Free and Open decentralized data store
|
Free and Open Decentralized Data Store
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|image0|
|
|image0|
|
||||||
@ -48,13 +48,17 @@ Please read more about Tahoe-LAFS architecture `here <docs/architecture.rst>`__.
|
|||||||
✅ Installation
|
✅ Installation
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
For more detailed instructions, read `docs/INSTALL.rst <docs/INSTALL.rst>`__ .
|
For more detailed instructions, read `Installing Tahoe-LAFS <docs/Installation/install-tahoe.rst>`__.
|
||||||
|
|
||||||
- `Building Tahoe-LAFS on Windows <docs/windows.rst>`__
|
|
||||||
|
|
||||||
- `OS-X Packaging <docs/OS-X.rst>`__
|
Once ``tahoe --version`` works, see `How to Run Tahoe-LAFS <docs/running.rst>`__ to learn how to set up your first Tahoe-LAFS node.
|
||||||
|
|
||||||
Once tahoe --version works, see `docs/running.rst <docs/running.rst>`__ to learn how to set up your first Tahoe-LAFS node.
|
🐍 Python 3 Support
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Python 3 support has been introduced starting with Tahoe-LAFS 1.16.0, alongside Python 2.
|
||||||
|
System administrators are advised to start running Tahoe on Python 3 and should expect Python 2 support to be dropped in a future version.
|
||||||
|
Please, feel free to file issues if you run into bugs while running Tahoe on Python 3.
|
||||||
|
|
||||||
|
|
||||||
🤖 Issues
|
🤖 Issues
|
||||||
@ -76,7 +80,7 @@ Get involved with the Tahoe-LAFS community:
|
|||||||
|
|
||||||
- Join our `weekly conference calls <https://www.tahoe-lafs.org/trac/tahoe-lafs/wiki/WeeklyMeeting>`__ with core developers and interested community members.
|
- Join our `weekly conference calls <https://www.tahoe-lafs.org/trac/tahoe-lafs/wiki/WeeklyMeeting>`__ with core developers and interested community members.
|
||||||
|
|
||||||
- Subscribe to `the tahoe-dev mailing list <https://www.tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev>`__, the community forum for discussion of Tahoe-LAFS design, implementation, and usage.
|
- Subscribe to `the tahoe-dev mailing list <https://lists.tahoe-lafs.org/mailman/listinfo/tahoe-dev>`__, the community forum for discussion of Tahoe-LAFS design, implementation, and usage.
|
||||||
|
|
||||||
🤗 Contributing
|
🤗 Contributing
|
||||||
---------------
|
---------------
|
||||||
@ -98,6 +102,8 @@ Before authoring or reviewing a patch, please familiarize yourself with the `Cod
|
|||||||
|
|
||||||
We would like to thank `Fosshost <https://fosshost.org>`__ for supporting us with hosting services. If your open source project needs help, you can apply for their support.
|
We would like to thank `Fosshost <https://fosshost.org>`__ for supporting us with hosting services. If your open source project needs help, you can apply for their support.
|
||||||
|
|
||||||
|
We are grateful to `Oregon State University Open Source Lab <https://osuosl.org/>`__ for hosting tahoe-dev mailing list.
|
||||||
|
|
||||||
❓ FAQ
|
❓ FAQ
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -65,4 +65,4 @@ If you are working on MacOS or a Linux distribution which does not have Tahoe-LA
|
|||||||
|
|
||||||
If you are looking to hack on the source code or run pre-release code, we recommend you install Tahoe-LAFS on a `virtualenv` instance. To learn more, see :doc:`install-on-linux`.
|
If you are looking to hack on the source code or run pre-release code, we recommend you install Tahoe-LAFS on a `virtualenv` instance. To learn more, see :doc:`install-on-linux`.
|
||||||
|
|
||||||
You can always write to the `tahoe-dev mailing list <https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev>`_ or chat on the `Libera.chat IRC <irc://irc.libera.chat/%23tahoe-lafs>`_ if you are not able to get Tahoe-LAFS up and running on your deployment.
|
You can always write to the `tahoe-dev mailing list <https://lists.tahoe-lafs.org/mailman/listinfo/tahoe-dev>`_ or chat on the `Libera.chat IRC <irc://irc.libera.chat/%23tahoe-lafs>`_ if you are not able to get Tahoe-LAFS up and running on your deployment.
|
||||||
|
@ -177,7 +177,7 @@ mutable files, you may be able to avoid the potential for "rollback"
|
|||||||
failure.
|
failure.
|
||||||
|
|
||||||
A future version of Tahoe will include a fix for this issue. Here is
|
A future version of Tahoe will include a fix for this issue. Here is
|
||||||
[https://tahoe-lafs.org/pipermail/tahoe-dev/2008-May/000630.html the
|
[https://lists.tahoe-lafs.org/pipermail/tahoe-dev/2008-May/000628.html the
|
||||||
mailing list discussion] about how that future version will work.
|
mailing list discussion] about how that future version will work.
|
||||||
|
|
||||||
|
|
||||||
|
@ -268,7 +268,7 @@ For known security issues see
|
|||||||
.PP
|
.PP
|
||||||
Tahoe-LAFS home page: <https://tahoe-lafs.org/>
|
Tahoe-LAFS home page: <https://tahoe-lafs.org/>
|
||||||
.PP
|
.PP
|
||||||
tahoe-dev mailing list: <https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev>
|
tahoe-dev mailing list: <https://lists.tahoe-lafs.org/mailman/listinfo/tahoe-dev>
|
||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
.PP
|
.PP
|
||||||
Copyright \@ 2006\[en]2013 The Tahoe-LAFS Software Foundation
|
Copyright \@ 2006\[en]2013 The Tahoe-LAFS Software Foundation
|
||||||
|
@ -13,6 +13,57 @@ Specifically, it should be possible to implement a Tahoe-LAFS storage server wit
|
|||||||
The Tahoe-LAFS client will also need to change but it is not expected that it will be noticably simplified by this change
|
The Tahoe-LAFS client will also need to change but it is not expected that it will be noticably simplified by this change
|
||||||
(though this may be the first step towards simplifying it).
|
(though this may be the first step towards simplifying it).
|
||||||
|
|
||||||
|
Glossary
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
`Foolscap <https://github.com/warner/foolscap/>`_
|
||||||
|
an RPC/RMI (Remote Procedure Call / Remote Method Invocation) protocol for use with Twisted
|
||||||
|
|
||||||
|
storage server
|
||||||
|
a Tahoe-LAFS process configured to offer storage and reachable over the network for store and retrieve operations
|
||||||
|
|
||||||
|
storage service
|
||||||
|
a Python object held in memory in the storage server which provides the implementation of the storage protocol
|
||||||
|
|
||||||
|
introducer
|
||||||
|
a Tahoe-LAFS process at a known location configured to re-publish announcements about the location of storage servers
|
||||||
|
|
||||||
|
fURL
|
||||||
|
a self-authenticating URL-like string which can be used to locate a remote object using the Foolscap protocol
|
||||||
|
(the storage service is an example of such an object)
|
||||||
|
|
||||||
|
NURL
|
||||||
|
a self-authenticating URL-like string almost exactly like a fURL but without being tied to Foolscap
|
||||||
|
|
||||||
|
swissnum
|
||||||
|
a short random string which is part of a fURL and which acts as a shared secret to authorize clients to use a storage service
|
||||||
|
|
||||||
|
lease
|
||||||
|
state associated with a share informing a storage server of the duration of storage desired by a client
|
||||||
|
|
||||||
|
share
|
||||||
|
a single unit of client-provided arbitrary data to be stored by a storage server
|
||||||
|
(in practice, one of the outputs of applying ZFEC encoding to some ciphertext with some additional metadata attached)
|
||||||
|
|
||||||
|
bucket
|
||||||
|
a group of one or more immutable shares held by a storage server and having a common storage index
|
||||||
|
|
||||||
|
slot
|
||||||
|
a group of one or more mutable shares held by a storage server and having a common storage index
|
||||||
|
(sometimes "slot" is considered a synonym for "storage index of a slot")
|
||||||
|
|
||||||
|
storage index
|
||||||
|
a 16 byte string which can address a slot or a bucket
|
||||||
|
(in practice, derived by hashing the encryption key associated with contents of that slot or bucket)
|
||||||
|
|
||||||
|
write enabler
|
||||||
|
a short secret string which storage servers require to be presented before allowing mutation of any mutable share
|
||||||
|
|
||||||
|
lease renew secret
|
||||||
|
a short secret string which storage servers required to be presented before allowing a particular lease to be renewed
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@ -87,6 +138,8 @@ The Foolscap-based protocol offers:
|
|||||||
* A careful configuration of the TLS connection parameters *may* also offer **forward secrecy**.
|
* A careful configuration of the TLS connection parameters *may* also offer **forward secrecy**.
|
||||||
However, Tahoe-LAFS' use of Foolscap takes no steps to ensure this is the case.
|
However, Tahoe-LAFS' use of Foolscap takes no steps to ensure this is the case.
|
||||||
|
|
||||||
|
* **Storage authorization** by way of a capability contained in the fURL addressing a storage service.
|
||||||
|
|
||||||
Discussion
|
Discussion
|
||||||
!!!!!!!!!!
|
!!!!!!!!!!
|
||||||
|
|
||||||
@ -117,6 +170,10 @@ there is no way to write data which appears legitimate to a legitimate client).
|
|||||||
Therefore, **message confidentiality** is necessary when exchanging these secrets.
|
Therefore, **message confidentiality** is necessary when exchanging these secrets.
|
||||||
**Forward secrecy** is preferred so that an attacker recording an exchange today cannot launch this attack at some future point after compromising the necessary keys.
|
**Forward secrecy** is preferred so that an attacker recording an exchange today cannot launch this attack at some future point after compromising the necessary keys.
|
||||||
|
|
||||||
|
A storage service offers service only to some clients.
|
||||||
|
A client proves their authorization to use the storage service by presenting a shared secret taken from the fURL.
|
||||||
|
In this way **storage authorization** is performed to prevent disallowed parties from consuming any storage resources.
|
||||||
|
|
||||||
Functionality
|
Functionality
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@ -173,6 +230,10 @@ Additionally,
|
|||||||
by continuing to interact using TLS,
|
by continuing to interact using TLS,
|
||||||
Bob's client and Alice's storage node are assured of both **message authentication** and **message confidentiality**.
|
Bob's client and Alice's storage node are assured of both **message authentication** and **message confidentiality**.
|
||||||
|
|
||||||
|
Bob's client further inspects the fURL for the *swissnum*.
|
||||||
|
When Bob's client issues HTTP requests to Alice's storage node it includes the *swissnum* in its requests.
|
||||||
|
**Storage authorization** has been achieved.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Foolscap TubIDs are 20 bytes (SHA1 digest of the certificate).
|
Foolscap TubIDs are 20 bytes (SHA1 digest of the certificate).
|
||||||
@ -302,6 +363,12 @@ one branch contains all of the share data;
|
|||||||
another branch contains all of the lease data;
|
another branch contains all of the lease data;
|
||||||
etc.
|
etc.
|
||||||
|
|
||||||
|
Authorization is required for all endpoints.
|
||||||
|
The standard HTTP authorization protocol is used.
|
||||||
|
The authentication *type* used is ``Tahoe-LAFS``.
|
||||||
|
The swissnum from the NURL used to locate the storage service is used as the *credentials*.
|
||||||
|
If credentials are not presented or the swissnum is not associated with a storage service then no storage processing is performed and the request receives an ``UNAUTHORIZED`` response.
|
||||||
|
|
||||||
General
|
General
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@ -328,19 +395,30 @@ For example::
|
|||||||
``PUT /v1/lease/:storage_index``
|
``PUT /v1/lease/:storage_index``
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
Create a new lease that applies to all shares for the given storage index.
|
Either renew or create a new lease on the bucket addressed by ``storage_index``.
|
||||||
The details of the lease are encoded in the request body.
|
The details of the lease are encoded in the request body.
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
{"renew-secret": "abcd", "cancel-secret": "efgh"}
|
{"renew-secret": "abcd", "cancel-secret": "efgh"}
|
||||||
|
|
||||||
If there are no shares for the given ``storage_index``
|
|
||||||
then do nothing and return ``NO CONTENT``.
|
|
||||||
|
|
||||||
If the ``renew-secret`` value matches an existing lease
|
If the ``renew-secret`` value matches an existing lease
|
||||||
then that lease will be renewed instead.
|
then the expiration time of that lease will be changed to 31 days after the time of this operation.
|
||||||
|
If it does not match an existing lease
|
||||||
|
then a new lease will be created with this ``renew-secret`` which expires 31 days after the time of this operation.
|
||||||
|
|
||||||
|
``renew-secret`` and ``cancel-secret`` values must be 32 bytes long.
|
||||||
|
The server treats them as opaque values.
|
||||||
|
:ref:`Share Leases` gives details about how the Tahoe-LAFS storage client constructs these values.
|
||||||
|
|
||||||
|
In these cases the response is ``NO CONTENT`` with an empty body.
|
||||||
|
|
||||||
|
It is possible that the storage server will have no shares for the given ``storage_index`` because:
|
||||||
|
|
||||||
|
* no such shares have ever been uploaded.
|
||||||
|
* a previous lease expired and the storage server reclaimed the storage by deleting the shares.
|
||||||
|
|
||||||
|
In these cases the server takes no action and returns ``NOT FOUND``.
|
||||||
|
|
||||||
The lease expires after 31 days.
|
|
||||||
|
|
||||||
Discussion
|
Discussion
|
||||||
``````````
|
``````````
|
||||||
@ -350,40 +428,13 @@ We chose to put these values into the request body to make the URL simpler.
|
|||||||
|
|
||||||
Several behaviors here are blindly copied from the Foolscap-based storage server protocol.
|
Several behaviors here are blindly copied from the Foolscap-based storage server protocol.
|
||||||
|
|
||||||
* There is a cancel secret but there is no API to use it to cancel a lease.
|
* There is a cancel secret but there is no API to use it to cancel a lease (see ticket:3768).
|
||||||
* The lease period is hard-coded at 31 days.
|
* The lease period is hard-coded at 31 days.
|
||||||
* There is no way to differentiate between success and an unknown **storage index**.
|
|
||||||
* There are separate **add** and **renew** lease APIs.
|
|
||||||
|
|
||||||
These are not necessarily ideal behaviors
|
These are not necessarily ideal behaviors
|
||||||
but they are adopted to avoid any *semantic* changes between the Foolscap- and HTTP-based protocols.
|
but they are adopted to avoid any *semantic* changes between the Foolscap- and HTTP-based protocols.
|
||||||
It is expected that some or all of these behaviors may change in a future revision of the HTTP-based protocol.
|
It is expected that some or all of these behaviors may change in a future revision of the HTTP-based protocol.
|
||||||
|
|
||||||
``POST /v1/lease/:storage_index``
|
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
|
|
||||||
Renew an existing lease for all shares for the given storage index.
|
|
||||||
The details of the lease are encoded in the request body.
|
|
||||||
For example::
|
|
||||||
|
|
||||||
{"renew-secret": "abcd"}
|
|
||||||
|
|
||||||
If there are no shares for the given ``storage_index``
|
|
||||||
then ``NOT FOUND`` is returned.
|
|
||||||
|
|
||||||
If there is no lease with a matching ``renew-secret`` value on the given storage index
|
|
||||||
then ``NOT FOUND`` is returned.
|
|
||||||
In this case,
|
|
||||||
if the storage index refers to mutable data
|
|
||||||
then the response also includes a list of nodeids where the lease can be renewed.
|
|
||||||
For example::
|
|
||||||
|
|
||||||
{"nodeids": ["aaa...", "bbb..."]}
|
|
||||||
|
|
||||||
Othewise,
|
|
||||||
the matching lease's expiration time is changed to be 31 days from the time of this operation
|
|
||||||
and ``NO CONTENT`` is returned.
|
|
||||||
|
|
||||||
Immutable
|
Immutable
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -422,23 +473,81 @@ However, we decided this does not matter because:
|
|||||||
therefore no proxy servers can perform any extra logging.
|
therefore no proxy servers can perform any extra logging.
|
||||||
* Tahoe-LAFS itself does not currently log HTTP request URLs.
|
* Tahoe-LAFS itself does not currently log HTTP request URLs.
|
||||||
|
|
||||||
``PUT /v1/immutable/:storage_index/:share_number``
|
The response includes ``already-have`` and ``allocated`` for two reasons:
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
|
* If an upload is interrupted and the client loses its local state that lets it know it already uploaded some shares
|
||||||
|
then this allows it to discover this fact (by inspecting ``already-have``) and only upload the missing shares (indicated by ``allocated``).
|
||||||
|
|
||||||
|
* If an upload has completed a client may still choose to re-balance storage by moving shares between servers.
|
||||||
|
This might be because a server has become unavailable and a remaining server needs to store more shares for the upload.
|
||||||
|
It could also just be that the client's preferred servers have changed.
|
||||||
|
|
||||||
|
``PATCH /v1/immutable/:storage_index/:share_number``
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
Write data for the indicated share.
|
Write data for the indicated share.
|
||||||
The share number must belong to the storage index.
|
The share number must belong to the storage index.
|
||||||
The request body is the raw share data (i.e., ``application/octet-stream``).
|
The request body is the raw share data (i.e., ``application/octet-stream``).
|
||||||
*Content-Range* requests are encouraged for large transfers.
|
*Content-Range* requests are encouraged for large transfers to allow partially complete uploads to be resumed.
|
||||||
For example,
|
For example,
|
||||||
for a 1MiB share the data can be broken in to 8 128KiB chunks.
|
a 1MiB share can be divided in to eight separate 128KiB chunks.
|
||||||
Each chunk can be *PUT* separately with the appropriate *Content-Range* header.
|
Each chunk can be uploaded in a separate request.
|
||||||
|
Each request can include a *Content-Range* value indicating its placement within the complete share.
|
||||||
|
If any one of these requests fails then at most 128KiB of upload work needs to be retried.
|
||||||
|
|
||||||
The server must recognize when all of the data has been received and mark the share as complete
|
The server must recognize when all of the data has been received and mark the share as complete
|
||||||
(which it can do because it was informed of the size when the storage index was initialized).
|
(which it can do because it was informed of the size when the storage index was initialized).
|
||||||
Clients should upload chunks in re-assembly order.
|
|
||||||
Servers may reject out-of-order chunks for implementation simplicity.
|
|
||||||
If an individual *PUT* fails then only a limited amount of effort is wasted on the necessary retry.
|
|
||||||
|
|
||||||
.. think about copying https://developers.google.com/drive/api/v2/resumable-upload
|
* When a chunk that does not complete the share is successfully uploaded the response is ``OK``.
|
||||||
|
The response body indicates the range of share data that has yet to be uploaded.
|
||||||
|
That is::
|
||||||
|
|
||||||
|
{ "required":
|
||||||
|
[ { "begin": <byte position, inclusive>
|
||||||
|
, "end": <byte position, exclusive>
|
||||||
|
}
|
||||||
|
,
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
* When the chunk that completes the share is successfully uploaded the response is ``CREATED``.
|
||||||
|
* If the *Content-Range* for a request covers part of the share that has already,
|
||||||
|
and the data does not match already written data,
|
||||||
|
the response is ``CONFLICT``.
|
||||||
|
At this point the only thing to do is abort the upload and start from scratch (see below).
|
||||||
|
|
||||||
|
``PUT /v1/immutable/:storage_index/:share_number/abort``
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
This cancels an *in-progress* upload.
|
||||||
|
|
||||||
|
The response code:
|
||||||
|
|
||||||
|
* When the upload is still in progress and therefore the abort has succeeded,
|
||||||
|
the response is ``OK``.
|
||||||
|
Future uploads can start from scratch with no pre-existing upload state stored on the server.
|
||||||
|
* If the uploaded has already finished, the response is 405 (Method Not Allowed)
|
||||||
|
and no change is made.
|
||||||
|
|
||||||
|
|
||||||
|
Discussion
|
||||||
|
``````````
|
||||||
|
|
||||||
|
``PUT`` verbs are only supposed to be used to replace the whole resource,
|
||||||
|
thus the use of ``PATCH``.
|
||||||
|
From RFC 7231::
|
||||||
|
|
||||||
|
An origin server that allows PUT on a given target resource MUST send
|
||||||
|
a 400 (Bad Request) response to a PUT request that contains a
|
||||||
|
Content-Range header field (Section 4.2 of [RFC7233]), since the
|
||||||
|
payload is likely to be partial content that has been mistakenly PUT
|
||||||
|
as a full representation. Partial content updates are possible by
|
||||||
|
targeting a separately identified resource with state that overlaps a
|
||||||
|
portion of the larger resource, or by using a different method that
|
||||||
|
has been specifically defined for partial updates (for example, the
|
||||||
|
PATCH method defined in [RFC5789]).
|
||||||
|
|
||||||
|
|
||||||
``POST /v1/immutable/:storage_index/:share_number/corrupt``
|
``POST /v1/immutable/:storage_index/:share_number/corrupt``
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
@ -464,31 +573,20 @@ For example::
|
|||||||
|
|
||||||
[1, 5]
|
[1, 5]
|
||||||
|
|
||||||
``GET /v1/immutable/:storage_index?share=:s0&share=:sN&offset=o1&size=z0&offset=oN&size=zN``
|
``GET /v1/immutable/:storage_index/:share_number``
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
Read data from the indicated immutable shares.
|
Read a contiguous sequence of bytes from one share in one bucket.
|
||||||
If ``share`` query parameters are given, selecte only those shares for reading.
|
The response body is the raw share data (i.e., ``application/octet-stream``).
|
||||||
Otherwise, select all shares present.
|
The ``Range`` header may be used to request exactly one ``bytes`` range.
|
||||||
If ``size`` and ``offset`` query parameters are given,
|
Interpretation and response behavior is as specified in RFC 7233 § 4.1.
|
||||||
only the portions thus identified of the selected shares are returned.
|
Multiple ranges in a single request are *not* supported.
|
||||||
Otherwise, all data is from the selected shares is returned.
|
|
||||||
|
|
||||||
The response body contains a mapping giving the read data.
|
|
||||||
For example::
|
|
||||||
|
|
||||||
{
|
|
||||||
3: ["foo", "bar"],
|
|
||||||
7: ["baz", "quux"]
|
|
||||||
}
|
|
||||||
|
|
||||||
Discussion
|
Discussion
|
||||||
``````````
|
``````````
|
||||||
|
|
||||||
Offset and size of the requested data are specified here as query arguments.
|
Multiple ``bytes`` ranges are not supported.
|
||||||
Instead, this information could be present in a ``Range`` header in the request.
|
HTTP requires that the ``Content-Type`` of the response in that case be ``multipart/...``.
|
||||||
This is the more obvious choice and leverages an HTTP feature built for exactly this use-case.
|
|
||||||
However, HTTP requires that the ``Content-Type`` of the response to "range requests" be ``multipart/...``.
|
|
||||||
The ``multipart`` major type brings along string sentinel delimiting as a means to frame the different response parts.
|
The ``multipart`` major type brings along string sentinel delimiting as a means to frame the different response parts.
|
||||||
There are many drawbacks to this framing technique:
|
There are many drawbacks to this framing technique:
|
||||||
|
|
||||||
@ -496,6 +594,15 @@ There are many drawbacks to this framing technique:
|
|||||||
2. It is resource-intensive to parse.
|
2. It is resource-intensive to parse.
|
||||||
3. It is complex to parse safely [#]_ [#]_ [#]_ [#]_.
|
3. It is complex to parse safely [#]_ [#]_ [#]_ [#]_.
|
||||||
|
|
||||||
|
A previous revision of this specification allowed requesting one or more contiguous sequences from one or more shares.
|
||||||
|
This *superficially* mirrored the Foolscap based interface somewhat closely.
|
||||||
|
The interface was simplified to this version because this version is all that is required to let clients retrieve any desired information.
|
||||||
|
It only requires that the client issue multiple requests.
|
||||||
|
This can be done with pipelining or parallel requests to avoid an additional latency penalty.
|
||||||
|
In the future,
|
||||||
|
if there are performance goals,
|
||||||
|
benchmarks can demonstrate whether they are achieved by a more complicated interface or some other change.
|
||||||
|
|
||||||
Mutable
|
Mutable
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -527,7 +634,6 @@ For example::
|
|||||||
"test": [{
|
"test": [{
|
||||||
"offset": 3,
|
"offset": 3,
|
||||||
"size": 5,
|
"size": 5,
|
||||||
"operator": "eq",
|
|
||||||
"specimen": "hello"
|
"specimen": "hello"
|
||||||
}, ...],
|
}, ...],
|
||||||
"write": [{
|
"write": [{
|
||||||
@ -553,6 +659,9 @@ For example::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
A test vector or read vector that read beyond the boundaries of existing data will return nothing for any bytes past the end.
|
||||||
|
As a result, if there is no data at all, an empty bytestring is returned no matter what the offset or length.
|
||||||
|
|
||||||
Reading
|
Reading
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@ -576,6 +685,137 @@ Just like ``GET /v1/mutable/:storage_index``.
|
|||||||
Advise the server the data read from the indicated share was corrupt.
|
Advise the server the data read from the indicated share was corrupt.
|
||||||
Just like the immutable version.
|
Just like the immutable version.
|
||||||
|
|
||||||
|
Sample Interactions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Immutable Data
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Create a bucket for storage index ``AAAAAAAAAAAAAAAA`` to hold two immutable shares, discovering that share ``1`` was already uploaded::
|
||||||
|
|
||||||
|
POST /v1/immutable/AAAAAAAAAAAAAAAA
|
||||||
|
{"renew-secret": "efgh", "cancel-secret": "ijkl",
|
||||||
|
"share-numbers": [1, 7], "allocated-size": 48}
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
{"already-have": [1], "allocated": [7]}
|
||||||
|
|
||||||
|
#. Upload the content for immutable share ``7``::
|
||||||
|
|
||||||
|
PATCH /v1/immutable/AAAAAAAAAAAAAAAA/7
|
||||||
|
Content-Range: bytes 0-15/48
|
||||||
|
<first 16 bytes of share data>
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
PATCH /v1/immutable/AAAAAAAAAAAAAAAA/7
|
||||||
|
Content-Range: bytes 16-31/48
|
||||||
|
<second 16 bytes of share data>
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
PATCH /v1/immutable/AAAAAAAAAAAAAAAA/7
|
||||||
|
Content-Range: bytes 32-47/48
|
||||||
|
<final 16 bytes of share data>
|
||||||
|
|
||||||
|
201 CREATED
|
||||||
|
|
||||||
|
#. Download the content of the previously uploaded immutable share ``7``::
|
||||||
|
|
||||||
|
GET /v1/immutable/AAAAAAAAAAAAAAAA?share=7&offset=0&size=48
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
<complete 48 bytes of previously uploaded data>
|
||||||
|
|
||||||
|
#. Renew the lease on all immutable shares in bucket ``AAAAAAAAAAAAAAAA``::
|
||||||
|
|
||||||
|
PUT /v1/lease/AAAAAAAAAAAAAAAA
|
||||||
|
{"renew-secret": "efgh", "cancel-secret": "ijkl"}
|
||||||
|
|
||||||
|
204 NO CONTENT
|
||||||
|
|
||||||
|
Mutable Data
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Create mutable share number ``3`` with ``10`` bytes of data in slot ``BBBBBBBBBBBBBBBB``.
|
||||||
|
The special test vector of size 1 but empty bytes will only pass
|
||||||
|
if there is no existing share,
|
||||||
|
otherwise it will read a byte which won't match `b""`::
|
||||||
|
|
||||||
|
POST /v1/mutable/BBBBBBBBBBBBBBBB/read-test-write
|
||||||
|
{
|
||||||
|
"secrets": {
|
||||||
|
"write-enabler": "abcd",
|
||||||
|
"lease-renew": "efgh",
|
||||||
|
"lease-cancel": "ijkl"
|
||||||
|
},
|
||||||
|
"test-write-vectors": {
|
||||||
|
3: {
|
||||||
|
"test": [{
|
||||||
|
"offset": 0,
|
||||||
|
"size": 1,
|
||||||
|
"specimen": ""
|
||||||
|
}],
|
||||||
|
"write": [{
|
||||||
|
"offset": 0,
|
||||||
|
"data": "xxxxxxxxxx"
|
||||||
|
}],
|
||||||
|
"new-length": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read-vector": []
|
||||||
|
}
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
|
||||||
|
#. Safely rewrite the contents of a known version of mutable share number ``3`` (or fail)::
|
||||||
|
|
||||||
|
POST /v1/mutable/BBBBBBBBBBBBBBBB/read-test-write
|
||||||
|
{
|
||||||
|
"secrets": {
|
||||||
|
"write-enabler": "abcd",
|
||||||
|
"lease-renew": "efgh",
|
||||||
|
"lease-cancel": "ijkl"
|
||||||
|
},
|
||||||
|
"test-write-vectors": {
|
||||||
|
3: {
|
||||||
|
"test": [{
|
||||||
|
"offset": 0,
|
||||||
|
"size": <length of checkstring>,
|
||||||
|
"specimen": "<checkstring>"
|
||||||
|
}],
|
||||||
|
"write": [{
|
||||||
|
"offset": 0,
|
||||||
|
"data": "yyyyyyyyyy"
|
||||||
|
}],
|
||||||
|
"new-length": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read-vector": []
|
||||||
|
}
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
|
||||||
|
#. Download the contents of share number ``3``::
|
||||||
|
|
||||||
|
GET /v1/mutable/BBBBBBBBBBBBBBBB?share=3&offset=0&size=10
|
||||||
|
<complete 16 bytes of previously uploaded data>
|
||||||
|
|
||||||
|
#. Renew the lease on previously uploaded mutable share in slot ``BBBBBBBBBBBBBBBB``::
|
||||||
|
|
||||||
|
PUT /v1/lease/BBBBBBBBBBBBBBBB
|
||||||
|
{"renew-secret": "efgh", "cancel-secret": "ijkl"}
|
||||||
|
|
||||||
|
204 NO CONTENT
|
||||||
|
|
||||||
.. _RFC 7469: https://tools.ietf.org/html/rfc7469#section-2.4
|
.. _RFC 7469: https://tools.ietf.org/html/rfc7469#section-2.4
|
||||||
|
|
||||||
.. _RFC 7049: https://tools.ietf.org/html/rfc7049#section-4
|
.. _RFC 7049: https://tools.ietf.org/html/rfc7049#section-4
|
||||||
|
@ -178,8 +178,8 @@ Announcing the Release Candidate
|
|||||||
````````````````````````````````
|
````````````````````````````````
|
||||||
|
|
||||||
The release-candidate should be announced by posting to the
|
The release-candidate should be announced by posting to the
|
||||||
mailing-list (tahoe-dev@tahoe-lafs.org). For example:
|
mailing-list (tahoe-dev@lists.tahoe-lafs.org). For example:
|
||||||
https://tahoe-lafs.org/pipermail/tahoe-dev/2020-October/009995.html
|
https://lists.tahoe-lafs.org/pipermail/tahoe-dev/2020-October/009978.html
|
||||||
|
|
||||||
|
|
||||||
Is The Release Done Yet?
|
Is The Release Done Yet?
|
||||||
|
@ -238,7 +238,7 @@ You can chat with other users of and hackers of this software on the
|
|||||||
#tahoe-lafs IRC channel at ``irc.libera.chat``, or on the `tahoe-dev mailing
|
#tahoe-lafs IRC channel at ``irc.libera.chat``, or on the `tahoe-dev mailing
|
||||||
list`_.
|
list`_.
|
||||||
|
|
||||||
.. _tahoe-dev mailing list: https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev
|
.. _tahoe-dev mailing list: https://lists.tahoe-lafs.org/mailman/listinfo/tahoe-dev
|
||||||
|
|
||||||
|
|
||||||
Complain
|
Complain
|
||||||
|
87
docs/specifications/derive_renewal_secret.py
Normal file
87
docs/specifications/derive_renewal_secret.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
This is a reference implementation of the lease renewal secret derivation
|
||||||
|
protocol in use by Tahoe-LAFS clients as of 1.16.0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from allmydata.util.base32 import (
|
||||||
|
a2b as b32decode,
|
||||||
|
b2a as b32encode,
|
||||||
|
)
|
||||||
|
from allmydata.util.hashutil import (
|
||||||
|
tagged_hash,
|
||||||
|
tagged_pair_hash,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def derive_renewal_secret(lease_secret: bytes, storage_index: bytes, tubid: bytes) -> bytes:
|
||||||
|
assert len(lease_secret) == 32
|
||||||
|
assert len(storage_index) == 16
|
||||||
|
assert len(tubid) == 20
|
||||||
|
|
||||||
|
bucket_renewal_tag = b"allmydata_bucket_renewal_secret_v1"
|
||||||
|
file_renewal_tag = b"allmydata_file_renewal_secret_v1"
|
||||||
|
client_renewal_tag = b"allmydata_client_renewal_secret_v1"
|
||||||
|
|
||||||
|
client_renewal_secret = tagged_hash(lease_secret, client_renewal_tag)
|
||||||
|
file_renewal_secret = tagged_pair_hash(
|
||||||
|
file_renewal_tag,
|
||||||
|
client_renewal_secret,
|
||||||
|
storage_index,
|
||||||
|
)
|
||||||
|
peer_id = tubid
|
||||||
|
|
||||||
|
return tagged_pair_hash(bucket_renewal_tag, file_renewal_secret, peer_id)
|
||||||
|
|
||||||
|
def demo():
|
||||||
|
secret = b32encode(derive_renewal_secret(
|
||||||
|
b"lease secretxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
b"storage indexxxx",
|
||||||
|
b"tub idxxxxxxxxxxxxxx",
|
||||||
|
)).decode("ascii")
|
||||||
|
print("An example renewal secret: {}".format(secret))
|
||||||
|
|
||||||
|
def test():
|
||||||
|
# These test vectors created by intrumenting Tahoe-LAFS
|
||||||
|
# bb57fcfb50d4e01bbc4de2e23dbbf7a60c004031 to emit `self.renew_secret` in
|
||||||
|
# allmydata.immutable.upload.ServerTracker.query and then uploading a
|
||||||
|
# couple files to a couple different storage servers.
|
||||||
|
test_vector = [
|
||||||
|
dict(lease_secret=b"boity2cdh7jvl3ltaeebuiobbspjmbuopnwbde2yeh4k6x7jioga",
|
||||||
|
storage_index=b"vrttmwlicrzbt7gh5qsooogr7u",
|
||||||
|
tubid=b"v67jiisoty6ooyxlql5fuucitqiok2ic",
|
||||||
|
expected=b"osd6wmc5vz4g3ukg64sitmzlfiaaordutrez7oxdp5kkze7zp5zq",
|
||||||
|
),
|
||||||
|
dict(lease_secret=b"boity2cdh7jvl3ltaeebuiobbspjmbuopnwbde2yeh4k6x7jioga",
|
||||||
|
storage_index=b"75gmmfts772ww4beiewc234o5e",
|
||||||
|
tubid=b"v67jiisoty6ooyxlql5fuucitqiok2ic",
|
||||||
|
expected=b"35itmusj7qm2pfimh62snbyxp3imreofhx4djr7i2fweta75szda",
|
||||||
|
),
|
||||||
|
dict(lease_secret=b"boity2cdh7jvl3ltaeebuiobbspjmbuopnwbde2yeh4k6x7jioga",
|
||||||
|
storage_index=b"75gmmfts772ww4beiewc234o5e",
|
||||||
|
tubid=b"lh5fhobkjrmkqjmkxhy3yaonoociggpz",
|
||||||
|
expected=b"srrlruge47ws3lm53vgdxprgqb6bz7cdblnuovdgtfkqrygrjm4q",
|
||||||
|
),
|
||||||
|
dict(lease_secret=b"vacviff4xfqxsbp64tdr3frg3xnkcsuwt5jpyat2qxcm44bwu75a",
|
||||||
|
storage_index=b"75gmmfts772ww4beiewc234o5e",
|
||||||
|
tubid=b"lh5fhobkjrmkqjmkxhy3yaonoociggpz",
|
||||||
|
expected=b"b4jledjiqjqekbm2erekzqumqzblegxi23i5ojva7g7xmqqnl5pq",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for n, item in enumerate(test_vector):
|
||||||
|
derived = b32encode(derive_renewal_secret(
|
||||||
|
b32decode(item["lease_secret"]),
|
||||||
|
b32decode(item["storage_index"]),
|
||||||
|
b32decode(item["tubid"]),
|
||||||
|
))
|
||||||
|
assert derived == item["expected"] , \
|
||||||
|
"Test vector {} failed: {} (expected) != {} (derived)".format(
|
||||||
|
n,
|
||||||
|
item["expected"],
|
||||||
|
derived,
|
||||||
|
)
|
||||||
|
print("{} test vectors validated".format(len(test_vector)))
|
||||||
|
|
||||||
|
test()
|
||||||
|
demo()
|
@ -14,5 +14,6 @@ the data formats used by Tahoe.
|
|||||||
URI-extension
|
URI-extension
|
||||||
mutable
|
mutable
|
||||||
dirnodes
|
dirnodes
|
||||||
|
lease
|
||||||
servers-of-happiness
|
servers-of-happiness
|
||||||
backends/raic
|
backends/raic
|
||||||
|
69
docs/specifications/lease.rst
Normal file
69
docs/specifications/lease.rst
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
.. -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
.. _share leases:
|
||||||
|
|
||||||
|
Share Leases
|
||||||
|
============
|
||||||
|
|
||||||
|
A lease is a marker attached to a share indicating that some client has asked for that share to be retained for some amount of time.
|
||||||
|
The intent is to allow clients and servers to collaborate to determine which data should still be retained and which can be discarded to reclaim storage space.
|
||||||
|
Zero or more leases may be attached to any particular share.
|
||||||
|
|
||||||
|
Renewal Secrets
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Each lease is uniquely identified by its **renewal secret**.
|
||||||
|
This is a 32 byte string which can be used to extend the validity period of that lease.
|
||||||
|
|
||||||
|
To a storage server a renewal secret is an opaque value which is only ever compared to other renewal secrets to determine equality.
|
||||||
|
|
||||||
|
Storage clients will typically want to follow a scheme to deterministically derive the renewal secret for a particular share from information the client already holds about that share.
|
||||||
|
This allows a client to maintain and renew single long-lived lease without maintaining additional local state.
|
||||||
|
|
||||||
|
The scheme in use in Tahoe-LAFS as of 1.16.0 is as follows.
|
||||||
|
|
||||||
|
* The **netstring encoding** of a byte string is the concatenation of:
|
||||||
|
|
||||||
|
* the ascii encoding of the base 10 representation of the length of the string
|
||||||
|
* ``":"``
|
||||||
|
* the string itself
|
||||||
|
* ``","``
|
||||||
|
|
||||||
|
* The **sha256d digest** is the **sha256 digest** of the **sha256 digest** of a string.
|
||||||
|
* The **sha256d tagged digest** is the **sha256d digest** of the concatenation of the **netstring encoding** of one string with one other unmodified string.
|
||||||
|
* The **sha256d tagged pair digest** the **sha256d digest** of the concatenation of the **netstring encodings** of each of three strings.
|
||||||
|
* The **bucket renewal tag** is ``"allmydata_bucket_renewal_secret_v1"``.
|
||||||
|
* The **file renewal tag** is ``"allmydata_file_renewal_secret_v1"``.
|
||||||
|
* The **client renewal tag** is ``"allmydata_client_renewal_secret_v1"``.
|
||||||
|
* The **lease secret** is a 32 byte string, typically randomly generated once and then persisted for all future uses.
|
||||||
|
* The **client renewal secret** is the **sha256d tagged digest** of (**lease secret**, **client renewal tag**).
|
||||||
|
* The **storage index** is constructed using a capability-type-specific scheme.
|
||||||
|
See ``storage_index_hash`` and ``ssk_storage_index_hash`` calls in ``src/allmydata/uri.py``.
|
||||||
|
* The **file renewal secret** is the **sha256d tagged pair digest** of (**file renewal tag**, **client renewal secret**, **storage index**).
|
||||||
|
* The **base32 encoding** is ``base64.b32encode`` lowercased and with trailing ``=`` stripped.
|
||||||
|
* The **peer id** is the **base32 encoding** of the SHA1 digest of the server's x509 certificate.
|
||||||
|
* The **renewal secret** is the **sha256d tagged pair digest** of (**bucket renewal tag**, **file renewal secret**, **peer id**).
|
||||||
|
|
||||||
|
A reference implementation is available.
|
||||||
|
|
||||||
|
.. literalinclude:: derive_renewal_secret.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Cancel Secrets
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Lease cancellation is unimplemented.
|
||||||
|
Nevertheless,
|
||||||
|
a cancel secret is sent by storage clients to storage servers and stored in lease records.
|
||||||
|
|
||||||
|
The scheme for deriving **cancel secret** in use in Tahoe-LAFS as of 1.16.0 is similar to that used to derive the **renewal secret**.
|
||||||
|
|
||||||
|
The differences are:
|
||||||
|
|
||||||
|
* Use of **client renewal tag** is replaced by use of **client cancel tag**.
|
||||||
|
* Use of **file renewal secret** is replaced by use of **file cancel tag**.
|
||||||
|
* Use of **bucket renewal tag** is replaced by use of **bucket cancel tag**.
|
||||||
|
* **client cancel tag** is ``"allmydata_client_cancel_secret_v1"``.
|
||||||
|
* **file cancel tag** is ``"allmydata_file_cancel_secret_v1"``.
|
||||||
|
* **bucket cancel tag** is ``"allmydata_bucket_cancel_secret_v1"``.
|
244
integration/test_i2p.py
Normal file
244
integration/test_i2p.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
"""
|
||||||
|
Integration tests for I2P support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from os.path import join, exists
|
||||||
|
from os import mkdir
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def which(path):
|
||||||
|
# This will result in skipping I2P tests on Python 2. Oh well.
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
|
from eliot import log_call
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytest_twisted
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
from twisted.python.filepath import (
|
||||||
|
FilePath,
|
||||||
|
)
|
||||||
|
from twisted.internet.error import ProcessExitedAlready
|
||||||
|
|
||||||
|
from allmydata.test.common import (
|
||||||
|
write_introducer,
|
||||||
|
)
|
||||||
|
|
||||||
|
if which("docker") is None:
|
||||||
|
pytest.skip('Skipping I2P tests since Docker is unavailable', allow_module_level=True)
|
||||||
|
# Docker on Windows machines sometimes expects Windows-y Docker images, so just
|
||||||
|
# don't bother.
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
pytest.skip('Skipping I2P tests on Windows', allow_module_level=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def i2p_network(reactor, temp_dir, request):
|
||||||
|
"""Fixture to start up local i2pd."""
|
||||||
|
proto = util._MagicTextProtocol("ephemeral keys")
|
||||||
|
reactor.spawnProcess(
|
||||||
|
proto,
|
||||||
|
which("docker"),
|
||||||
|
(
|
||||||
|
"docker", "run", "-p", "7656:7656", "purplei2p/i2pd",
|
||||||
|
# Bad URL for reseeds, so it can't talk to other routers.
|
||||||
|
"--reseed.urls", "http://localhost:1/",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
try:
|
||||||
|
proto.transport.signalProcess("KILL")
|
||||||
|
util.block_with_timeout(proto.exited, reactor)
|
||||||
|
except ProcessExitedAlready:
|
||||||
|
pass
|
||||||
|
request.addfinalizer(cleanup)
|
||||||
|
|
||||||
|
util.block_with_timeout(proto.magic_seen, reactor, timeout=30)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
@log_call(
|
||||||
|
action_type=u"integration:i2p:introducer",
|
||||||
|
include_args=["temp_dir", "flog_gatherer"],
|
||||||
|
include_result=False,
|
||||||
|
)
|
||||||
|
def i2p_introducer(reactor, temp_dir, flog_gatherer, request):
|
||||||
|
config = '''
|
||||||
|
[node]
|
||||||
|
nickname = introducer_i2p
|
||||||
|
web.port = 4561
|
||||||
|
log_gatherer.furl = {log_furl}
|
||||||
|
'''.format(log_furl=flog_gatherer)
|
||||||
|
|
||||||
|
intro_dir = join(temp_dir, 'introducer_i2p')
|
||||||
|
print("making introducer", intro_dir)
|
||||||
|
|
||||||
|
if not exists(intro_dir):
|
||||||
|
mkdir(intro_dir)
|
||||||
|
done_proto = util._ProcessExitedProtocol()
|
||||||
|
util._tahoe_runner_optional_coverage(
|
||||||
|
done_proto,
|
||||||
|
reactor,
|
||||||
|
request,
|
||||||
|
(
|
||||||
|
'create-introducer',
|
||||||
|
'--listen=i2p',
|
||||||
|
intro_dir,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
pytest_twisted.blockon(done_proto.done)
|
||||||
|
|
||||||
|
# over-write the config file with our stuff
|
||||||
|
with open(join(intro_dir, 'tahoe.cfg'), 'w') as f:
|
||||||
|
f.write(config)
|
||||||
|
|
||||||
|
# "tahoe run" is consistent across Linux/macOS/Windows, unlike the old
|
||||||
|
# "start" command.
|
||||||
|
protocol = util._MagicTextProtocol('introducer running')
|
||||||
|
transport = util._tahoe_runner_optional_coverage(
|
||||||
|
protocol,
|
||||||
|
reactor,
|
||||||
|
request,
|
||||||
|
(
|
||||||
|
'run',
|
||||||
|
intro_dir,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
try:
|
||||||
|
transport.signalProcess('TERM')
|
||||||
|
util.block_with_timeout(protocol.exited, reactor)
|
||||||
|
except ProcessExitedAlready:
|
||||||
|
pass
|
||||||
|
request.addfinalizer(cleanup)
|
||||||
|
|
||||||
|
pytest_twisted.blockon(protocol.magic_seen)
|
||||||
|
return transport
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def i2p_introducer_furl(i2p_introducer, temp_dir):
|
||||||
|
furl_fname = join(temp_dir, 'introducer_i2p', 'private', 'introducer.furl')
|
||||||
|
while not exists(furl_fname):
|
||||||
|
print("Don't see {} yet".format(furl_fname))
|
||||||
|
sleep(.1)
|
||||||
|
furl = open(furl_fname, 'r').read()
|
||||||
|
return furl
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_twisted.inlineCallbacks
|
||||||
|
def test_i2p_service_storage(reactor, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl):
|
||||||
|
yield _create_anonymous_node(reactor, 'carol_i2p', 8008, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl)
|
||||||
|
yield _create_anonymous_node(reactor, 'dave_i2p', 8009, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl)
|
||||||
|
# ensure both nodes are connected to "a grid" by uploading
|
||||||
|
# something via carol, and retrieve it using dave.
|
||||||
|
gold_path = join(temp_dir, "gold")
|
||||||
|
with open(gold_path, "w") as f:
|
||||||
|
f.write(
|
||||||
|
"The object-capability model is a computer security model. A "
|
||||||
|
"capability describes a transferable right to perform one (or "
|
||||||
|
"more) operations on a given object."
|
||||||
|
)
|
||||||
|
# XXX could use treq or similar to POST these to their respective
|
||||||
|
# WUIs instead ...
|
||||||
|
|
||||||
|
proto = util._CollectOutputProtocol()
|
||||||
|
reactor.spawnProcess(
|
||||||
|
proto,
|
||||||
|
sys.executable,
|
||||||
|
(
|
||||||
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
||||||
|
'-d', join(temp_dir, 'carol_i2p'),
|
||||||
|
'put', gold_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yield proto.done
|
||||||
|
cap = proto.output.getvalue().strip().split()[-1]
|
||||||
|
print("TEH CAP!", cap)
|
||||||
|
|
||||||
|
proto = util._CollectOutputProtocol(capture_stderr=False)
|
||||||
|
reactor.spawnProcess(
|
||||||
|
proto,
|
||||||
|
sys.executable,
|
||||||
|
(
|
||||||
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
||||||
|
'-d', join(temp_dir, 'dave_i2p'),
|
||||||
|
'get', cap,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yield proto.done
|
||||||
|
|
||||||
|
dave_got = proto.output.getvalue().strip()
|
||||||
|
assert dave_got == open(gold_path, 'rb').read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_twisted.inlineCallbacks
|
||||||
|
def _create_anonymous_node(reactor, name, control_port, request, temp_dir, flog_gatherer, i2p_network, introducer_furl):
|
||||||
|
node_dir = FilePath(temp_dir).child(name)
|
||||||
|
web_port = "tcp:{}:interface=localhost".format(control_port + 2000)
|
||||||
|
|
||||||
|
print("creating", node_dir.path)
|
||||||
|
node_dir.makedirs()
|
||||||
|
proto = util._DumpOutputProtocol(None)
|
||||||
|
reactor.spawnProcess(
|
||||||
|
proto,
|
||||||
|
sys.executable,
|
||||||
|
(
|
||||||
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
||||||
|
'create-node',
|
||||||
|
'--nickname', name,
|
||||||
|
'--introducer', introducer_furl,
|
||||||
|
'--hide-ip',
|
||||||
|
'--listen', 'i2p',
|
||||||
|
node_dir.path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yield proto.done
|
||||||
|
|
||||||
|
|
||||||
|
# Which services should this client connect to?
|
||||||
|
write_introducer(node_dir, "default", introducer_furl)
|
||||||
|
with node_dir.child('tahoe.cfg').open('w') as f:
|
||||||
|
node_config = '''
|
||||||
|
[node]
|
||||||
|
nickname = %(name)s
|
||||||
|
web.port = %(web_port)s
|
||||||
|
web.static = public_html
|
||||||
|
log_gatherer.furl = %(log_furl)s
|
||||||
|
|
||||||
|
[i2p]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[client]
|
||||||
|
shares.needed = 1
|
||||||
|
shares.happy = 1
|
||||||
|
shares.total = 2
|
||||||
|
|
||||||
|
''' % {
|
||||||
|
'name': name,
|
||||||
|
'web_port': web_port,
|
||||||
|
'log_furl': flog_gatherer,
|
||||||
|
}
|
||||||
|
node_config = node_config.encode("utf-8")
|
||||||
|
f.write(node_config)
|
||||||
|
|
||||||
|
print("running")
|
||||||
|
yield util._run_node(reactor, node_dir.path, request, None)
|
||||||
|
print("okay, launched")
|
@ -16,7 +16,7 @@ if git diff-index --quiet HEAD; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
git config user.name 'Build Automation'
|
git config user.name 'Build Automation'
|
||||||
git config user.email 'tahoe-dev@tahoe-lafs.org'
|
git config user.email 'tahoe-dev@lists.tahoe-lafs.org'
|
||||||
|
|
||||||
git add tahoe-deps.json tahoe-ported.json
|
git add tahoe-deps.json tahoe-ported.json
|
||||||
git commit -m "\
|
git commit -m "\
|
||||||
|
0
newsfragments/3525.minor
Normal file
0
newsfragments/3525.minor
Normal file
0
newsfragments/3528.minor
Normal file
0
newsfragments/3528.minor
Normal file
0
newsfragments/3563.minor
Normal file
0
newsfragments/3563.minor
Normal file
0
newsfragments/3743.minor
Normal file
0
newsfragments/3743.minor
Normal file
1
newsfragments/3749.documentation
Normal file
1
newsfragments/3749.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
Documentation and installation links in the README have been fixed.
|
0
newsfragments/3751.minor
Normal file
0
newsfragments/3751.minor
Normal file
1
newsfragments/3757.other
Normal file
1
newsfragments/3757.other
Normal file
@ -0,0 +1 @@
|
|||||||
|
Refactored test_introducer in web tests to use custom base test cases
|
0
newsfragments/3759.minor
Normal file
0
newsfragments/3759.minor
Normal file
0
newsfragments/3760.minor
Normal file
0
newsfragments/3760.minor
Normal file
0
newsfragments/3763.minor
Normal file
0
newsfragments/3763.minor
Normal file
1
newsfragments/3764.documentation
Normal file
1
newsfragments/3764.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The Great Black Swamp proposed specification now includes sample interactions to demonstrate expected usage patterns.
|
1
newsfragments/3765.documentation
Normal file
1
newsfragments/3765.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The Great Black Swamp proposed specification now includes a glossary.
|
1
newsfragments/3769.documentation
Normal file
1
newsfragments/3769.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The Great Black Swamp specification now allows parallel upload of immutable share data.
|
0
newsfragments/3773.minor
Normal file
0
newsfragments/3773.minor
Normal file
1
newsfragments/3774.documentation
Normal file
1
newsfragments/3774.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
There is now a specification for the scheme which Tahoe-LAFS storage clients use to derive their lease renewal secrets.
|
1
newsfragments/3777.documentation
Normal file
1
newsfragments/3777.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The Great Black Swamp proposed specification now has a simplified interface for reading data from immutable shares.
|
1
newsfragments/3779.bugfix
Normal file
1
newsfragments/3779.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fixed bug where share corruption events were not logged on storage servers running on Windows.
|
0
newsfragments/3781.minor
Normal file
0
newsfragments/3781.minor
Normal file
1
newsfragments/3782.documentation
Normal file
1
newsfragments/3782.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
tahoe-dev mailing list is now at tahoe-dev@lists.tahoe-lafs.org.
|
0
newsfragments/3784.minor
Normal file
0
newsfragments/3784.minor
Normal file
1
newsfragments/3785.documentation
Normal file
1
newsfragments/3785.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The Great Black Swamp specification now describes the required authorization scheme.
|
1
newsfragments/3786.feature
Normal file
1
newsfragments/3786.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
tahoe-lafs now provides its statistics also in OpenMetrics format (for Prometheus et. al.) at `/statistics?t=openmetrics`.
|
0
newsfragments/3792.minor
Normal file
0
newsfragments/3792.minor
Normal file
0
newsfragments/3793.minor
Normal file
0
newsfragments/3793.minor
Normal file
0
newsfragments/3795.minor
Normal file
0
newsfragments/3795.minor
Normal file
0
newsfragments/3797.minor
Normal file
0
newsfragments/3797.minor
Normal file
0
newsfragments/3798.minor
Normal file
0
newsfragments/3798.minor
Normal file
0
newsfragments/3799.minor
Normal file
0
newsfragments/3799.minor
Normal file
1
newsfragments/3801.bugfix
Normal file
1
newsfragments/3801.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
When uploading an immutable, overlapping writes that include conflicting data are rejected. In practice, this likely didn't happen in real-world usage.
|
0
newsfragments/3805.minor
Normal file
0
newsfragments/3805.minor
Normal file
0
newsfragments/3806.minor
Normal file
0
newsfragments/3806.minor
Normal file
1
newsfragments/3808.installation
Normal file
1
newsfragments/3808.installation
Normal file
@ -0,0 +1 @@
|
|||||||
|
Tahoe-LAFS now supports running on NixOS 21.05 with Python 3.
|
0
newsfragments/3810.minor
Normal file
0
newsfragments/3810.minor
Normal file
0
newsfragments/3812.minor
Normal file
0
newsfragments/3812.minor
Normal file
1
newsfragments/3815.documentation
Normal file
1
newsfragments/3815.documentation
Normal file
@ -0,0 +1 @@
|
|||||||
|
The news file for future releases will include a section for changes with a security impact.
|
19
nix/collections-extended.nix
Normal file
19
nix/collections-extended.nix
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{ lib, buildPythonPackage, fetchPypi }:
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "collections-extended";
|
||||||
|
version = "1.0.3";
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
inherit pname version;
|
||||||
|
sha256 = "0lb69x23asd68n0dgw6lzxfclavrp2764xsnh45jm97njdplznkw";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Tests aren't in tarball, for 1.0.3 at least.
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
homepage = https://github.com/mlenzen/collections-extended;
|
||||||
|
description = "Extra Python Collections - bags (multisets), setlists (unique list / indexed set), RangeMap and IndexedDict";
|
||||||
|
license = licenses.asl20;
|
||||||
|
};
|
||||||
|
}
|
@ -2,22 +2,32 @@ self: super: {
|
|||||||
python27 = super.python27.override {
|
python27 = super.python27.override {
|
||||||
packageOverrides = python-self: python-super: {
|
packageOverrides = python-self: python-super: {
|
||||||
# eliot is not part of nixpkgs at all at this time.
|
# eliot is not part of nixpkgs at all at this time.
|
||||||
eliot = python-self.callPackage ./eliot.nix { };
|
eliot = python-self.pythonPackages.callPackage ./eliot.nix { };
|
||||||
|
|
||||||
# NixOS autobahn package has trollius as a dependency, although
|
# NixOS autobahn package has trollius as a dependency, although
|
||||||
# it is optional. Trollius is unmaintained and fails on CI.
|
# it is optional. Trollius is unmaintained and fails on CI.
|
||||||
autobahn = python-super.callPackage ./autobahn.nix { };
|
autobahn = python-super.pythonPackages.callPackage ./autobahn.nix { };
|
||||||
|
|
||||||
# Porting to Python 3 is greatly aided by the future package. A
|
# Porting to Python 3 is greatly aided by the future package. A
|
||||||
# slightly newer version than appears in nixos 19.09 is helpful.
|
# slightly newer version than appears in nixos 19.09 is helpful.
|
||||||
future = python-super.callPackage ./future.nix { };
|
future = python-super.pythonPackages.callPackage ./future.nix { };
|
||||||
|
|
||||||
# Need version of pyutil that supports Python 3. The version in 19.09
|
# Need version of pyutil that supports Python 3. The version in 19.09
|
||||||
# is too old.
|
# is too old.
|
||||||
pyutil = python-super.callPackage ./pyutil.nix { };
|
pyutil = python-super.pythonPackages.callPackage ./pyutil.nix { };
|
||||||
|
|
||||||
# Need a newer version of Twisted, too.
|
# Need a newer version of Twisted, too.
|
||||||
twisted = python-super.callPackage ./twisted.nix { };
|
twisted = python-super.pythonPackages.callPackage ./twisted.nix { };
|
||||||
|
|
||||||
|
# collections-extended is not part of nixpkgs at this time.
|
||||||
|
collections-extended = python-super.pythonPackages.callPackage ./collections-extended.nix { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
python39 = super.python39.override {
|
||||||
|
packageOverrides = python-self: python-super: {
|
||||||
|
# collections-extended is not part of nixpkgs at this time.
|
||||||
|
collections-extended = python-super.pythonPackages.callPackage ./collections-extended.nix { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
7
nix/py3.nix
Normal file
7
nix/py3.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This is the main entrypoint for the Tahoe-LAFS derivation.
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
# Add our Python packages to nixpkgs to simplify the expression for the
|
||||||
|
# Tahoe-LAFS derivation.
|
||||||
|
let pkgs' = pkgs.extend (import ./overlays.nix);
|
||||||
|
# Evaluate the expression for our Tahoe-LAFS derivation.
|
||||||
|
in pkgs'.python39.pkgs.callPackage ./tahoe-lafs.nix { }
|
@ -72,10 +72,6 @@ python.pkgs.buildPythonPackage rec {
|
|||||||
rm src/allmydata/test/test_connections.py
|
rm src/allmydata/test/test_connections.py
|
||||||
rm src/allmydata/test/cli/test_create.py
|
rm src/allmydata/test/cli/test_create.py
|
||||||
|
|
||||||
# Since we're deleting files, this complains they're missing. For now Nix
|
|
||||||
# is Python 2-only, anyway, so these tests don't add anything yet.
|
|
||||||
rm src/allmydata/test/test_python3.py
|
|
||||||
|
|
||||||
# Generate _version.py ourselves since we can't rely on the Python code
|
# Generate _version.py ourselves since we can't rely on the Python code
|
||||||
# extracting the information from the .git directory we excluded.
|
# extracting the information from the .git directory we excluded.
|
||||||
cat > src/allmydata/_version.py <<EOF
|
cat > src/allmydata/_version.py <<EOF
|
||||||
@ -101,7 +97,7 @@ EOF
|
|||||||
setuptoolsTrial pyasn1 zope_interface
|
setuptoolsTrial pyasn1 zope_interface
|
||||||
service-identity pyyaml magic-wormhole treq
|
service-identity pyyaml magic-wormhole treq
|
||||||
eliot autobahn cryptography netifaces setuptools
|
eliot autobahn cryptography netifaces setuptools
|
||||||
future pyutil distro configparser
|
future pyutil distro configparser collections-extended
|
||||||
];
|
];
|
||||||
|
|
||||||
checkInputs = with python.pkgs; [
|
checkInputs = with python.pkgs; [
|
||||||
@ -111,6 +107,7 @@ EOF
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
html5lib
|
html5lib
|
||||||
tenacity
|
tenacity
|
||||||
|
prometheus_client
|
||||||
];
|
];
|
||||||
|
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
|
@ -155,7 +155,7 @@ Planet Earth
|
|||||||
[4] https://github.com/tahoe-lafs/tahoe-lafs/blob/tahoe-lafs-1.15.1/COPYING.GPL
|
[4] https://github.com/tahoe-lafs/tahoe-lafs/blob/tahoe-lafs-1.15.1/COPYING.GPL
|
||||||
[5] https://github.com/tahoe-lafs/tahoe-lafs/blob/tahoe-lafs-1.15.1/COPYING.TGPPL.rst
|
[5] https://github.com/tahoe-lafs/tahoe-lafs/blob/tahoe-lafs-1.15.1/COPYING.TGPPL.rst
|
||||||
[6] https://tahoe-lafs.readthedocs.org/en/tahoe-lafs-1.15.1/INSTALL.html
|
[6] https://tahoe-lafs.readthedocs.org/en/tahoe-lafs-1.15.1/INSTALL.html
|
||||||
[7] https://tahoe-lafs.org/cgi-bin/mailman/listinfo/tahoe-dev
|
[7] https://lists.tahoe-lafs.org/mailman/listinfo/tahoe-dev
|
||||||
[8] https://tahoe-lafs.org/trac/tahoe-lafs/roadmap
|
[8] https://tahoe-lafs.org/trac/tahoe-lafs/roadmap
|
||||||
[9] https://github.com/tahoe-lafs/tahoe-lafs/blob/master/CREDITS
|
[9] https://github.com/tahoe-lafs/tahoe-lafs/blob/master/CREDITS
|
||||||
[10] https://tahoe-lafs.org/trac/tahoe-lafs/wiki/Dev
|
[10] https://tahoe-lafs.org/trac/tahoe-lafs/wiki/Dev
|
||||||
|
7
setup.py
7
setup.py
@ -137,6 +137,9 @@ install_requires = [
|
|||||||
|
|
||||||
# Backported configparser for Python 2:
|
# Backported configparser for Python 2:
|
||||||
"configparser ; python_version < '3.0'",
|
"configparser ; python_version < '3.0'",
|
||||||
|
|
||||||
|
# For the RangeMap datastructure.
|
||||||
|
"collections-extended",
|
||||||
]
|
]
|
||||||
|
|
||||||
setup_requires = [
|
setup_requires = [
|
||||||
@ -359,7 +362,7 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
|||||||
description='secure, decentralized, fault-tolerant file store',
|
description='secure, decentralized, fault-tolerant file store',
|
||||||
long_description=open('README.rst', 'r', encoding='utf-8').read(),
|
long_description=open('README.rst', 'r', encoding='utf-8').read(),
|
||||||
author='the Tahoe-LAFS project',
|
author='the Tahoe-LAFS project',
|
||||||
author_email='tahoe-dev@tahoe-lafs.org',
|
author_email='tahoe-dev@lists.tahoe-lafs.org',
|
||||||
url='https://tahoe-lafs.org/',
|
url='https://tahoe-lafs.org/',
|
||||||
license='GNU GPL', # see README.rst -- there is an alternative licence
|
license='GNU GPL', # see README.rst -- there is an alternative licence
|
||||||
cmdclass={"update_version": UpdateVersion,
|
cmdclass={"update_version": UpdateVersion,
|
||||||
@ -404,6 +407,8 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
|||||||
"tenacity",
|
"tenacity",
|
||||||
"paramiko",
|
"paramiko",
|
||||||
"pytest-timeout",
|
"pytest-timeout",
|
||||||
|
# Does our OpenMetrics endpoint adhere to the spec:
|
||||||
|
"prometheus-client == 0.11.0",
|
||||||
] + tor_requires + i2p_requires,
|
] + tor_requires + i2p_requires,
|
||||||
"tor": tor_requires,
|
"tor": tor_requires,
|
||||||
"i2p": i2p_requires,
|
"i2p": i2p_requires,
|
||||||
|
@ -475,7 +475,9 @@ class Share(object):
|
|||||||
# there was corruption somewhere in the given range
|
# there was corruption somewhere in the given range
|
||||||
reason = "corruption in share[%d-%d): %s" % (start, start+offset,
|
reason = "corruption in share[%d-%d): %s" % (start, start+offset,
|
||||||
str(f.value))
|
str(f.value))
|
||||||
self._rref.callRemoteOnly("advise_corrupt_share", reason.encode("utf-8"))
|
return self._rref.callRemote(
|
||||||
|
"advise_corrupt_share", reason.encode("utf-8")
|
||||||
|
).addErrback(log.err, "Error from remote call to advise_corrupt_share")
|
||||||
|
|
||||||
def _satisfy_block_hash_tree(self, needed_hashes):
|
def _satisfy_block_hash_tree(self, needed_hashes):
|
||||||
o_bh = self.actual_offsets["block_hashes"]
|
o_bh = self.actual_offsets["block_hashes"]
|
||||||
|
@ -15,7 +15,7 @@ from zope.interface import implementer
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from allmydata.interfaces import IStorageBucketWriter, IStorageBucketReader, \
|
from allmydata.interfaces import IStorageBucketWriter, IStorageBucketReader, \
|
||||||
FileTooLargeError, HASH_SIZE
|
FileTooLargeError, HASH_SIZE
|
||||||
from allmydata.util import mathutil, observer, pipeline
|
from allmydata.util import mathutil, observer, pipeline, log
|
||||||
from allmydata.util.assertutil import precondition
|
from allmydata.util.assertutil import precondition
|
||||||
from allmydata.storage.server import si_b2a
|
from allmydata.storage.server import si_b2a
|
||||||
|
|
||||||
@ -254,8 +254,7 @@ class WriteBucketProxy(object):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
return self._rref.callRemoteOnly("abort")
|
return self._rref.callRemote("abort").addErrback(log.err, "Error from remote call to abort an immutable write bucket")
|
||||||
|
|
||||||
|
|
||||||
def get_servername(self):
|
def get_servername(self):
|
||||||
return self._server.get_name()
|
return self._server.get_name()
|
||||||
|
@ -53,6 +53,14 @@ LeaseRenewSecret = Hash # used to protect lease renewal requests
|
|||||||
LeaseCancelSecret = Hash # was used to protect lease cancellation requests
|
LeaseCancelSecret = Hash # was used to protect lease cancellation requests
|
||||||
|
|
||||||
|
|
||||||
|
class DataTooLargeError(Exception):
|
||||||
|
"""The write went past the expected size of the bucket."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConflictingWriteError(Exception):
|
||||||
|
"""Two writes happened to same immutable with different data."""
|
||||||
|
|
||||||
|
|
||||||
class RIBucketWriter(RemoteInterface):
|
class RIBucketWriter(RemoteInterface):
|
||||||
""" Objects of this kind live on the server side. """
|
""" Objects of this kind live on the server side. """
|
||||||
def write(offset=Offset, data=ShareData):
|
def write(offset=Offset, data=ShareData):
|
||||||
@ -91,9 +99,9 @@ class RIBucketReader(RemoteInterface):
|
|||||||
|
|
||||||
TestVector = ListOf(TupleOf(Offset, ReadSize, bytes, bytes))
|
TestVector = ListOf(TupleOf(Offset, ReadSize, bytes, bytes))
|
||||||
# elements are (offset, length, operator, specimen)
|
# elements are (offset, length, operator, specimen)
|
||||||
# operator is one of "lt, le, eq, ne, ge, gt"
|
# operator must be b"eq", typically length==len(specimen), but one can ensure
|
||||||
# nop always passes and is used to fetch data while writing.
|
# writes don't happen to empty shares by setting length to 1 and specimen to
|
||||||
# you should use length==len(specimen) for everything except nop
|
# b"". The operator is still used for wire compatibility with old versions.
|
||||||
DataVector = ListOf(TupleOf(Offset, ShareData))
|
DataVector = ListOf(TupleOf(Offset, ShareData))
|
||||||
# (offset, data). This limits us to 30 writes of 1MiB each per call
|
# (offset, data). This limits us to 30 writes of 1MiB each per call
|
||||||
TestAndWriteVectorsForShares = DictOf(int,
|
TestAndWriteVectorsForShares = DictOf(int,
|
||||||
@ -154,25 +162,9 @@ class RIStorageServer(RemoteInterface):
|
|||||||
"""
|
"""
|
||||||
return Any() # returns None now, but future versions might change
|
return Any() # returns None now, but future versions might change
|
||||||
|
|
||||||
def renew_lease(storage_index=StorageIndex, renew_secret=LeaseRenewSecret):
|
|
||||||
"""
|
|
||||||
Renew the lease on a given bucket, resetting the timer to 31 days.
|
|
||||||
Some networks will use this, some will not. If there is no bucket for
|
|
||||||
the given storage_index, IndexError will be raised.
|
|
||||||
|
|
||||||
For mutable shares, if the given renew_secret does not match an
|
|
||||||
existing lease, IndexError will be raised with a note listing the
|
|
||||||
server-nodeids on the existing leases, so leases on migrated shares
|
|
||||||
can be renewed. For immutable shares, IndexError (without the note)
|
|
||||||
will be raised.
|
|
||||||
"""
|
|
||||||
return Any()
|
|
||||||
|
|
||||||
def get_buckets(storage_index=StorageIndex):
|
def get_buckets(storage_index=StorageIndex):
|
||||||
return DictOf(int, RIBucketReader, maxKeys=MAX_BUCKETS)
|
return DictOf(int, RIBucketReader, maxKeys=MAX_BUCKETS)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def slot_readv(storage_index=StorageIndex,
|
def slot_readv(storage_index=StorageIndex,
|
||||||
shares=ListOf(int), readv=ReadVector):
|
shares=ListOf(int), readv=ReadVector):
|
||||||
"""Read a vector from the numbered shares associated with the given
|
"""Read a vector from the numbered shares associated with the given
|
||||||
@ -343,14 +335,6 @@ class IStorageServer(Interface):
|
|||||||
:see: ``RIStorageServer.add_lease``
|
:see: ``RIStorageServer.add_lease``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def renew_lease(
|
|
||||||
storage_index,
|
|
||||||
renew_secret,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
:see: ``RIStorageServer.renew_lease``
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_buckets(
|
def get_buckets(
|
||||||
storage_index,
|
storage_index,
|
||||||
):
|
):
|
||||||
@ -375,6 +359,12 @@ class IStorageServer(Interface):
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:see: ``RIStorageServer.slot_testv_readv_and_writev``
|
:see: ``RIStorageServer.slot_testv_readv_and_writev``
|
||||||
|
|
||||||
|
While the interface mostly matches, test vectors are simplified.
|
||||||
|
Instead of a tuple ``(offset, read_size, operator, expected_data)`` in
|
||||||
|
the original, for this method you need only pass in
|
||||||
|
``(offset, read_size, expected_data)``, with the operator implicitly
|
||||||
|
being ``b"eq"``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def advise_corrupt_share(
|
def advise_corrupt_share(
|
||||||
|
@ -309,7 +309,7 @@ class SDMFSlotWriteProxy(object):
|
|||||||
salt)
|
salt)
|
||||||
else:
|
else:
|
||||||
checkstring = checkstring_or_seqnum
|
checkstring = checkstring_or_seqnum
|
||||||
self._testvs = [(0, len(checkstring), b"eq", checkstring)]
|
self._testvs = [(0, len(checkstring), checkstring)]
|
||||||
|
|
||||||
|
|
||||||
def get_checkstring(self):
|
def get_checkstring(self):
|
||||||
@ -318,7 +318,7 @@ class SDMFSlotWriteProxy(object):
|
|||||||
server.
|
server.
|
||||||
"""
|
"""
|
||||||
if self._testvs:
|
if self._testvs:
|
||||||
return self._testvs[0][3]
|
return self._testvs[0][2]
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|
||||||
@ -548,9 +548,9 @@ class SDMFSlotWriteProxy(object):
|
|||||||
if not self._testvs:
|
if not self._testvs:
|
||||||
# Our caller has not provided us with another checkstring
|
# Our caller has not provided us with another checkstring
|
||||||
# yet, so we assume that we are writing a new share, and set
|
# yet, so we assume that we are writing a new share, and set
|
||||||
# a test vector that will allow a new share to be written.
|
# a test vector that will only allow a new share to be written.
|
||||||
self._testvs = []
|
self._testvs = []
|
||||||
self._testvs.append(tuple([0, 1, b"eq", b""]))
|
self._testvs.append(tuple([0, 1, b""]))
|
||||||
|
|
||||||
tw_vectors = {}
|
tw_vectors = {}
|
||||||
tw_vectors[self.shnum] = (self._testvs, datavs, None)
|
tw_vectors[self.shnum] = (self._testvs, datavs, None)
|
||||||
@ -889,7 +889,7 @@ class MDMFSlotWriteProxy(object):
|
|||||||
self._testvs = []
|
self._testvs = []
|
||||||
else:
|
else:
|
||||||
self._testvs = []
|
self._testvs = []
|
||||||
self._testvs.append((0, len(checkstring), b"eq", checkstring))
|
self._testvs.append((0, len(checkstring), checkstring))
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -1161,8 +1161,10 @@ class MDMFSlotWriteProxy(object):
|
|||||||
"""I write the data vectors in datavs to the remote slot."""
|
"""I write the data vectors in datavs to the remote slot."""
|
||||||
tw_vectors = {}
|
tw_vectors = {}
|
||||||
if not self._testvs:
|
if not self._testvs:
|
||||||
|
# Make sure we will only successfully write if the share didn't
|
||||||
|
# previously exist.
|
||||||
self._testvs = []
|
self._testvs = []
|
||||||
self._testvs.append(tuple([0, 1, b"eq", b""]))
|
self._testvs.append(tuple([0, 1, b""]))
|
||||||
if not self._written:
|
if not self._written:
|
||||||
# Write a new checkstring to the share when we write it, so
|
# Write a new checkstring to the share when we write it, so
|
||||||
# that we have something to check later.
|
# that we have something to check later.
|
||||||
@ -1170,7 +1172,7 @@ class MDMFSlotWriteProxy(object):
|
|||||||
datavs.append((0, new_checkstring))
|
datavs.append((0, new_checkstring))
|
||||||
def _first_write():
|
def _first_write():
|
||||||
self._written = True
|
self._written = True
|
||||||
self._testvs = [(0, len(new_checkstring), b"eq", new_checkstring)]
|
self._testvs = [(0, len(new_checkstring), new_checkstring)]
|
||||||
on_success = _first_write
|
on_success = _first_write
|
||||||
tw_vectors[self.shnum] = (self._testvs, datavs, None)
|
tw_vectors[self.shnum] = (self._testvs, datavs, None)
|
||||||
d = self._storage_server.slot_testv_and_readv_and_writev(
|
d = self._storage_server.slot_testv_and_readv_and_writev(
|
||||||
|
@ -607,13 +607,14 @@ class ServermapUpdater(object):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def _do_read(self, server, storage_index, shnums, readv):
|
def _do_read(self, server, storage_index, shnums, readv):
|
||||||
|
"""
|
||||||
|
If self._add_lease is true, a lease is added, and the result only fires
|
||||||
|
once the least has also been added.
|
||||||
|
"""
|
||||||
ss = server.get_storage_server()
|
ss = server.get_storage_server()
|
||||||
if self._add_lease:
|
if self._add_lease:
|
||||||
# send an add-lease message in parallel. The results are handled
|
# send an add-lease message in parallel. The results are handled
|
||||||
# separately. This is sent before the slot_readv() so that we can
|
# separately.
|
||||||
# be sure the add_lease is retired by the time slot_readv comes
|
|
||||||
# back (this relies upon our knowledge that the server code for
|
|
||||||
# add_lease is synchronous).
|
|
||||||
renew_secret = self._node.get_renewal_secret(server)
|
renew_secret = self._node.get_renewal_secret(server)
|
||||||
cancel_secret = self._node.get_cancel_secret(server)
|
cancel_secret = self._node.get_cancel_secret(server)
|
||||||
d2 = ss.add_lease(
|
d2 = ss.add_lease(
|
||||||
@ -623,7 +624,16 @@ class ServermapUpdater(object):
|
|||||||
)
|
)
|
||||||
# we ignore success
|
# we ignore success
|
||||||
d2.addErrback(self._add_lease_failed, server, storage_index)
|
d2.addErrback(self._add_lease_failed, server, storage_index)
|
||||||
|
else:
|
||||||
|
d2 = defer.succeed(None)
|
||||||
d = ss.slot_readv(storage_index, shnums, readv)
|
d = ss.slot_readv(storage_index, shnums, readv)
|
||||||
|
|
||||||
|
def passthrough(result):
|
||||||
|
# Wait for d2, but fire with result of slot_readv() regardless of
|
||||||
|
# result of d2.
|
||||||
|
return d2.addBoth(lambda _: result)
|
||||||
|
|
||||||
|
d.addCallback(passthrough)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@ -793,7 +793,7 @@ def _tub_portlocation(config, get_local_addresses_sync, allocate_tcp_port):
|
|||||||
tubport = _convert_tub_port(cfg_tubport)
|
tubport = _convert_tub_port(cfg_tubport)
|
||||||
|
|
||||||
for port in tubport.split(","):
|
for port in tubport.split(","):
|
||||||
if port in ("0", "tcp:0"):
|
if port in ("0", "tcp:0", "tcp:port=0", "tcp:0:interface=127.0.0.1"):
|
||||||
raise PortAssignmentRequired()
|
raise PortAssignmentRequired()
|
||||||
|
|
||||||
if cfg_location is None:
|
if cfg_location is None:
|
||||||
|
@ -9,6 +9,7 @@ if PY2:
|
|||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
from six.moves import StringIO
|
from six.moves import StringIO
|
||||||
|
from past.builtins import unicode
|
||||||
import six
|
import six
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -115,23 +116,75 @@ for module in (create_node,):
|
|||||||
def parse_options(argv, config=None):
|
def parse_options(argv, config=None):
|
||||||
if not config:
|
if not config:
|
||||||
config = Options()
|
config = Options()
|
||||||
config.parseOptions(argv) # may raise usage.error
|
try:
|
||||||
|
config.parseOptions(argv)
|
||||||
|
except usage.error as e:
|
||||||
|
if six.PY2:
|
||||||
|
# On Python 2 the exception may hold non-ascii in a byte string.
|
||||||
|
# This makes it impossible to convert the exception to any kind of
|
||||||
|
# string using str() or unicode(). It could also hold non-ascii
|
||||||
|
# in a unicode string which still makes it difficult to convert it
|
||||||
|
# to a byte string later.
|
||||||
|
#
|
||||||
|
# So, reach inside and turn it into some entirely safe ascii byte
|
||||||
|
# strings that will survive being written to stdout without
|
||||||
|
# causing too much damage in the process.
|
||||||
|
#
|
||||||
|
# As a result, non-ascii will not be rendered correctly but
|
||||||
|
# instead as escape sequences. At least this can go away when
|
||||||
|
# we're done with Python 2 support.
|
||||||
|
raise usage.error(*(
|
||||||
|
arg.encode("ascii", errors="backslashreplace")
|
||||||
|
if isinstance(arg, unicode)
|
||||||
|
else arg.decode("utf-8").encode("ascii", errors="backslashreplace")
|
||||||
|
for arg
|
||||||
|
in e.args
|
||||||
|
))
|
||||||
|
raise
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def parse_or_exit(config, argv, stdout, stderr):
|
||||||
|
"""
|
||||||
|
Parse Tahoe-LAFS CLI arguments and return a configuration object if they
|
||||||
|
are valid.
|
||||||
|
|
||||||
def parse_or_exit_with_explanation(argv, stdout=sys.stdout):
|
If they are invalid, write an explanation to ``stdout`` and exit.
|
||||||
config = Options()
|
|
||||||
|
:param allmydata.scripts.runner.Options config: An instance of the
|
||||||
|
argument-parsing class to use.
|
||||||
|
|
||||||
|
:param [unicode] argv: The argument list to parse, including the name of the
|
||||||
|
program being run as ``argv[0]``.
|
||||||
|
|
||||||
|
:param stdout: The file-like object to use as stdout.
|
||||||
|
:param stderr: The file-like object to use as stderr.
|
||||||
|
|
||||||
|
:raise SystemExit: If there is an argument-parsing problem.
|
||||||
|
|
||||||
|
:return: ``config``, after using it to parse the argument list.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
parse_options(argv, config=config)
|
parse_options(argv[1:], config=config)
|
||||||
except usage.error as e:
|
except usage.error as e:
|
||||||
|
# `parse_options` may have the side-effect of initializing a
|
||||||
|
# "sub-option" of the given configuration, even if it ultimately
|
||||||
|
# raises an exception. For example, `tahoe run --invalid-option` will
|
||||||
|
# set `config.subOptions` to an instance of
|
||||||
|
# `allmydata.scripts.tahoe_run.RunOptions` and then raise a
|
||||||
|
# `usage.error` because `RunOptions` does not recognize
|
||||||
|
# `--invalid-option`. If `run` itself had a sub-options then the same
|
||||||
|
# thing could happen but with another layer of nesting. We can
|
||||||
|
# present the user with the most precise information about their usage
|
||||||
|
# error possible by finding the most "sub" of the sub-options and then
|
||||||
|
# showing that to the user along with the usage error.
|
||||||
c = config
|
c = config
|
||||||
while hasattr(c, 'subOptions'):
|
while hasattr(c, 'subOptions'):
|
||||||
c = c.subOptions
|
c = c.subOptions
|
||||||
print(str(c), file=stdout)
|
print(str(c), file=stdout)
|
||||||
# On Python 2 the string may turn into a unicode string, e.g. the error
|
exc_str = str(e)
|
||||||
# may be unicode, in which case it will print funny. Once we're on
|
exc_bytes = six.ensure_binary(exc_str, "utf-8")
|
||||||
# Python 3 we can just drop the ensure_str().
|
msg_bytes = b"%s: %s\n" % (six.ensure_binary(argv[0]), exc_bytes)
|
||||||
print(six.ensure_str("%s: %s\n" % (sys.argv[0], e)), file=stdout)
|
print(six.ensure_text(msg_bytes, "utf-8"), file=stdout)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@ -183,31 +236,65 @@ def _maybe_enable_eliot_logging(options, reactor):
|
|||||||
# Pass on the options so we can dispatch the subcommand.
|
# Pass on the options so we can dispatch the subcommand.
|
||||||
return options
|
return options
|
||||||
|
|
||||||
PYTHON_3_WARNING = ("Support for Python 3 is an incomplete work-in-progress."
|
|
||||||
" Use at your own risk.")
|
|
||||||
|
|
||||||
def run():
|
def run(configFactory=Options, argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr):
|
||||||
if six.PY3:
|
"""
|
||||||
print(PYTHON_3_WARNING, file=sys.stderr)
|
Run a Tahoe-LAFS node.
|
||||||
|
|
||||||
|
:param configFactory: A zero-argument callable which creates the config
|
||||||
|
object to use to parse the argument list.
|
||||||
|
|
||||||
|
:param [str] argv: The argument list to use to configure the run.
|
||||||
|
|
||||||
|
:param stdout: The file-like object to use for stdout.
|
||||||
|
:param stderr: The file-like object to use for stderr.
|
||||||
|
|
||||||
|
:raise SystemExit: Always raised after the run is complete.
|
||||||
|
"""
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
from allmydata.windows.fixups import initialize
|
from allmydata.windows.fixups import initialize
|
||||||
initialize()
|
initialize()
|
||||||
# doesn't return: calls sys.exit(rc)
|
# doesn't return: calls sys.exit(rc)
|
||||||
task.react(_run_with_reactor)
|
task.react(
|
||||||
|
lambda reactor: _run_with_reactor(
|
||||||
|
reactor,
|
||||||
|
configFactory(),
|
||||||
|
argv,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _setup_coverage(reactor):
|
def _setup_coverage(reactor, argv):
|
||||||
"""
|
"""
|
||||||
Arrange for coverage to be collected if the 'coverage' package is
|
If coverage measurement was requested, start collecting coverage
|
||||||
installed
|
measurements and arrange to record those measurements when the process is
|
||||||
|
done.
|
||||||
|
|
||||||
|
Coverage measurement is considered requested if ``"--coverage"`` is in
|
||||||
|
``argv`` (and it will be removed from ``argv`` if it is found). There
|
||||||
|
should be a ``.coveragerc`` file in the working directory if coverage
|
||||||
|
measurement is requested.
|
||||||
|
|
||||||
|
This is only necessary to support multi-process coverage measurement,
|
||||||
|
typically when the test suite is running, and with the pytest-based
|
||||||
|
*integration* test suite (at ``integration/`` in the root of the source
|
||||||
|
tree) foremost in mind. The idea is that if you are running Tahoe-LAFS in
|
||||||
|
a configuration where multiple processes are involved - for example, a
|
||||||
|
test process and a client node process, if you only measure coverage from
|
||||||
|
the test process then you will fail to observe most Tahoe-LAFS code that
|
||||||
|
is being run.
|
||||||
|
|
||||||
|
This function arranges to have any Tahoe-LAFS process (such as that
|
||||||
|
client node process) collect and report coverage measurements as well.
|
||||||
"""
|
"""
|
||||||
# can we put this _setup_coverage call after we hit
|
# can we put this _setup_coverage call after we hit
|
||||||
# argument-parsing?
|
# argument-parsing?
|
||||||
# ensure_str() only necessary on Python 2.
|
# ensure_str() only necessary on Python 2.
|
||||||
if six.ensure_str('--coverage') not in sys.argv:
|
if six.ensure_str('--coverage') not in sys.argv:
|
||||||
return
|
return
|
||||||
sys.argv.remove('--coverage')
|
argv.remove('--coverage')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import coverage
|
import coverage
|
||||||
@ -238,14 +325,37 @@ def _setup_coverage(reactor):
|
|||||||
reactor.addSystemEventTrigger('after', 'shutdown', write_coverage_data)
|
reactor.addSystemEventTrigger('after', 'shutdown', write_coverage_data)
|
||||||
|
|
||||||
|
|
||||||
def _run_with_reactor(reactor):
|
def _run_with_reactor(reactor, config, argv, stdout, stderr):
|
||||||
|
"""
|
||||||
|
Run a Tahoe-LAFS node using the given reactor.
|
||||||
|
|
||||||
_setup_coverage(reactor)
|
:param reactor: The reactor to use. This implementation largely ignores
|
||||||
|
this and lets the rest of the implementation pick its own reactor.
|
||||||
|
Oops.
|
||||||
|
|
||||||
argv = list(map(argv_to_unicode, sys.argv[1:]))
|
:param twisted.python.usage.Options config: The config object to use to
|
||||||
d = defer.maybeDeferred(parse_or_exit_with_explanation, argv)
|
parse the argument list.
|
||||||
|
|
||||||
|
:param [str] argv: The argument list to parse, *excluding* the name of the
|
||||||
|
program being run.
|
||||||
|
|
||||||
|
:param stdout: See ``run``.
|
||||||
|
:param stderr: See ``run``.
|
||||||
|
|
||||||
|
:return: A ``Deferred`` that fires when the run is complete.
|
||||||
|
"""
|
||||||
|
_setup_coverage(reactor, argv)
|
||||||
|
|
||||||
|
argv = list(map(argv_to_unicode, argv))
|
||||||
|
d = defer.maybeDeferred(
|
||||||
|
parse_or_exit,
|
||||||
|
config,
|
||||||
|
argv,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
)
|
||||||
d.addCallback(_maybe_enable_eliot_logging, reactor)
|
d.addCallback(_maybe_enable_eliot_logging, reactor)
|
||||||
d.addCallback(dispatch)
|
d.addCallback(dispatch, stdout=stdout, stderr=stderr)
|
||||||
def _show_exception(f):
|
def _show_exception(f):
|
||||||
# when task.react() notices a non-SystemExit exception, it does
|
# when task.react() notices a non-SystemExit exception, it does
|
||||||
# log.err() with the failure and then exits with rc=1. We want this
|
# log.err() with the failure and then exits with rc=1. We want this
|
||||||
@ -253,7 +363,7 @@ def _run_with_reactor(reactor):
|
|||||||
# weren't using react().
|
# weren't using react().
|
||||||
if f.check(SystemExit):
|
if f.check(SystemExit):
|
||||||
return f # dispatch function handled it
|
return f # dispatch function handled it
|
||||||
f.printTraceback(file=sys.stderr)
|
f.printTraceback(file=stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
d.addErrback(_show_exception)
|
d.addErrback(_show_exception)
|
||||||
return d
|
return d
|
||||||
|
@ -192,7 +192,7 @@ class DaemonizeTahoeNodePlugin(object):
|
|||||||
return DaemonizeTheRealService(self.nodetype, self.basedir, so)
|
return DaemonizeTheRealService(self.nodetype, self.basedir, so)
|
||||||
|
|
||||||
|
|
||||||
def run(config):
|
def run(config, runApp=twistd.runApp):
|
||||||
"""
|
"""
|
||||||
Runs a Tahoe-LAFS node in the foreground.
|
Runs a Tahoe-LAFS node in the foreground.
|
||||||
|
|
||||||
@ -212,10 +212,11 @@ def run(config):
|
|||||||
if not nodetype:
|
if not nodetype:
|
||||||
print("%s is not a recognizable node directory" % quoted_basedir, file=err)
|
print("%s is not a recognizable node directory" % quoted_basedir, file=err)
|
||||||
return 1
|
return 1
|
||||||
# Now prepare to turn into a twistd process. This os.chdir is the point
|
|
||||||
# of no return.
|
twistd_args = ["--nodaemon", "--rundir", basedir]
|
||||||
os.chdir(basedir)
|
if sys.platform != "win32":
|
||||||
twistd_args = ["--nodaemon"]
|
pidfile = get_pidfile(basedir)
|
||||||
|
twistd_args.extend(["--pidfile", pidfile])
|
||||||
twistd_args.extend(config.twistd_args)
|
twistd_args.extend(config.twistd_args)
|
||||||
twistd_args.append("DaemonizeTahoeNode") # point at our DaemonizeTahoeNodePlugin
|
twistd_args.append("DaemonizeTahoeNode") # point at our DaemonizeTahoeNodePlugin
|
||||||
|
|
||||||
@ -232,12 +233,11 @@ def run(config):
|
|||||||
twistd_config.loadedPlugins = {"DaemonizeTahoeNode": DaemonizeTahoeNodePlugin(nodetype, basedir)}
|
twistd_config.loadedPlugins = {"DaemonizeTahoeNode": DaemonizeTahoeNodePlugin(nodetype, basedir)}
|
||||||
|
|
||||||
# handle invalid PID file (twistd might not start otherwise)
|
# handle invalid PID file (twistd might not start otherwise)
|
||||||
pidfile = get_pidfile(basedir)
|
if sys.platform != "win32" and get_pid_from_pidfile(pidfile) == -1:
|
||||||
if get_pid_from_pidfile(pidfile) == -1:
|
|
||||||
print("found invalid PID file in %s - deleting it" % basedir, file=err)
|
print("found invalid PID file in %s - deleting it" % basedir, file=err)
|
||||||
os.remove(pidfile)
|
os.remove(pidfile)
|
||||||
|
|
||||||
# We always pass --nodaemon so twistd.runApp does not daemonize.
|
# We always pass --nodaemon so twistd.runApp does not daemonize.
|
||||||
print("running node in %s" % (quoted_basedir,), file=out)
|
print("running node in %s" % (quoted_basedir,), file=out)
|
||||||
twistd.runApp(twistd_config)
|
runApp(twistd_config)
|
||||||
return 0
|
return 0
|
||||||
|
@ -11,21 +11,48 @@ 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 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
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from urllib.parse import urlencode, quote as url_quote
|
from sys import stdout as _sys_stdout
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import BaseOptions
|
from .common import BaseOptions
|
||||||
from allmydata.scripts.common import get_default_nodedir
|
from allmydata.scripts.common import get_default_nodedir
|
||||||
from allmydata.scripts.common_http import do_http, BadResponse
|
from allmydata.scripts.common_http import BadResponse
|
||||||
from allmydata.util.abbreviate import abbreviate_space, abbreviate_time
|
from allmydata.util.abbreviate import abbreviate_space, abbreviate_time
|
||||||
from allmydata.util.encodingutil import argv_to_abspath
|
from allmydata.util.encodingutil import argv_to_abspath
|
||||||
|
|
||||||
|
_print = print
|
||||||
def _get_json_for_fragment(options, fragment, method='GET', post_args=None):
|
def print(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
returns the JSON for a particular URI-fragment (to which is
|
Builtin ``print``-alike that will even write unicode which cannot be
|
||||||
pre-pended the node's URL)
|
encoded using the specified output file's encoding.
|
||||||
|
|
||||||
|
This differs from the builtin print in that it will use the "replace"
|
||||||
|
encoding error handler and then write the result whereas builtin print
|
||||||
|
uses the "strict" encoding error handler.
|
||||||
|
"""
|
||||||
|
from past.builtins import unicode
|
||||||
|
out = kwargs.pop("file", None)
|
||||||
|
if out is None:
|
||||||
|
out = _sys_stdout
|
||||||
|
encoding = out.encoding or "ascii"
|
||||||
|
def ensafe(o):
|
||||||
|
if isinstance(o, unicode):
|
||||||
|
return o.encode(encoding, errors="replace").decode(encoding)
|
||||||
|
return o
|
||||||
|
return _print(
|
||||||
|
*(ensafe(a) for a in args),
|
||||||
|
file=out,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_request_parameters_for_fragment(options, fragment, method, post_args):
|
||||||
|
"""
|
||||||
|
Get parameters for ``do_http`` for requesting the given fragment.
|
||||||
|
|
||||||
|
:return dict: A dictionary suitable for use as keyword arguments to
|
||||||
|
``do_http``.
|
||||||
"""
|
"""
|
||||||
nodeurl = options['node-url']
|
nodeurl = options['node-url']
|
||||||
if nodeurl.endswith('/'):
|
if nodeurl.endswith('/'):
|
||||||
@ -40,7 +67,17 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None):
|
|||||||
body = ''
|
body = ''
|
||||||
if post_args is not None:
|
if post_args is not None:
|
||||||
raise ValueError("post_args= only valid for POST method")
|
raise ValueError("post_args= only valid for POST method")
|
||||||
resp = do_http(method, url, body=body.encode("utf-8"))
|
return dict(
|
||||||
|
method=method,
|
||||||
|
url=url,
|
||||||
|
body=body.encode("utf-8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_response_for_fragment(resp, nodeurl):
|
||||||
|
"""
|
||||||
|
Inspect an HTTP response and return the parsed payload, if possible.
|
||||||
|
"""
|
||||||
if isinstance(resp, BadResponse):
|
if isinstance(resp, BadResponse):
|
||||||
# specifically NOT using format_http_error() here because the
|
# specifically NOT using format_http_error() here because the
|
||||||
# URL is pretty sensitive (we're doing /uri/<key>).
|
# URL is pretty sensitive (we're doing /uri/<key>).
|
||||||
@ -55,12 +92,6 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None):
|
|||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
def _get_json_for_cap(options, cap):
|
|
||||||
return _get_json_for_fragment(
|
|
||||||
options,
|
|
||||||
'uri/%s?t=json' % url_quote(cap),
|
|
||||||
)
|
|
||||||
|
|
||||||
def pretty_progress(percent, size=10, output_ascii=False):
|
def pretty_progress(percent, size=10, output_ascii=False):
|
||||||
"""
|
"""
|
||||||
Displays a unicode or ascii based progress bar of a certain
|
Displays a unicode or ascii based progress bar of a certain
|
||||||
@ -251,7 +282,10 @@ def render_recent(verbose, stdout, status_data):
|
|||||||
print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=stdout)
|
print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=stdout)
|
||||||
|
|
||||||
|
|
||||||
def do_status(options):
|
def do_status(options, do_http=None):
|
||||||
|
if do_http is None:
|
||||||
|
from allmydata.scripts.common_http import do_http
|
||||||
|
|
||||||
nodedir = options["node-directory"]
|
nodedir = options["node-directory"]
|
||||||
with open(os.path.join(nodedir, u'private', u'api_auth_token'), 'r') as f:
|
with open(os.path.join(nodedir, u'private', u'api_auth_token'), 'r') as f:
|
||||||
token = f.read().strip()
|
token = f.read().strip()
|
||||||
@ -260,25 +294,30 @@ def do_status(options):
|
|||||||
|
|
||||||
# do *all* our data-retrievals first in case there's an error
|
# do *all* our data-retrievals first in case there's an error
|
||||||
try:
|
try:
|
||||||
status_data = _get_json_for_fragment(
|
status_data = _handle_response_for_fragment(
|
||||||
options,
|
do_http(**_get_request_parameters_for_fragment(
|
||||||
'status?t=json',
|
options,
|
||||||
method='POST',
|
'status?t=json',
|
||||||
post_args=dict(
|
method='POST',
|
||||||
t='json',
|
post_args=dict(
|
||||||
token=token,
|
t='json',
|
||||||
)
|
token=token,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
options['node-url'],
|
||||||
)
|
)
|
||||||
statistics_data = _get_json_for_fragment(
|
statistics_data = _handle_response_for_fragment(
|
||||||
options,
|
do_http(**_get_request_parameters_for_fragment(
|
||||||
'statistics?t=json',
|
options,
|
||||||
method='POST',
|
'statistics?t=json',
|
||||||
post_args=dict(
|
method='POST',
|
||||||
t='json',
|
post_args=dict(
|
||||||
token=token,
|
t='json',
|
||||||
)
|
token=token,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
options['node-url'],
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(u"failed to retrieve data: %s" % str(e), file=options.stderr)
|
print(u"failed to retrieve data: %s" % str(e), file=options.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
@ -13,8 +13,9 @@ if PY2:
|
|||||||
import os.path
|
import os.path
|
||||||
from allmydata.util import base32
|
from allmydata.util import base32
|
||||||
|
|
||||||
class DataTooLargeError(Exception):
|
# Backwards compatibility.
|
||||||
pass
|
from allmydata.interfaces import DataTooLargeError # noqa: F401
|
||||||
|
|
||||||
class UnknownMutableContainerVersionError(Exception):
|
class UnknownMutableContainerVersionError(Exception):
|
||||||
pass
|
pass
|
||||||
class UnknownImmutableContainerVersionError(Exception):
|
class UnknownImmutableContainerVersionError(Exception):
|
||||||
|
@ -13,16 +13,20 @@ if PY2:
|
|||||||
|
|
||||||
import os, stat, struct, time
|
import os, stat, struct, time
|
||||||
|
|
||||||
|
from collections_extended import RangeMap
|
||||||
|
|
||||||
from foolscap.api import Referenceable
|
from foolscap.api import Referenceable
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
from allmydata.interfaces import RIBucketWriter, RIBucketReader
|
from allmydata.interfaces import (
|
||||||
|
RIBucketWriter, RIBucketReader, ConflictingWriteError,
|
||||||
|
DataTooLargeError,
|
||||||
|
)
|
||||||
from allmydata.util import base32, fileutil, log
|
from allmydata.util import base32, fileutil, log
|
||||||
from allmydata.util.assertutil import precondition
|
from allmydata.util.assertutil import precondition
|
||||||
from allmydata.util.hashutil import timing_safe_compare
|
from allmydata.util.hashutil import timing_safe_compare
|
||||||
from allmydata.storage.lease import LeaseInfo
|
from allmydata.storage.lease import LeaseInfo
|
||||||
from allmydata.storage.common import UnknownImmutableContainerVersionError, \
|
from allmydata.storage.common import UnknownImmutableContainerVersionError
|
||||||
DataTooLargeError
|
|
||||||
|
|
||||||
# each share file (in storage/shares/$SI/$SHNUM) contains lease information
|
# each share file (in storage/shares/$SI/$SHNUM) contains lease information
|
||||||
# and share data. The share data is accessed by RIBucketWriter.write and
|
# and share data. The share data is accessed by RIBucketWriter.write and
|
||||||
@ -204,19 +208,18 @@ class ShareFile(object):
|
|||||||
@implementer(RIBucketWriter)
|
@implementer(RIBucketWriter)
|
||||||
class BucketWriter(Referenceable): # type: ignore # warner/foolscap#78
|
class BucketWriter(Referenceable): # type: ignore # warner/foolscap#78
|
||||||
|
|
||||||
def __init__(self, ss, incominghome, finalhome, max_size, lease_info, canary):
|
def __init__(self, ss, incominghome, finalhome, max_size, lease_info):
|
||||||
self.ss = ss
|
self.ss = ss
|
||||||
self.incominghome = incominghome
|
self.incominghome = incominghome
|
||||||
self.finalhome = finalhome
|
self.finalhome = finalhome
|
||||||
self._max_size = max_size # don't allow the client to write more than this
|
self._max_size = max_size # don't allow the client to write more than this
|
||||||
self._canary = canary
|
|
||||||
self._disconnect_marker = canary.notifyOnDisconnect(self._disconnected)
|
|
||||||
self.closed = False
|
self.closed = False
|
||||||
self.throw_out_all_data = False
|
self.throw_out_all_data = False
|
||||||
self._sharefile = ShareFile(incominghome, create=True, max_size=max_size)
|
self._sharefile = ShareFile(incominghome, create=True, max_size=max_size)
|
||||||
# also, add our lease to the file now, so that other ones can be
|
# also, add our lease to the file now, so that other ones can be
|
||||||
# added by simultaneous uploaders
|
# added by simultaneous uploaders
|
||||||
self._sharefile.add_lease(lease_info)
|
self._sharefile.add_lease(lease_info)
|
||||||
|
self._already_written = RangeMap()
|
||||||
|
|
||||||
def allocated_size(self):
|
def allocated_size(self):
|
||||||
return self._max_size
|
return self._max_size
|
||||||
@ -226,7 +229,20 @@ class BucketWriter(Referenceable): # type: ignore # warner/foolscap#78
|
|||||||
precondition(not self.closed)
|
precondition(not self.closed)
|
||||||
if self.throw_out_all_data:
|
if self.throw_out_all_data:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Make sure we're not conflicting with existing data:
|
||||||
|
end = offset + len(data)
|
||||||
|
for (chunk_start, chunk_stop, _) in self._already_written.ranges(offset, end):
|
||||||
|
chunk_len = chunk_stop - chunk_start
|
||||||
|
actual_chunk = self._sharefile.read_share_data(chunk_start, chunk_len)
|
||||||
|
writing_chunk = data[chunk_start - offset:chunk_stop - offset]
|
||||||
|
if actual_chunk != writing_chunk:
|
||||||
|
raise ConflictingWriteError(
|
||||||
|
"Chunk {}-{} doesn't match already written data.".format(chunk_start, chunk_stop)
|
||||||
|
)
|
||||||
self._sharefile.write_share_data(offset, data)
|
self._sharefile.write_share_data(offset, data)
|
||||||
|
|
||||||
|
self._already_written.set(True, offset, end)
|
||||||
self.ss.add_latency("write", time.time() - start)
|
self.ss.add_latency("write", time.time() - start)
|
||||||
self.ss.count("write")
|
self.ss.count("write")
|
||||||
|
|
||||||
@ -262,22 +278,19 @@ class BucketWriter(Referenceable): # type: ignore # warner/foolscap#78
|
|||||||
pass
|
pass
|
||||||
self._sharefile = None
|
self._sharefile = None
|
||||||
self.closed = True
|
self.closed = True
|
||||||
self._canary.dontNotifyOnDisconnect(self._disconnect_marker)
|
|
||||||
|
|
||||||
filelen = os.stat(self.finalhome)[stat.ST_SIZE]
|
filelen = os.stat(self.finalhome)[stat.ST_SIZE]
|
||||||
self.ss.bucket_writer_closed(self, filelen)
|
self.ss.bucket_writer_closed(self, filelen)
|
||||||
self.ss.add_latency("close", time.time() - start)
|
self.ss.add_latency("close", time.time() - start)
|
||||||
self.ss.count("close")
|
self.ss.count("close")
|
||||||
|
|
||||||
def _disconnected(self):
|
def disconnected(self):
|
||||||
if not self.closed:
|
if not self.closed:
|
||||||
self._abort()
|
self._abort()
|
||||||
|
|
||||||
def remote_abort(self):
|
def remote_abort(self):
|
||||||
log.msg("storage: aborting sharefile %s" % self.incominghome,
|
log.msg("storage: aborting sharefile %s" % self.incominghome,
|
||||||
facility="tahoe.storage", level=log.UNUSUAL)
|
facility="tahoe.storage", level=log.UNUSUAL)
|
||||||
if not self.closed:
|
|
||||||
self._canary.dontNotifyOnDisconnect(self._disconnect_marker)
|
|
||||||
self._abort()
|
self._abort()
|
||||||
self.ss.count("abort")
|
self.ss.count("abort")
|
||||||
|
|
||||||
|
@ -434,20 +434,9 @@ class MutableShareFile(object):
|
|||||||
# self._change_container_size() here.
|
# self._change_container_size() here.
|
||||||
|
|
||||||
def testv_compare(a, op, b):
|
def testv_compare(a, op, b):
|
||||||
assert op in (b"lt", b"le", b"eq", b"ne", b"ge", b"gt")
|
assert op == b"eq"
|
||||||
if op == b"lt":
|
return a == b
|
||||||
return a < b
|
|
||||||
if op == b"le":
|
|
||||||
return a <= b
|
|
||||||
if op == b"eq":
|
|
||||||
return a == b
|
|
||||||
if op == b"ne":
|
|
||||||
return a != b
|
|
||||||
if op == b"ge":
|
|
||||||
return a >= b
|
|
||||||
if op == b"gt":
|
|
||||||
return a > b
|
|
||||||
# never reached
|
|
||||||
|
|
||||||
class EmptyShare(object):
|
class EmptyShare(object):
|
||||||
|
|
||||||
|
@ -11,13 +11,14 @@ if PY2:
|
|||||||
# Omit open() to get native behavior where open("w") always accepts native
|
# Omit open() to get native behavior where open("w") always accepts native
|
||||||
# strings. Omit bytes so we don't leak future's custom bytes.
|
# 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
|
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
|
||||||
|
|
||||||
import os, re, struct, time
|
import os, re, struct, time
|
||||||
import weakref
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from foolscap.api import Referenceable
|
from foolscap.api import Referenceable
|
||||||
|
from foolscap.ipb import IRemoteReference
|
||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
@ -49,6 +50,10 @@ from allmydata.storage.expirer import LeaseCheckingCrawler
|
|||||||
NUM_RE=re.compile("^[0-9]+$")
|
NUM_RE=re.compile("^[0-9]+$")
|
||||||
|
|
||||||
|
|
||||||
|
# Number of seconds to add to expiration time on lease renewal.
|
||||||
|
# For now it's not actually configurable, but maybe someday.
|
||||||
|
DEFAULT_RENEWAL_TIME = 31 * 24 * 60 * 60
|
||||||
|
|
||||||
|
|
||||||
@implementer(RIStorageServer, IStatsProducer)
|
@implementer(RIStorageServer, IStatsProducer)
|
||||||
class StorageServer(service.MultiService, Referenceable):
|
class StorageServer(service.MultiService, Referenceable):
|
||||||
@ -62,7 +67,8 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
expiration_mode="age",
|
expiration_mode="age",
|
||||||
expiration_override_lease_duration=None,
|
expiration_override_lease_duration=None,
|
||||||
expiration_cutoff_date=None,
|
expiration_cutoff_date=None,
|
||||||
expiration_sharetypes=("mutable", "immutable")):
|
expiration_sharetypes=("mutable", "immutable"),
|
||||||
|
get_current_time=time.time):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
assert isinstance(nodeid, bytes)
|
assert isinstance(nodeid, bytes)
|
||||||
assert len(nodeid) == 20
|
assert len(nodeid) == 20
|
||||||
@ -84,7 +90,6 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
self.incomingdir = os.path.join(sharedir, 'incoming')
|
self.incomingdir = os.path.join(sharedir, 'incoming')
|
||||||
self._clean_incomplete()
|
self._clean_incomplete()
|
||||||
fileutil.make_dirs(self.incomingdir)
|
fileutil.make_dirs(self.incomingdir)
|
||||||
self._active_writers = weakref.WeakKeyDictionary()
|
|
||||||
log.msg("StorageServer created", facility="tahoe.storage")
|
log.msg("StorageServer created", facility="tahoe.storage")
|
||||||
|
|
||||||
if reserved_space:
|
if reserved_space:
|
||||||
@ -114,6 +119,18 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
expiration_cutoff_date,
|
expiration_cutoff_date,
|
||||||
expiration_sharetypes)
|
expiration_sharetypes)
|
||||||
self.lease_checker.setServiceParent(self)
|
self.lease_checker.setServiceParent(self)
|
||||||
|
self._get_current_time = get_current_time
|
||||||
|
|
||||||
|
# Currently being-written Bucketwriters. For Foolscap, lifetime is tied
|
||||||
|
# to connection: when disconnection happens, the BucketWriters are
|
||||||
|
# removed. For HTTP, this makes no sense, so there will be
|
||||||
|
# timeout-based cleanup; see
|
||||||
|
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3807.
|
||||||
|
|
||||||
|
# Map in-progress filesystem path -> BucketWriter:
|
||||||
|
self._bucket_writers = {} # type: Dict[str,BucketWriter]
|
||||||
|
# Canaries and disconnect markers for BucketWriters created via Foolscap:
|
||||||
|
self._bucket_writer_disconnect_markers = {} # type: Dict[BucketWriter,(IRemoteReference, object)]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<StorageServer %s>" % (idlib.shortnodeid_b2a(self.my_nodeid),)
|
return "<StorageServer %s>" % (idlib.shortnodeid_b2a(self.my_nodeid),)
|
||||||
@ -232,7 +249,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
|
|
||||||
def allocated_size(self):
|
def allocated_size(self):
|
||||||
space = 0
|
space = 0
|
||||||
for bw in self._active_writers:
|
for bw in self._bucket_writers.values():
|
||||||
space += bw.allocated_size()
|
space += bw.allocated_size()
|
||||||
return space
|
return space
|
||||||
|
|
||||||
@ -257,14 +274,17 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
}
|
}
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def remote_allocate_buckets(self, storage_index,
|
def _allocate_buckets(self, storage_index,
|
||||||
renew_secret, cancel_secret,
|
renew_secret, cancel_secret,
|
||||||
sharenums, allocated_size,
|
sharenums, allocated_size,
|
||||||
canary, owner_num=0):
|
owner_num=0):
|
||||||
|
"""
|
||||||
|
Generic bucket allocation API.
|
||||||
|
"""
|
||||||
# owner_num is not for clients to set, but rather it should be
|
# owner_num is not for clients to set, but rather it should be
|
||||||
# curried into the PersonalStorageServer instance that is dedicated
|
# curried into the PersonalStorageServer instance that is dedicated
|
||||||
# to a particular owner.
|
# to a particular owner.
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
self.count("allocate")
|
self.count("allocate")
|
||||||
alreadygot = set()
|
alreadygot = set()
|
||||||
bucketwriters = {} # k: shnum, v: BucketWriter
|
bucketwriters = {} # k: shnum, v: BucketWriter
|
||||||
@ -277,7 +297,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
# goes into the share files themselves. It could also be put into a
|
# goes into the share files themselves. It could also be put into a
|
||||||
# separate database. Note that the lease should not be added until
|
# separate database. Note that the lease should not be added until
|
||||||
# the BucketWriter has been closed.
|
# the BucketWriter has been closed.
|
||||||
expire_time = time.time() + 31*24*60*60
|
expire_time = self._get_current_time() + DEFAULT_RENEWAL_TIME
|
||||||
lease_info = LeaseInfo(owner_num,
|
lease_info = LeaseInfo(owner_num,
|
||||||
renew_secret, cancel_secret,
|
renew_secret, cancel_secret,
|
||||||
expire_time, self.my_nodeid)
|
expire_time, self.my_nodeid)
|
||||||
@ -309,7 +329,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
# great! we already have it. easy.
|
# great! we already have it. easy.
|
||||||
pass
|
pass
|
||||||
elif os.path.exists(incominghome):
|
elif os.path.exists(incominghome):
|
||||||
# Note that we don't create BucketWriters for shnums that
|
# For Foolscap we don't create BucketWriters for shnums that
|
||||||
# have a partial share (in incoming/), so if a second upload
|
# have a partial share (in incoming/), so if a second upload
|
||||||
# occurs while the first is still in progress, the second
|
# occurs while the first is still in progress, the second
|
||||||
# uploader will use different storage servers.
|
# uploader will use different storage servers.
|
||||||
@ -317,11 +337,11 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
elif (not limited) or (remaining_space >= max_space_per_bucket):
|
elif (not limited) or (remaining_space >= max_space_per_bucket):
|
||||||
# ok! we need to create the new share file.
|
# ok! we need to create the new share file.
|
||||||
bw = BucketWriter(self, incominghome, finalhome,
|
bw = BucketWriter(self, incominghome, finalhome,
|
||||||
max_space_per_bucket, lease_info, canary)
|
max_space_per_bucket, lease_info)
|
||||||
if self.no_storage:
|
if self.no_storage:
|
||||||
bw.throw_out_all_data = True
|
bw.throw_out_all_data = True
|
||||||
bucketwriters[shnum] = bw
|
bucketwriters[shnum] = bw
|
||||||
self._active_writers[bw] = 1
|
self._bucket_writers[incominghome] = bw
|
||||||
if limited:
|
if limited:
|
||||||
remaining_space -= max_space_per_bucket
|
remaining_space -= max_space_per_bucket
|
||||||
else:
|
else:
|
||||||
@ -331,7 +351,22 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
if bucketwriters:
|
if bucketwriters:
|
||||||
fileutil.make_dirs(os.path.join(self.sharedir, si_dir))
|
fileutil.make_dirs(os.path.join(self.sharedir, si_dir))
|
||||||
|
|
||||||
self.add_latency("allocate", time.time() - start)
|
self.add_latency("allocate", self._get_current_time() - start)
|
||||||
|
return alreadygot, bucketwriters
|
||||||
|
|
||||||
|
def remote_allocate_buckets(self, storage_index,
|
||||||
|
renew_secret, cancel_secret,
|
||||||
|
sharenums, allocated_size,
|
||||||
|
canary, owner_num=0):
|
||||||
|
"""Foolscap-specific ``allocate_buckets()`` API."""
|
||||||
|
alreadygot, bucketwriters = self._allocate_buckets(
|
||||||
|
storage_index, renew_secret, cancel_secret, sharenums, allocated_size,
|
||||||
|
owner_num=owner_num,
|
||||||
|
)
|
||||||
|
# Abort BucketWriters if disconnection happens.
|
||||||
|
for bw in bucketwriters.values():
|
||||||
|
disconnect_marker = canary.notifyOnDisconnect(bw.disconnected)
|
||||||
|
self._bucket_writer_disconnect_markers[bw] = (canary, disconnect_marker)
|
||||||
return alreadygot, bucketwriters
|
return alreadygot, bucketwriters
|
||||||
|
|
||||||
def _iter_share_files(self, storage_index):
|
def _iter_share_files(self, storage_index):
|
||||||
@ -351,33 +386,36 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
|
|
||||||
def remote_add_lease(self, storage_index, renew_secret, cancel_secret,
|
def remote_add_lease(self, storage_index, renew_secret, cancel_secret,
|
||||||
owner_num=1):
|
owner_num=1):
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
self.count("add-lease")
|
self.count("add-lease")
|
||||||
new_expire_time = time.time() + 31*24*60*60
|
new_expire_time = self._get_current_time() + DEFAULT_RENEWAL_TIME
|
||||||
lease_info = LeaseInfo(owner_num,
|
lease_info = LeaseInfo(owner_num,
|
||||||
renew_secret, cancel_secret,
|
renew_secret, cancel_secret,
|
||||||
new_expire_time, self.my_nodeid)
|
new_expire_time, self.my_nodeid)
|
||||||
for sf in self._iter_share_files(storage_index):
|
for sf in self._iter_share_files(storage_index):
|
||||||
sf.add_or_renew_lease(lease_info)
|
sf.add_or_renew_lease(lease_info)
|
||||||
self.add_latency("add-lease", time.time() - start)
|
self.add_latency("add-lease", self._get_current_time() - start)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def remote_renew_lease(self, storage_index, renew_secret):
|
def remote_renew_lease(self, storage_index, renew_secret):
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
self.count("renew")
|
self.count("renew")
|
||||||
new_expire_time = time.time() + 31*24*60*60
|
new_expire_time = self._get_current_time() + DEFAULT_RENEWAL_TIME
|
||||||
found_buckets = False
|
found_buckets = False
|
||||||
for sf in self._iter_share_files(storage_index):
|
for sf in self._iter_share_files(storage_index):
|
||||||
found_buckets = True
|
found_buckets = True
|
||||||
sf.renew_lease(renew_secret, new_expire_time)
|
sf.renew_lease(renew_secret, new_expire_time)
|
||||||
self.add_latency("renew", time.time() - start)
|
self.add_latency("renew", self._get_current_time() - start)
|
||||||
if not found_buckets:
|
if not found_buckets:
|
||||||
raise IndexError("no such lease to renew")
|
raise IndexError("no such lease to renew")
|
||||||
|
|
||||||
def bucket_writer_closed(self, bw, consumed_size):
|
def bucket_writer_closed(self, bw, consumed_size):
|
||||||
if self.stats_provider:
|
if self.stats_provider:
|
||||||
self.stats_provider.count('storage_server.bytes_added', consumed_size)
|
self.stats_provider.count('storage_server.bytes_added', consumed_size)
|
||||||
del self._active_writers[bw]
|
del self._bucket_writers[bw.incominghome]
|
||||||
|
if bw in self._bucket_writer_disconnect_markers:
|
||||||
|
canary, disconnect_marker = self._bucket_writer_disconnect_markers.pop(bw)
|
||||||
|
canary.dontNotifyOnDisconnect(disconnect_marker)
|
||||||
|
|
||||||
def _get_bucket_shares(self, storage_index):
|
def _get_bucket_shares(self, storage_index):
|
||||||
"""Return a list of (shnum, pathname) tuples for files that hold
|
"""Return a list of (shnum, pathname) tuples for files that hold
|
||||||
@ -394,7 +432,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def remote_get_buckets(self, storage_index):
|
def remote_get_buckets(self, storage_index):
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
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)
|
||||||
@ -402,7 +440,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
for shnum, filename in self._get_bucket_shares(storage_index):
|
for shnum, filename in self._get_bucket_shares(storage_index):
|
||||||
bucketreaders[shnum] = BucketReader(self, filename,
|
bucketreaders[shnum] = BucketReader(self, filename,
|
||||||
storage_index, shnum)
|
storage_index, shnum)
|
||||||
self.add_latency("get", time.time() - start)
|
self.add_latency("get", self._get_current_time() - start)
|
||||||
return bucketreaders
|
return bucketreaders
|
||||||
|
|
||||||
def get_leases(self, storage_index):
|
def get_leases(self, storage_index):
|
||||||
@ -563,7 +601,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
:return LeaseInfo: Information for a new lease for a share.
|
:return LeaseInfo: Information for a new lease for a share.
|
||||||
"""
|
"""
|
||||||
ownerid = 1 # TODO
|
ownerid = 1 # TODO
|
||||||
expire_time = time.time() + 31*24*60*60 # one month
|
expire_time = self._get_current_time() + DEFAULT_RENEWAL_TIME
|
||||||
lease_info = LeaseInfo(ownerid,
|
lease_info = LeaseInfo(ownerid,
|
||||||
renew_secret, cancel_secret,
|
renew_secret, cancel_secret,
|
||||||
expire_time, self.my_nodeid)
|
expire_time, self.my_nodeid)
|
||||||
@ -599,7 +637,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
See ``allmydata.interfaces.RIStorageServer`` for details about other
|
See ``allmydata.interfaces.RIStorageServer`` for details about other
|
||||||
parameters and return value.
|
parameters and return value.
|
||||||
"""
|
"""
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
self.count("writev")
|
self.count("writev")
|
||||||
si_s = si_b2a(storage_index)
|
si_s = si_b2a(storage_index)
|
||||||
log.msg("storage: slot_writev %r" % si_s)
|
log.msg("storage: slot_writev %r" % si_s)
|
||||||
@ -640,7 +678,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
self._add_or_renew_leases(remaining_shares, lease_info)
|
self._add_or_renew_leases(remaining_shares, lease_info)
|
||||||
|
|
||||||
# all done
|
# all done
|
||||||
self.add_latency("writev", time.time() - start)
|
self.add_latency("writev", self._get_current_time() - start)
|
||||||
return (testv_is_good, read_data)
|
return (testv_is_good, read_data)
|
||||||
|
|
||||||
def remote_slot_testv_and_readv_and_writev(self, storage_index,
|
def remote_slot_testv_and_readv_and_writev(self, storage_index,
|
||||||
@ -666,7 +704,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
return share
|
return share
|
||||||
|
|
||||||
def remote_slot_readv(self, storage_index, shares, readv):
|
def remote_slot_readv(self, storage_index, shares, readv):
|
||||||
start = time.time()
|
start = self._get_current_time()
|
||||||
self.count("readv")
|
self.count("readv")
|
||||||
si_s = si_b2a(storage_index)
|
si_s = si_b2a(storage_index)
|
||||||
lp = log.msg("storage: slot_readv %r %r" % (si_s, shares),
|
lp = log.msg("storage: slot_readv %r %r" % (si_s, shares),
|
||||||
@ -675,7 +713,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
# shares exist if there is a file for them
|
# shares exist if there is a file for them
|
||||||
bucketdir = os.path.join(self.sharedir, si_dir)
|
bucketdir = os.path.join(self.sharedir, si_dir)
|
||||||
if not os.path.isdir(bucketdir):
|
if not os.path.isdir(bucketdir):
|
||||||
self.add_latency("readv", time.time() - start)
|
self.add_latency("readv", self._get_current_time() - start)
|
||||||
return {}
|
return {}
|
||||||
datavs = {}
|
datavs = {}
|
||||||
for sharenum_s in os.listdir(bucketdir):
|
for sharenum_s in os.listdir(bucketdir):
|
||||||
@ -689,7 +727,7 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
datavs[sharenum] = msf.readv(readv)
|
datavs[sharenum] = msf.readv(readv)
|
||||||
log.msg("returning shares %s" % (list(datavs.keys()),),
|
log.msg("returning shares %s" % (list(datavs.keys()),),
|
||||||
facility="tahoe.storage", level=log.NOISY, parent=lp)
|
facility="tahoe.storage", level=log.NOISY, parent=lp)
|
||||||
self.add_latency("readv", time.time() - start)
|
self.add_latency("readv", self._get_current_time() - start)
|
||||||
return datavs
|
return datavs
|
||||||
|
|
||||||
def remote_advise_corrupt_share(self, share_type, storage_index, shnum,
|
def remote_advise_corrupt_share(self, share_type, storage_index, shnum,
|
||||||
@ -702,8 +740,10 @@ class StorageServer(service.MultiService, Referenceable):
|
|||||||
now = time_format.iso_utc(sep="T")
|
now = time_format.iso_utc(sep="T")
|
||||||
si_s = si_b2a(storage_index)
|
si_s = si_b2a(storage_index)
|
||||||
# windows can't handle colons in the filename
|
# windows can't handle colons in the filename
|
||||||
fn = os.path.join(self.corruption_advisory_dir,
|
fn = os.path.join(
|
||||||
"%s--%s-%d" % (now, str(si_s, "utf-8"), shnum)).replace(":","")
|
self.corruption_advisory_dir,
|
||||||
|
("%s--%s-%d" % (now, str(si_s, "utf-8"), shnum)).replace(":","")
|
||||||
|
)
|
||||||
with open(fn, "w") as f:
|
with open(fn, "w") as f:
|
||||||
f.write("report: Share Corruption\n")
|
f.write("report: Share Corruption\n")
|
||||||
f.write("type: %s\n" % bytes_to_native_str(share_type))
|
f.write("type: %s\n" % bytes_to_native_str(share_type))
|
||||||
|
@ -965,17 +965,6 @@ class _StorageServer(object):
|
|||||||
cancel_secret,
|
cancel_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
def renew_lease(
|
|
||||||
self,
|
|
||||||
storage_index,
|
|
||||||
renew_secret,
|
|
||||||
):
|
|
||||||
return self._rref.callRemote(
|
|
||||||
"renew_lease",
|
|
||||||
storage_index,
|
|
||||||
renew_secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_buckets(
|
def get_buckets(
|
||||||
self,
|
self,
|
||||||
storage_index,
|
storage_index,
|
||||||
@ -1005,11 +994,19 @@ class _StorageServer(object):
|
|||||||
tw_vectors,
|
tw_vectors,
|
||||||
r_vector,
|
r_vector,
|
||||||
):
|
):
|
||||||
|
# Match the wire protocol, which requires 4-tuples for test vectors.
|
||||||
|
wire_format_tw_vectors = {
|
||||||
|
key: (
|
||||||
|
[(start, length, b"eq", data) for (start, length, data) in value[0]],
|
||||||
|
value[1],
|
||||||
|
value[2],
|
||||||
|
) for (key, value) in tw_vectors.items()
|
||||||
|
}
|
||||||
return self._rref.callRemote(
|
return self._rref.callRemote(
|
||||||
"slot_testv_and_readv_and_writev",
|
"slot_testv_and_readv_and_writev",
|
||||||
storage_index,
|
storage_index,
|
||||||
secrets,
|
secrets,
|
||||||
tw_vectors,
|
wire_format_tw_vectors,
|
||||||
r_vector,
|
r_vector,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1020,10 +1017,10 @@ class _StorageServer(object):
|
|||||||
shnum,
|
shnum,
|
||||||
reason,
|
reason,
|
||||||
):
|
):
|
||||||
return self._rref.callRemoteOnly(
|
return self._rref.callRemote(
|
||||||
"advise_corrupt_share",
|
"advise_corrupt_share",
|
||||||
share_type,
|
share_type,
|
||||||
storage_index,
|
storage_index,
|
||||||
shnum,
|
shnum,
|
||||||
reason,
|
reason,
|
||||||
)
|
).addErrback(log.err, "Error from remote call to advise_corrupt_share")
|
||||||
|
@ -11,23 +11,22 @@ 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 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.moves import cStringIO as StringIO
|
from six.moves import cStringIO as StringIO
|
||||||
from six import ensure_text, ensure_str
|
import re
|
||||||
|
from six import ensure_text
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
from mock import patch, Mock
|
|
||||||
from urllib.parse import quote as url_quote
|
from urllib.parse import quote as url_quote
|
||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.python.monkey import MonkeyPatcher
|
from twisted.internet.testing import (
|
||||||
from twisted.internet import task
|
MemoryReactor,
|
||||||
from twisted.python.filepath import FilePath
|
)
|
||||||
|
from twisted.internet.test.modulehelpers import (
|
||||||
|
AlternateReactor,
|
||||||
|
)
|
||||||
import allmydata
|
import allmydata
|
||||||
from allmydata.crypto import ed25519
|
from allmydata.crypto import ed25519
|
||||||
from allmydata.util import fileutil, hashutil, base32
|
from allmydata.util import fileutil, hashutil, base32
|
||||||
from allmydata.util.namespace import Namespace
|
|
||||||
from allmydata import uri
|
from allmydata import uri
|
||||||
from allmydata.immutable import upload
|
from allmydata.immutable import upload
|
||||||
from allmydata.dirnode import normalize
|
from allmydata.dirnode import normalize
|
||||||
@ -524,42 +523,34 @@ class CLI(CLITestMixin, unittest.TestCase):
|
|||||||
self.failUnlessIn(normalize(file), filenames)
|
self.failUnlessIn(normalize(file), filenames)
|
||||||
|
|
||||||
def test_exception_catcher(self):
|
def test_exception_catcher(self):
|
||||||
|
"""
|
||||||
|
An exception that is otherwise unhandled during argument dispatch is
|
||||||
|
written to stderr and causes the process to exit with code 1.
|
||||||
|
"""
|
||||||
self.basedir = "cli/exception_catcher"
|
self.basedir = "cli/exception_catcher"
|
||||||
|
|
||||||
stderr = StringIO()
|
|
||||||
exc = Exception("canary")
|
exc = Exception("canary")
|
||||||
ns = Namespace()
|
class BrokenOptions(object):
|
||||||
|
def parseOptions(self, argv):
|
||||||
|
raise exc
|
||||||
|
|
||||||
ns.parse_called = False
|
stderr = StringIO()
|
||||||
def call_parse_or_exit(args):
|
|
||||||
ns.parse_called = True
|
|
||||||
raise exc
|
|
||||||
|
|
||||||
ns.sys_exit_called = False
|
reactor = MemoryReactor()
|
||||||
def call_sys_exit(exitcode):
|
|
||||||
ns.sys_exit_called = True
|
|
||||||
self.failUnlessEqual(exitcode, 1)
|
|
||||||
|
|
||||||
def fake_react(f):
|
with AlternateReactor(reactor):
|
||||||
reactor = Mock()
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
d = f(reactor)
|
runner.run(
|
||||||
# normally this Deferred would be errbacked with SystemExit, but
|
configFactory=BrokenOptions,
|
||||||
# since we mocked out sys.exit, it will be fired with None. So
|
argv=["tahoe"],
|
||||||
# it's safe to drop it on the floor.
|
stderr=stderr,
|
||||||
del d
|
)
|
||||||
|
|
||||||
patcher = MonkeyPatcher((runner, 'parse_or_exit_with_explanation',
|
self.assertTrue(reactor.hasRun)
|
||||||
call_parse_or_exit),
|
self.assertFalse(reactor.running)
|
||||||
(sys, 'argv', ["tahoe"]),
|
|
||||||
(sys, 'exit', call_sys_exit),
|
|
||||||
(sys, 'stderr', stderr),
|
|
||||||
(task, 'react', fake_react),
|
|
||||||
)
|
|
||||||
patcher.runWithPatches(runner.run)
|
|
||||||
|
|
||||||
self.failUnless(ns.parse_called)
|
|
||||||
self.failUnless(ns.sys_exit_called)
|
|
||||||
self.failUnlessIn(str(exc), stderr.getvalue())
|
self.failUnlessIn(str(exc), stderr.getvalue())
|
||||||
|
self.assertEqual(1, ctx.exception.code)
|
||||||
|
|
||||||
|
|
||||||
class Help(unittest.TestCase):
|
class Help(unittest.TestCase):
|
||||||
@ -1331,30 +1322,3 @@ class Options(ReallyEqualMixin, unittest.TestCase):
|
|||||||
["--node-directory=there", "run", some_twistd_option])
|
["--node-directory=there", "run", some_twistd_option])
|
||||||
self.failUnlessRaises(usage.UsageError, self.parse,
|
self.failUnlessRaises(usage.UsageError, self.parse,
|
||||||
["run", "--basedir=here", some_twistd_option])
|
["run", "--basedir=here", some_twistd_option])
|
||||||
|
|
||||||
|
|
||||||
class Run(unittest.TestCase):
|
|
||||||
|
|
||||||
@patch('allmydata.scripts.tahoe_run.os.chdir')
|
|
||||||
@patch('allmydata.scripts.tahoe_run.twistd')
|
|
||||||
def test_non_numeric_pid(self, mock_twistd, chdir):
|
|
||||||
"""
|
|
||||||
If the pidfile exists but does not contain a numeric value, a complaint to
|
|
||||||
this effect is written to stderr.
|
|
||||||
"""
|
|
||||||
basedir = FilePath(ensure_str(self.mktemp()))
|
|
||||||
basedir.makedirs()
|
|
||||||
basedir.child(u"twistd.pid").setContent(b"foo")
|
|
||||||
basedir.child(u"tahoe-client.tac").setContent(b"")
|
|
||||||
|
|
||||||
config = tahoe_run.RunOptions()
|
|
||||||
config.stdout = StringIO()
|
|
||||||
config.stderr = StringIO()
|
|
||||||
config['basedir'] = ensure_text(basedir.path)
|
|
||||||
config.twistd_args = []
|
|
||||||
|
|
||||||
result_code = tahoe_run.run(config)
|
|
||||||
self.assertIn("invalid PID file", config.stderr.getvalue())
|
|
||||||
self.assertTrue(len(mock_twistd.mock_calls), 1)
|
|
||||||
self.assertEqual(mock_twistd.mock_calls[0][0], 'runApp')
|
|
||||||
self.assertEqual(0, result_code)
|
|
||||||
|
@ -16,11 +16,19 @@ from six.moves import (
|
|||||||
StringIO,
|
StringIO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from testtools import (
|
||||||
|
skipIf,
|
||||||
|
)
|
||||||
|
|
||||||
from testtools.matchers import (
|
from testtools.matchers import (
|
||||||
Contains,
|
Contains,
|
||||||
Equals,
|
Equals,
|
||||||
|
HasLength,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from twisted.python.runtime import (
|
||||||
|
platform,
|
||||||
|
)
|
||||||
from twisted.python.filepath import (
|
from twisted.python.filepath import (
|
||||||
FilePath,
|
FilePath,
|
||||||
)
|
)
|
||||||
@ -33,6 +41,8 @@ from twisted.internet.test.modulehelpers import (
|
|||||||
|
|
||||||
from ...scripts.tahoe_run import (
|
from ...scripts.tahoe_run import (
|
||||||
DaemonizeTheRealService,
|
DaemonizeTheRealService,
|
||||||
|
RunOptions,
|
||||||
|
run,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...scripts.runner import (
|
from ...scripts.runner import (
|
||||||
@ -135,3 +145,40 @@ class DaemonizeTheRealServiceTests(SyncTestCase):
|
|||||||
""",
|
""",
|
||||||
"Privacy requested",
|
"Privacy requested",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RunTests(SyncTestCase):
|
||||||
|
"""
|
||||||
|
Tests for ``run``.
|
||||||
|
"""
|
||||||
|
@skipIf(platform.isWindows(), "There are no PID files on Windows.")
|
||||||
|
def test_non_numeric_pid(self):
|
||||||
|
"""
|
||||||
|
If the pidfile exists but does not contain a numeric value, a complaint to
|
||||||
|
this effect is written to stderr.
|
||||||
|
"""
|
||||||
|
basedir = FilePath(self.mktemp()).asTextMode()
|
||||||
|
basedir.makedirs()
|
||||||
|
basedir.child(u"twistd.pid").setContent(b"foo")
|
||||||
|
basedir.child(u"tahoe-client.tac").setContent(b"")
|
||||||
|
|
||||||
|
config = RunOptions()
|
||||||
|
config.stdout = StringIO()
|
||||||
|
config.stderr = StringIO()
|
||||||
|
config['basedir'] = basedir.path
|
||||||
|
config.twistd_args = []
|
||||||
|
|
||||||
|
runs = []
|
||||||
|
result_code = run(config, runApp=runs.append)
|
||||||
|
self.assertThat(
|
||||||
|
config.stderr.getvalue(),
|
||||||
|
Contains("found invalid PID file in"),
|
||||||
|
)
|
||||||
|
self.assertThat(
|
||||||
|
runs,
|
||||||
|
HasLength(1),
|
||||||
|
)
|
||||||
|
self.assertThat(
|
||||||
|
result_code,
|
||||||
|
Equals(0),
|
||||||
|
)
|
||||||
|
@ -12,7 +12,6 @@ if PY2:
|
|||||||
from six import ensure_text
|
from six import ensure_text
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import mock
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from os.path import join
|
from os.path import join
|
||||||
@ -22,8 +21,8 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from allmydata.mutable.publish import MutableData
|
from allmydata.mutable.publish import MutableData
|
||||||
from allmydata.scripts.common_http import BadResponse
|
from allmydata.scripts.common_http import BadResponse
|
||||||
from allmydata.scripts.tahoe_status import _get_json_for_fragment
|
from allmydata.scripts.tahoe_status import _handle_response_for_fragment
|
||||||
from allmydata.scripts.tahoe_status import _get_json_for_cap
|
from allmydata.scripts.tahoe_status import _get_request_parameters_for_fragment
|
||||||
from allmydata.scripts.tahoe_status import pretty_progress
|
from allmydata.scripts.tahoe_status import pretty_progress
|
||||||
from allmydata.scripts.tahoe_status import do_status
|
from allmydata.scripts.tahoe_status import do_status
|
||||||
from allmydata.web.status import marshal_json
|
from allmydata.web.status import marshal_json
|
||||||
@ -140,17 +139,12 @@ class CommandStatus(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
These tests just exercise the renderers and ensure they don't
|
These tests just exercise the renderers and ensure they don't
|
||||||
catastrophically fail.
|
catastrophically fail.
|
||||||
|
|
||||||
They could be enhanced to look for "some" magic strings in the
|
|
||||||
results and assert they're in the output.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.options = _FakeOptions()
|
self.options = _FakeOptions()
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_no_operations(self):
|
||||||
@mock.patch('sys.stdout', StringIO())
|
|
||||||
def test_no_operations(self, http):
|
|
||||||
values = [
|
values = [
|
||||||
StringIO(ensure_text(json.dumps({
|
StringIO(ensure_text(json.dumps({
|
||||||
"active": [],
|
"active": [],
|
||||||
@ -165,12 +159,11 @@ class CommandStatus(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
}))),
|
}))),
|
||||||
]
|
]
|
||||||
http.side_effect = lambda *args, **kw: values.pop(0)
|
def do_http(*args, **kw):
|
||||||
do_status(self.options)
|
return values.pop(0)
|
||||||
|
do_status(self.options, do_http)
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_simple(self):
|
||||||
@mock.patch('sys.stdout', StringIO())
|
|
||||||
def test_simple(self, http):
|
|
||||||
recent_items = active_items = [
|
recent_items = active_items = [
|
||||||
UploadStatus(),
|
UploadStatus(),
|
||||||
DownloadStatus(b"abcd", 12345),
|
DownloadStatus(b"abcd", 12345),
|
||||||
@ -201,80 +194,72 @@ class CommandStatus(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
}).encode("utf-8")),
|
}).encode("utf-8")),
|
||||||
]
|
]
|
||||||
http.side_effect = lambda *args, **kw: values.pop(0)
|
def do_http(*args, **kw):
|
||||||
do_status(self.options)
|
return values.pop(0)
|
||||||
|
do_status(self.options, do_http)
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_fetch_error(self):
|
||||||
def test_fetch_error(self, http):
|
def do_http(*args, **kw):
|
||||||
|
|
||||||
def boom(*args, **kw):
|
|
||||||
raise RuntimeError("boom")
|
raise RuntimeError("boom")
|
||||||
http.side_effect = boom
|
do_status(self.options, do_http)
|
||||||
do_status(self.options)
|
|
||||||
|
|
||||||
|
|
||||||
class JsonHelpers(unittest.TestCase):
|
class JsonHelpers(unittest.TestCase):
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_bad_response(self):
|
||||||
def test_bad_response(self, http):
|
def do_http(*args, **kw):
|
||||||
http.return_value = BadResponse('the url', 'some err')
|
return
|
||||||
with self.assertRaises(RuntimeError) as ctx:
|
with self.assertRaises(RuntimeError) as ctx:
|
||||||
_get_json_for_fragment({'node-url': 'http://localhost:1234'}, '/fragment')
|
_handle_response_for_fragment(
|
||||||
self.assertTrue(
|
BadResponse('the url', 'some err'),
|
||||||
"Failed to get" in str(ctx.exception)
|
'http://localhost:1234',
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Failed to get",
|
||||||
|
str(ctx.exception),
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_happy_path(self):
|
||||||
def test_happy_path(self, http):
|
resp = _handle_response_for_fragment(
|
||||||
http.return_value = StringIO('{"some": "json"}')
|
StringIO('{"some": "json"}'),
|
||||||
resp = _get_json_for_fragment({'node-url': 'http://localhost:1234/'}, '/fragment/')
|
'http://localhost:1234/',
|
||||||
self.assertEqual(resp, dict(some='json'))
|
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
|
||||||
def test_happy_path_post(self, http):
|
|
||||||
http.return_value = StringIO('{"some": "json"}')
|
|
||||||
resp = _get_json_for_fragment(
|
|
||||||
{'node-url': 'http://localhost:1234/'},
|
|
||||||
'/fragment/',
|
|
||||||
method='POST',
|
|
||||||
post_args={'foo': 'bar'}
|
|
||||||
)
|
)
|
||||||
self.assertEqual(resp, dict(some='json'))
|
self.assertEqual(resp, dict(some='json'))
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_happy_path_post(self):
|
||||||
def test_happy_path_for_cap(self, http):
|
resp = _handle_response_for_fragment(
|
||||||
http.return_value = StringIO('{"some": "json"}')
|
StringIO('{"some": "json"}'),
|
||||||
resp = _get_json_for_cap({'node-url': 'http://localhost:1234'}, 'fake cap')
|
'http://localhost:1234/',
|
||||||
|
)
|
||||||
self.assertEqual(resp, dict(some='json'))
|
self.assertEqual(resp, dict(some='json'))
|
||||||
|
|
||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
def test_no_data_returned(self):
|
||||||
def test_no_data_returned(self, http):
|
|
||||||
http.return_value = StringIO('null')
|
|
||||||
|
|
||||||
with self.assertRaises(RuntimeError) as ctx:
|
with self.assertRaises(RuntimeError) as ctx:
|
||||||
_get_json_for_cap({'node-url': 'http://localhost:1234'}, 'fake cap')
|
_handle_response_for_fragment(StringIO('null'), 'http://localhost:1234')
|
||||||
self.assertTrue('No data from' in str(ctx.exception))
|
self.assertIn('No data from', str(ctx.exception))
|
||||||
|
|
||||||
def test_no_post_args(self):
|
def test_no_post_args(self):
|
||||||
with self.assertRaises(ValueError) as ctx:
|
with self.assertRaises(ValueError) as ctx:
|
||||||
_get_json_for_fragment(
|
_get_request_parameters_for_fragment(
|
||||||
{'node-url': 'http://localhost:1234'},
|
{'node-url': 'http://localhost:1234'},
|
||||||
'/fragment',
|
'/fragment',
|
||||||
method='POST',
|
method='POST',
|
||||||
post_args=None,
|
post_args=None,
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertIn(
|
||||||
"Must pass post_args" in str(ctx.exception)
|
"Must pass post_args",
|
||||||
|
str(ctx.exception),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_post_args_for_get(self):
|
def test_post_args_for_get(self):
|
||||||
with self.assertRaises(ValueError) as ctx:
|
with self.assertRaises(ValueError) as ctx:
|
||||||
_get_json_for_fragment(
|
_get_request_parameters_for_fragment(
|
||||||
{'node-url': 'http://localhost:1234'},
|
{'node-url': 'http://localhost:1234'},
|
||||||
'/fragment',
|
'/fragment',
|
||||||
method='GET',
|
method='GET',
|
||||||
post_args={'foo': 'bar'}
|
post_args={'foo': 'bar'}
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertIn(
|
||||||
"only valid for POST" in str(ctx.exception)
|
"only valid for POST",
|
||||||
|
str(ctx.exception),
|
||||||
)
|
)
|
||||||
|
@ -35,6 +35,9 @@ from twisted.internet.error import (
|
|||||||
from twisted.internet.interfaces import (
|
from twisted.internet.interfaces import (
|
||||||
IProcessProtocol,
|
IProcessProtocol,
|
||||||
)
|
)
|
||||||
|
from twisted.python.log import (
|
||||||
|
msg,
|
||||||
|
)
|
||||||
from twisted.python.filepath import (
|
from twisted.python.filepath import (
|
||||||
FilePath,
|
FilePath,
|
||||||
)
|
)
|
||||||
@ -99,7 +102,10 @@ class _ProcessProtocolAdapter(ProcessProtocol, object):
|
|||||||
try:
|
try:
|
||||||
proto = self._fds[childFD]
|
proto = self._fds[childFD]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
msg(format="Received unhandled output on %(fd)s: %(output)s",
|
||||||
|
fd=childFD,
|
||||||
|
output=data,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
proto.dataReceived(data)
|
proto.dataReceived(data)
|
||||||
|
|
||||||
@ -158,6 +164,9 @@ class CLINodeAPI(object):
|
|||||||
u"-m",
|
u"-m",
|
||||||
u"allmydata.scripts.runner",
|
u"allmydata.scripts.runner",
|
||||||
] + argv
|
] + argv
|
||||||
|
msg(format="Executing %(argv)s",
|
||||||
|
argv=argv,
|
||||||
|
)
|
||||||
return self.reactor.spawnProcess(
|
return self.reactor.spawnProcess(
|
||||||
processProtocol=process_protocol,
|
processProtocol=process_protocol,
|
||||||
executable=exe,
|
executable=exe,
|
||||||
|
915
src/allmydata/test/common_system.py
Normal file
915
src/allmydata/test/common_system.py
Normal file
@ -0,0 +1,915 @@
|
|||||||
|
"""
|
||||||
|
Test infrastructure for integration-y tests that run actual nodes, like those
|
||||||
|
in ``allmydata.test.test_system``.
|
||||||
|
|
||||||
|
Ported to Python 3.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from future.utils import PY2
|
||||||
|
if PY2:
|
||||||
|
# Don't import bytes since it causes issues on (so far unported) modules on Python 2.
|
||||||
|
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, max, min, str # noqa: F401
|
||||||
|
|
||||||
|
import os
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.internet import defer
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
from twisted.application import service
|
||||||
|
|
||||||
|
from foolscap.api import flushEventualQueue
|
||||||
|
|
||||||
|
from allmydata import client
|
||||||
|
from allmydata.introducer.server import create_introducer
|
||||||
|
from allmydata.util import fileutil, log, pollmixin
|
||||||
|
|
||||||
|
from twisted.python.filepath import (
|
||||||
|
FilePath,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .common import (
|
||||||
|
TEST_RSA_KEY_SIZE,
|
||||||
|
SameProcessStreamEndpointAssigner,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import common_util as testutil
|
||||||
|
from ..scripts.common import (
|
||||||
|
write_introducer,
|
||||||
|
)
|
||||||
|
|
||||||
|
# our system test uses the same Tub certificates each time, to avoid the
|
||||||
|
# overhead of key generation
|
||||||
|
SYSTEM_TEST_CERTS = [
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1iNV
|
||||||
|
z07PYwZwucl87QlL2TFZvDxD4flZ/p3BZE3DCT5Efn9w2NT4sHXL1e+R/qsDFuNG
|
||||||
|
bw1y1TRM0DGK6Wr0XRT2mLQULNgB8y/HrhcSdONsYRyWdj+LimyECKjwh0iSkApv
|
||||||
|
Yj/7IOuq6dOoh67YXPdf75OHLShm4+8q8fuwhBL+nuuO4NhZDJKupYHcnuCkcF88
|
||||||
|
LN77HKrrgbpyVmeghUkwJMLeJCewvYVlambgWRiuGGexFgAm6laS3rWetOcdm9eg
|
||||||
|
FoA9PKNN6xvPatbj99MPoLpBbzsI64M0yT/wTSw1pj/Nom3rwfMa2OH8Kk7c8R/r
|
||||||
|
U3xj4ZY1DTlGERvejQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAwyQjQ3ZgtJ3JW
|
||||||
|
r3/EPdqSUBamTfXIpOh9rXmRjPpbe+MvenqIzl4q+GnkL5mdEb1e1hdKQZgFQ5Q5
|
||||||
|
tbcNIz6h5C07KaNtbqhZCx5c/RUEH87VeXuAuOqZHbZWJ18q0tnk+YgWER2TOkgE
|
||||||
|
RI2AslcsJBt88UUOjHX6/7J3KjPFaAjW1QV3TTsHxk14aYDYJwPdz+ijchgbOPQ0
|
||||||
|
i+ilhzcB+qQnOC1s4xQSFo+zblTO7EgqM9KpupYfOVFh46P1Mak2W8EDvhz0livl
|
||||||
|
OROXJ6nR/13lmQdfVX6T45d+ITBwtmW2nGAh3oI3JlArGKHaW+7qnuHR72q9FSES
|
||||||
|
cEYA/wmk
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWI1XPTs9jBnC5
|
||||||
|
yXztCUvZMVm8PEPh+Vn+ncFkTcMJPkR+f3DY1PiwdcvV75H+qwMW40ZvDXLVNEzQ
|
||||||
|
MYrpavRdFPaYtBQs2AHzL8euFxJ042xhHJZ2P4uKbIQIqPCHSJKQCm9iP/sg66rp
|
||||||
|
06iHrthc91/vk4ctKGbj7yrx+7CEEv6e647g2FkMkq6lgdye4KRwXzws3vscquuB
|
||||||
|
unJWZ6CFSTAkwt4kJ7C9hWVqZuBZGK4YZ7EWACbqVpLetZ605x2b16AWgD08o03r
|
||||||
|
G89q1uP30w+gukFvOwjrgzTJP/BNLDWmP82ibevB8xrY4fwqTtzxH+tTfGPhljUN
|
||||||
|
OUYRG96NAgMBAAECggEAJ5xztBx0+nFnisZ9yG8uy6d4XPyc5gE1J4dRDdfgmyYc
|
||||||
|
j3XNjx6ePi4cHZ/qVryVnrc+AS7wrgW1q9FuS81QFKPbFdZB4SW3/p85BbgY3uxu
|
||||||
|
0Ovz3T3V9y4polx12eCP0/tKLVd+gdF2VTik9Sxfs5rC8VNN7wmJNuK4A/k15sgy
|
||||||
|
BIu/R8NlMNGQySNhtccp+dzB8uTyKx5zFZhVvnAK/3YX9BC2V4QBW9JxO4S8N0/9
|
||||||
|
48e9Sw/fGCfQ/EFPKGCvTvfuRqJ+4t5k10FygXJ+s+y70ifYi+aSsjJBuranbLJp
|
||||||
|
g5TwhuKnTWs8Nth3YRLbcJL4VBIOehjAWy8pDMMtlQKBgQD0O8cHb8cOTGW0BijC
|
||||||
|
NDofhA2GooQUUR3WL324PXWZq0DXuBDQhJVBKWO3AYonivhhd/qWO8lea9MEmU41
|
||||||
|
nKZ7maS4B8AJLJC08P8GL1uCIE/ezEXEi9JwC1zJiyl595Ap4lSAozH0DwjNvmGL
|
||||||
|
5mIdYg0BliqFXbloNJkNlb7INwKBgQDgdGEIWXc5Y1ncWNs6iDIV/t2MlL8vLrP0
|
||||||
|
hpkl/QiMndOQyD6JBo0+ZqvOQTSS4NTSxBROjPxvFbEJ3eH8Pmn8gHOf46fzP1OJ
|
||||||
|
wlYv0gYzkN4FE/tN6JnO2u9pN0euyyZLM1fnEcrMWColMN8JlWjtA7Gbxm8lkfa4
|
||||||
|
3vicaJtlWwKBgQCQYL4ZgVR0+Wit8W4qz+EEPHYafvwBXqp6sXxqa7qXawtb+q3F
|
||||||
|
9nqdGLCfwMNA+QA37ksugI1byfXmpBH902r/aiZbvAkj4zpwHH9F0r0PwbY1iSA9
|
||||||
|
PkLahX0Gj8OnHFgWynsVyGOBWVnk9oSHxVt+7zWtGG5uhKdUGLPZugocJQKBgB61
|
||||||
|
7bzduOFiRZ5PjhdxISE/UQL2Kz6Cbl7rt7Kp72yF/7eUnnHTMqoyFBnRdCcQmi4I
|
||||||
|
ZBrnUXbFigamlFAWHhxNWwSqeoVeychUjcRXQT/291nMhRsA02KpNA66YJV6+E9b
|
||||||
|
xBA6r/vLqGCUUkAWcFfVpIyC1xxV32MmJvAHpBN3AoGAPF3MUFiO0iKNZfst6Tm3
|
||||||
|
rzrldLawDo98DRZ7Yb2kWlWZYqUk/Nvryvo2cns75WGSMDYVbbRp+BY7kZmNYa9K
|
||||||
|
iQzKDL54ZRu6V+getJdeAO8yXoCmnZKxt5OHvOSrQMfAmFKSwLwxBbZBfXEyuune
|
||||||
|
yfusXLtCgajpreoVIa0xWdQ=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 0
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApDzW
|
||||||
|
4ZBeK9w4xpRaed6lXzeCO0Xmr3f0ynbueSdiZ89FWoAMgK+SiBIOViYV6hfm0Wah
|
||||||
|
lemSNzFGx5LvDSg2uwSqEP23DeM9O/SQPgIAiLeeEsYZJcgg2jz92YfFEaahsGdI
|
||||||
|
6qSP4XI2/5dgKRpPOYDGyw6R5PQR6w22Xq1WD1jBvImk/k09I9jHRn40pYbaJzbg
|
||||||
|
U2aIjvOruo2kqe4f6iDqE0piYimAZJUvemu1UoyV5NG590hGkDuWsMD77+d2FxCj
|
||||||
|
9Nzb+iuuG3ksnanHPyXi1hQmzp5OmzVWaevCHinNjWgsuSuLGO9H2SLf3wwp2UCs
|
||||||
|
EpKtzoKrnZdEg/anNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQChxtr67o1aZZMJ
|
||||||
|
A6gESPtFjZLw6wG0j50JsrWKLvoXVts1ToJ9u2nx01aFKjBwb4Yg+vdJfDgIIAEm
|
||||||
|
jS56h6H2DfJlkTWHmi8Vx1wuusWnrNwYMI53tdlRIpD2+Ne7yeoLQZcVN2wuPmxD
|
||||||
|
Mbksg4AI4csmbkU/NPX5DtMy4EzM/pFvIcxNIVRUMVTFzn5zxhKfhyPqrMI4fxw1
|
||||||
|
UhUbEKO+QgIqTNp/dZ0lTbFs5HJQn6yirWyyvQKBPmaaK+pKd0RST/T38OU2oJ/J
|
||||||
|
LojRs7ugCJ+bxJqegmQrdcVqZZGbpYeK4O/5eIn8KOlgh0nUza1MyjJJemgBBWf7
|
||||||
|
HoXB8Fge
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkPNbhkF4r3DjG
|
||||||
|
lFp53qVfN4I7Reavd/TKdu55J2Jnz0VagAyAr5KIEg5WJhXqF+bRZqGV6ZI3MUbH
|
||||||
|
ku8NKDa7BKoQ/bcN4z079JA+AgCIt54SxhklyCDaPP3Zh8URpqGwZ0jqpI/hcjb/
|
||||||
|
l2ApGk85gMbLDpHk9BHrDbZerVYPWMG8iaT+TT0j2MdGfjSlhtonNuBTZoiO86u6
|
||||||
|
jaSp7h/qIOoTSmJiKYBklS96a7VSjJXk0bn3SEaQO5awwPvv53YXEKP03Nv6K64b
|
||||||
|
eSydqcc/JeLWFCbOnk6bNVZp68IeKc2NaCy5K4sY70fZIt/fDCnZQKwSkq3Ogqud
|
||||||
|
l0SD9qc3AgMBAAECggEBAIu55uaIOFYASZ1IYaEFNpRHWVisI5Js76nAfSo9w46l
|
||||||
|
3E8eWYSx2mxBUEkipco/A3RraFVuHaMvHRR1gUMkT0vUsAs8jxwVk+cKLh1S/rlR
|
||||||
|
3f4C4yotlSWWdjE3PQXDShQWCwb1ciNPVFMmqfzOEVDOqlHe12h97TCYverWdT0f
|
||||||
|
3LZICLQsZd1WPKnPNXrsRRDCBuRLapdg+M0oJ+y6IiCdm+qM7Qvaoef6hlvm5ECz
|
||||||
|
LCM92db5BKTuPOQXMx2J8mjaBgU3aHxRV08IFgs7mI6q0t0FM7LlytIAJq1Hg5QU
|
||||||
|
36zDKo8tblkPijWZWlqlZCnlarrd3Ar/BiLEiuOGDMECgYEA1GOp0KHy0mbJeN13
|
||||||
|
+TDsgP7zhmqFcuJREu2xziNJPK2S06NfGYE8vuVqBGzBroLTQ3dK7rOJs9C6IjCE
|
||||||
|
mH7ZeHzfcKohpZnl443vHMSpgdh/bXTEO1aQZNbJ2hLYs8ie/VqqHR0u6YtpUqZL
|
||||||
|
LgaUA0U8GnlsO55B8kyCelckmDkCgYEAxfYQMPEEzg1tg2neqEfyoeY0qQTEJTeh
|
||||||
|
CPMztowSJpIyF1rQH6TaG0ZchkiAkw3W58RVDfvK72TuVlC5Kz00C2/uPnrqm0dX
|
||||||
|
iMPeML5rFlG3VGCrSTnAPI+az6P65q8zodqcTtA8xoxgPOlc/lINOxiTEMxLyeGF
|
||||||
|
8GyP+sCM2u8CgYEAvMBR05OJnEky9hJEpBZBqSZrQGL8dCwDh0HtCdi8JovPd/yx
|
||||||
|
8JW1aaWywXnx6uhjXoru8hJm54IxWV8rB+d716OKY7MfMfACqWejQDratgW0wY7L
|
||||||
|
MjztGGD2hLLJGYXLHjfsBPHBllaKZKRbHe1Er19hWdndQWKVEwPB1X4KjKkCgYEA
|
||||||
|
nWHmN3K2djbYtRyLR1CEBtDlVuaSJmCWp23q1BuCJqYeKtEpG69NM1f6IUws5Dyh
|
||||||
|
eXtuf4KKMU8V6QueW1D6OomPaJ8CO9c5MWM/F5ObwY/P58Y/ByVhvwQQeToONC5g
|
||||||
|
JzKNCF+nodZigKqrIwoKuMvtx/IT4vloKd+1jA5fLYMCgYBoT3HLCyATVdDSt1TZ
|
||||||
|
SbEDoLSYt23KRjQV93+INP949dYCagtgh/kTzxBopw5FljISLfdYizIRo2AzhhfP
|
||||||
|
WWpILlnt19kD+sNirJVqxJacfEZsu5baWTedI/yrCuVsAs/s3/EEY6q0Qywknxtp
|
||||||
|
Fwh1/8y5t14ib5fxOVhi8X1nEA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 1
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwMTn
|
||||||
|
hXnpKHGAir3WYbOxefVrMA07OZNAsNa29nBwLA+NVIJNUFgquibMj7QYo8+M45oY
|
||||||
|
6LKr4yRcBryZVvyxfdr92xp8+kLeVApk2WLjkdBTRagHh9qdrY0hQmagCBN6/hLG
|
||||||
|
Xug8VksQUdhX3vu6ZyMvTLfKRkDOMRVkRGRGg/dOcvom7zpqMCGYenMG2FStr6UV
|
||||||
|
3s3dlCSZZTdTX5Uoq6yfUUJE3nITGKjpnpJKqIs3PWCIxdj7INIcjJKvIdUcavIV
|
||||||
|
2hEhh60A8ltmtdpQAXVBE+U7aZgS1fGAWS2A0a3UwuP2pkQp6OyKCUVHpZatbl9F
|
||||||
|
ahDN2QBzegv/rdJ1zwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAl4OQZ+FB9ZSUv
|
||||||
|
FL/KwLNt+ONU8Sve/xiX+8vKAvgKm2FrjyK+AZPwibnu+FSt2G4ndZBx4Wvpe5V+
|
||||||
|
gCsbzSXlh9cDn2SRXyprt2l/8Fj4eUMaThmLKOK200/N/s2SpmBtnuflBrhNaJpw
|
||||||
|
DEi2KEPuXsgvkuVzXN06j75cUHwn5LeWDAh0RalkVuGbEWBoFx9Hq8WECdlCy0YS
|
||||||
|
y09+yO01qz70y88C2rPThKw8kP4bX8aFZbvsnRHsLu/8nEQNlrELcfBarPVHjJ/9
|
||||||
|
imxOdymJkV152V58voiXP/PwXhynctQbF7e+0UZ+XEGdbAbZA0BMl7z+b09Z+jF2
|
||||||
|
afm4mVox
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAxOeFeekocYCK
|
||||||
|
vdZhs7F59WswDTs5k0Cw1rb2cHAsD41Ugk1QWCq6JsyPtBijz4zjmhjosqvjJFwG
|
||||||
|
vJlW/LF92v3bGnz6Qt5UCmTZYuOR0FNFqAeH2p2tjSFCZqAIE3r+EsZe6DxWSxBR
|
||||||
|
2Ffe+7pnIy9Mt8pGQM4xFWREZEaD905y+ibvOmowIZh6cwbYVK2vpRXezd2UJJll
|
||||||
|
N1NflSirrJ9RQkTechMYqOmekkqoizc9YIjF2Psg0hyMkq8h1Rxq8hXaESGHrQDy
|
||||||
|
W2a12lABdUET5TtpmBLV8YBZLYDRrdTC4/amRCno7IoJRUellq1uX0VqEM3ZAHN6
|
||||||
|
C/+t0nXPAgMBAAECggEAF+2ZK4lZdsq4AQDVhqUuh4v+NSW/T0NHCWxto6OLWPzJ
|
||||||
|
N09BV5LKIvdD9yaM1HCj9XCgXOooyfYuciuhARo20f+H+VWNY+c+/8GWiSFsTCJG
|
||||||
|
4+Oao7NwVSWqljp07Ou2Hamo9AjxzGhe6znmlmg62CiW63f45MWQkqksHA0yb5jg
|
||||||
|
/onJ2//I+OI+aTKNfjt1G6h2x7oxeGTU1jJ0Hb2xSh+Mpqx9NDfb/KZyOndhSG5N
|
||||||
|
xRVosQ6uV+9mqHxTTwTZurTG31uhZzarkMuqxhcHS94ub7berEc/OlqvbyMKNZ3A
|
||||||
|
lzuvq0NBZhEUhAVgORAIS17r/q2BvyG4u5LFbG2p0QKBgQDeyyOl+A7xc4lPE2OL
|
||||||
|
Z3KHJPP4RuUnHnWFC+bNdr5Ag8K7jcjZIcasyUom9rOR0Fpuw9wmXpp3+6fyp9bJ
|
||||||
|
y6Bi5VioR0ZFP5X+nXxIN3yvgypu6AZvkhHrEFer+heGHxPlbwNKCKMbPzDZPBTZ
|
||||||
|
vlC7g7xUUcpNmGhrOKr3Qq5FlwKBgQDdgCmRvsHUyzicn8TI3IJBAOcaQG0Yr/R2
|
||||||
|
FzBqNfHHx7fUZlJfKJsnu9R9VRZmBi4B7MA2xcvz4QrdZWEtY8uoYp8TAGILfW1u
|
||||||
|
CP4ZHrzfDo/67Uzk2uTMTd0+JOqSm/HiVNguRPvC8EWBoFls+h129GKThMvKR1hP
|
||||||
|
1oarfAGIiQKBgQCIMAq5gHm59JMhqEt4QqMKo3cS9FtNX1wdGRpbzFMd4q0dstzs
|
||||||
|
ha4Jnv3Z9YHtBzzQap9fQQMRht6yARDVx8hhy6o3K2J0IBtTSfdXubtZGkfNBb4x
|
||||||
|
Y0vaseG1uam5jbO+0u5iygbSN/1nPUfNln2JMkzkCh8s8ZYavMgdX0BiPwKBgChR
|
||||||
|
QL/Hog5yoy5XIoGRKaBdYrNzkKgStwObuvNKOGUt5DckHNA3Wu6DkOzzRO1zKIKv
|
||||||
|
LlmJ7VLJ3qln36VcaeCPevcBddczkGyb9GxsHOLZCroY4YsykLzjW2cJXy0qd3/E
|
||||||
|
A8mAQvc7ttsebciZSi2x1BOX82QxUlDN8ptaKglJAoGBAMnLN1TQB0xtWYDPGcGV
|
||||||
|
2IvgX7OTRRlMVrTvIOvP5Julux9z1r0x0cesl/jaXupsBUlLLicPyBMSBJrXlr24
|
||||||
|
mrgkodk4TdqO1VtBCZBqak97DHVezstMrbpCGlUD5jBnsHVRLERvS09QlGhqMeNL
|
||||||
|
jpNQbWH9VhutzbvpYquKrhvK
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 2
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAypqi
|
||||||
|
YTni3s60Uo8vgGcFvjWWkB5CD9Fx9pW/2KcxRJ/u137Y+BG8qWMA4lgII3ZIuvo4
|
||||||
|
6rLDiXnAnDZqUtrvZ90O/gH6RyQqX3AI4EwPvCnRIIe0okRcxnxYBL/LfBY54xuv
|
||||||
|
46JRYZP4c9IImqQH9QVo2/egtEzcpbmT/mfhpf6NGQWC3Xps2BqDT2SV/DrX/wPA
|
||||||
|
8P1atE1AxNp8ENxK/cjFAteEyDZOsDSa757ZHKAdM7L8rZ1Fd2xAA1Dq7IyYpTNE
|
||||||
|
IX72xytWxllcNvSUPLT+oicsSZBadc/p3moc3tR/rNdgrHKybedadru/f9Gwpa+v
|
||||||
|
0sllZlEcVPSYddAzWwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmk60Nj5FPvemx
|
||||||
|
DSSQjJPyJoIDpTxQ4luSzIq4hPwlUXw7dqrvHyCWgn2YVe9xZsGrT/+n376ecmgu
|
||||||
|
sw4s4qVhR9bzKkTMewjC2wUooTA5v9HYsNWZy3Ah7hHPbDHlMADYobjB5/XolNUP
|
||||||
|
bCM9xALEdM9DxpC4vjUZexlRKmjww9QKE22jIM+bqsK0zqDSq+zHpfHNGGcS3vva
|
||||||
|
OvI6FPc1fAr3pZpVzevMSN2zufIJwjL4FT5/uzwOCaSCwgR1ztD5CSbQLTLlwIsX
|
||||||
|
S7h2WF9078XumeRjKejdjEjyH4abKRq8+5LVLcjKEpg7OvktuRpPoGPCEToaAzuv
|
||||||
|
h+RSQwwY
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDKmqJhOeLezrRS
|
||||||
|
jy+AZwW+NZaQHkIP0XH2lb/YpzFEn+7Xftj4EbypYwDiWAgjdki6+jjqssOJecCc
|
||||||
|
NmpS2u9n3Q7+AfpHJCpfcAjgTA+8KdEgh7SiRFzGfFgEv8t8FjnjG6/jolFhk/hz
|
||||||
|
0giapAf1BWjb96C0TNyluZP+Z+Gl/o0ZBYLdemzYGoNPZJX8Otf/A8Dw/Vq0TUDE
|
||||||
|
2nwQ3Er9yMUC14TINk6wNJrvntkcoB0zsvytnUV3bEADUOrsjJilM0QhfvbHK1bG
|
||||||
|
WVw29JQ8tP6iJyxJkFp1z+neahze1H+s12CscrJt51p2u79/0bClr6/SyWVmURxU
|
||||||
|
9Jh10DNbAgMBAAECggEBALv7Q+Rf+C7wrQDZF6LUc9CrGfq4CGVy2IGJKgqT/jOF
|
||||||
|
DO9nI1rv4hNr55sbQNneWtcZaYvht2mrzNlj57zepDjDM7DcFuLBHIuWgLXT/NmC
|
||||||
|
FyZOo3vXYBlNr8EgT2XfnXAp9UWJCmc2CtUzsIYC4dsmXMeTd8kyc5tUl4r5ybTf
|
||||||
|
1g+RTck/IGgqdfzpuTsNl79FW2rP9z111Py6dbqgQzhuSAune9dnLFvZst8dyL8j
|
||||||
|
FStETMxBM6jrCF1UcKXzG7trDHiCdzJ8WUhx6opN/8OasQGndwpXto6FZuBy/AVP
|
||||||
|
4kVQNpUXImYcLEpva0MqGRHg+YN+c84C71CMchnF4aECgYEA7J2go4CkCcZNKCy5
|
||||||
|
R5XVCqNFYRHjekR+UwH8cnCa7pMKKfP+lTCiBrO2q8zwWwknRMyuycS5g/xbSpg1
|
||||||
|
L6hi92CV1YQy1/JhlQedekjejNTTuLOPKf78AFNSfc7axDnes2v4Bvcdp9gsbUIO
|
||||||
|
10cXh0tOSLE7P9y+yC86KQkFAPECgYEA2zO0M2nvbPHv2jjtymY3pflYm0HzhM/T
|
||||||
|
kPtue3GxOgbEPsHffBGssShBTE3yCOX3aAONXJucMrSAPL9iwUfgfGx6ADdkwBsA
|
||||||
|
OjDlkxvTbP/9trE6/lsSPtGpWRdJNHqXN4Hx7gXJizRwG7Ym+oHvIIh53aIjdFoE
|
||||||
|
HLQLpxObuQsCgYAuMQ99G83qQpYpc6GwAeYXL4yJyK453kky9z5LMQRt8rKXQhS/
|
||||||
|
F0FqQYc1vsplW0IZQkQVC5yT0Z4Yz+ICLcM0O9zEVAyA78ZxC42Io9UedSXn9tXK
|
||||||
|
Awc7IQkHmmxGxm1dZYSEB5X4gFEb+zted3h2ZxMfScohS3zLI70c6a/aYQKBgQCU
|
||||||
|
phRuxUkrTUpFZ1PCbN0R/ezbpLbaewFTEV7T8b6oxgvxLxI6FdZRcSYO89DNvf2w
|
||||||
|
GLCVe6VKMWPBTlxPDEostndpjCcTq3vU+nHE+BrBkTvh14BVGzddSFsaYpMvNm8z
|
||||||
|
ojiJHH2XnCDmefkm6lRacJKL/Tcj4SNmv6YjUEXLDwKBgF8WV9lzez3d/X5dphLy
|
||||||
|
2S7osRegH99iFanw0v5VK2HqDcYO9A7AD31D9nwX46QVYfgEwa6cHtVCZbpLeJpw
|
||||||
|
qXnYXe/hUU3yn5ipdNJ0Dm/ZhJPDD8TeqhnRRhxbZmsXs8EzfwB2tcUbASvjb3qA
|
||||||
|
vAaPlOSU1wXqhAsG9aVs8gtL
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 3
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUqQ
|
||||||
|
M08E7F2ZE99bFHvpsR6LmgIJOOoGMXacTcEUhRF63E6+730FjxER2a30synv9GGS
|
||||||
|
3G9FstUmfhyimufkbTumri8Novw5CWZQLiE1rmMBI5nPcR2wAzy9z2odR6bfAwms
|
||||||
|
yyc3IPYg1BEDBPZl0LCQrQRRU/rVOrbCf7IMq+ATazmBg01gXMzq2M953ieorkQX
|
||||||
|
MsHVR/kyW0Q0yzhYF1OtIqbXxrdiZ+laTLWNqivj/FdegiWPCf8OcqpcpbgEjlDW
|
||||||
|
gBcC/vre+0E+16nfUV8xHL5jseJMJqfT508OtHxAzp+2D7b54NvYNIvbOAP+F9gj
|
||||||
|
aXy5mOvjXclK+hNmDwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAjZzTFKG7uoXxm
|
||||||
|
BPHfQvsKHIB/Cx9zMKj6pLwJzCPHQBzKOMoUen09oq+fb77RM7WvdX0pvFgEXaJW
|
||||||
|
q/ImooRMo+paf8GOZAuPwdafb2/OGdHZGZ2Cbo/ICGo1wGDCdMvbxTxrDNq1Yae+
|
||||||
|
m+2epN2pXAO1rlc7ktRkojM/qi3zXtbLjTs3IoPDXWhYPHdI1ThkneRmvxpzB1rW
|
||||||
|
2SBqj2snvyI+/3k3RHmldcdOrTlgWQ9hq05jWR8IVtRUFFVn9A+yQC3gnnLIUhwP
|
||||||
|
HJWwTIPuYW25TuxFxYZXIbnAiluZL0UIjd3IAwxaafvB6uhI7v0K789DKj2vRUkY
|
||||||
|
E8ptxZH4
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDNSpAzTwTsXZkT
|
||||||
|
31sUe+mxHouaAgk46gYxdpxNwRSFEXrcTr7vfQWPERHZrfSzKe/0YZLcb0Wy1SZ+
|
||||||
|
HKKa5+RtO6auLw2i/DkJZlAuITWuYwEjmc9xHbADPL3Pah1Hpt8DCazLJzcg9iDU
|
||||||
|
EQME9mXQsJCtBFFT+tU6tsJ/sgyr4BNrOYGDTWBczOrYz3neJ6iuRBcywdVH+TJb
|
||||||
|
RDTLOFgXU60iptfGt2Jn6VpMtY2qK+P8V16CJY8J/w5yqlyluASOUNaAFwL++t77
|
||||||
|
QT7Xqd9RXzEcvmOx4kwmp9PnTw60fEDOn7YPtvng29g0i9s4A/4X2CNpfLmY6+Nd
|
||||||
|
yUr6E2YPAgMBAAECggEBAIiL6uQl0AmDrBj6vHMghGzp+0MBza6MgngOA6L4JTTp
|
||||||
|
ToYQ3pEe4D6rxOq7+QHeiBtNd0ilvn9XpVXGqCVOzrIVNiWvaGubRjjJU9WLA1Ct
|
||||||
|
y4kpekAr1fIhScMXOsh45ub3XXZ27AVBkM5dTlvTpB8uAd0C/TFVqtR10WLsQ99h
|
||||||
|
Zm9Jczgs/6InYTssnAaqdeCLAf1LbmO4zwFsJfJOeSGGT6WBwlpHwMAgPhg8OLEu
|
||||||
|
kVWG7BEJ0hxcODk/es/vce9SN7BSyIzNY+qHcGtsrx/o0eO2Av/Z7ltV4Sz6UN1K
|
||||||
|
0y0OTiDyT/l62U2OugSN3wQ4xPTwlrWl7ZUHJmvpEaECgYEA+w2JoB2i1OV2JTPl
|
||||||
|
Y0TKSKcZYdwn7Nwh4fxMAJNJ8UbpPqrZEo37nxqlWNJrY/jKX3wHVk4ESSTaxXgF
|
||||||
|
UY7yKT0gRuD9+vE0gCbUmJQJTwbceNJUu4XrJ6SBtf72WgmphL+MtyKdwV8XltVl
|
||||||
|
Yp0hkswGmxl+5+Js6Crh7WznPl8CgYEA0VYtKs2YaSmT1zraY6Fv3AIQZq012vdA
|
||||||
|
7nVxmQ6jKDdc401OWARmiv0PrZaVNiEJ1YV8KxaPrKTfwhWqxNegmEBgA1FZ66NN
|
||||||
|
SAm8P9OCbt8alEaVkcATveXTeOCvfpZUO3sqZdDOiYLiLCsokHblkcenK85n0yT6
|
||||||
|
CzhTbvzDllECgYEAu9mfVy2Vv5OK2b+BLsw0SDSwa2cegL8eo0fzXqLXOzCCKqAQ
|
||||||
|
GTAgTSbU/idEr+NjGhtmKg/qaQioogVyhVpenLjeQ+rqYDDHxfRIM3rhlD5gDg/j
|
||||||
|
0wUbtegEHrgOgcSlEW16zzWZsS2EKxq16BoHGx6K+tcS/FOShg5ASzWnuiUCgYEA
|
||||||
|
sMz+0tLX8aG7CqHbRyBW8FMR9RY/kRMY1Q1+Bw40wMeZfSSSkYYN8T9wWWT/2rqm
|
||||||
|
qp7V0zJ34BFUJoDUPPH84fok3Uh9EKZYpAoM4z9JP0jREwBWXMYEJnOQWtwxfFGN
|
||||||
|
DLumgF2Nwtg3G6TL2s+AbtJYH4hxagQl5woIdYmnyzECgYEAsLASpou16A3uXG5J
|
||||||
|
+5ZgF2appS9Yfrqfh6TKywMsGG/JuiH3djdYhbJFIRGeHIIDb4XEXOHrg/SFflas
|
||||||
|
If0IjFRh9WCvQxnoRha3/pKRSc3OEka1MR/ZREK/d/LQEPmsRJVzY6ABKqmPAMDD
|
||||||
|
5CnG6Hz/rP87BiEKd1+3PGp8GCw=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 4
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sap
|
||||||
|
75YbbkEL85LFava3FrO1jpgVteQ4NGxxy1Nu9w2hPfMMeCPWjB8UfAwFk+LVPyvW
|
||||||
|
LAXd1zWL5rGpQ2ytIVQlTraR5EnALA1sMcQYbFz1ISPTYB031bEN/Ch8JWYwCG5A
|
||||||
|
X2H4D6BC7NgT6YyWDt8vxQnqAisPHQ/OK4ABD15CwkTyPimek2/ufYN2dapg1xhG
|
||||||
|
IUD96gqetJv9bu0r869s688kADIComsYG+8KKfFN67S3rSHMIpZPuGTtoHGnVO89
|
||||||
|
XBm0vNe0UxQkJEGJzZPn0tdec0LTC4GNtTaz5JuCjx/VsJBqrnTnHHjx0wFz8pff
|
||||||
|
afCimRwA+LCopxPE1QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBOkAnpBb3nY+dG
|
||||||
|
mKCjiLqSsuEPqpNiBYR+ue/8aVDnOKLKqAyQuyRZttQ7bPpKHaw7pwyCZH8iHnt6
|
||||||
|
pMCLCftNSlV2Fa8msRmuf5AiGjUvR1M8VtHWNYE8pedWrJqUgBhF/405B99yd8CT
|
||||||
|
kQJXKF18LObj7YKNsWRoMkVgqlQzWDMEqbfmy9MhuLx2EZPsTB1L0BHNGGDVBd9o
|
||||||
|
cpPLUixcc12u+RPMKq8x3KgwsnUf5vX/pCnoGcCy4JahWdDgcZlf0hUKGT7PUem5
|
||||||
|
CWW8SMeqSWQX9XpE5Qlm1+W/QXdDXLbbHqDtvBeUy3iFQe3C9RSkp0qdutxkAlFk
|
||||||
|
f5QHXfJ7
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSxqnvlhtuQQvz
|
||||||
|
ksVq9rcWs7WOmBW15Dg0bHHLU273DaE98wx4I9aMHxR8DAWT4tU/K9YsBd3XNYvm
|
||||||
|
salDbK0hVCVOtpHkScAsDWwxxBhsXPUhI9NgHTfVsQ38KHwlZjAIbkBfYfgPoELs
|
||||||
|
2BPpjJYO3y/FCeoCKw8dD84rgAEPXkLCRPI+KZ6Tb+59g3Z1qmDXGEYhQP3qCp60
|
||||||
|
m/1u7Svzr2zrzyQAMgKiaxgb7wop8U3rtLetIcwilk+4ZO2gcadU7z1cGbS817RT
|
||||||
|
FCQkQYnNk+fS115zQtMLgY21NrPkm4KPH9WwkGqudOccePHTAXPyl99p8KKZHAD4
|
||||||
|
sKinE8TVAgMBAAECggEALU5EotoqJUXYEtAenUJQ0pFoWjE4oXNf3Wzd/O1/MZ19
|
||||||
|
ZjqDGKPjbxUTKyLOZB5i5gQ/MhFEwQiifMD9eB+5CyvyJPw7Wc28f/uWoQ/cjBZj
|
||||||
|
Hm979PHy2X0IW4Y8QTG462b/cUE2t+0j1ZMQnKf6bVHuC7V41mR5CC8oitMl5y5g
|
||||||
|
34yJmWXlIA0ep/WotLMqvil6DnSM/2V8Ch4SxjnzPpjbe4Kj+woucGNr4UKstZER
|
||||||
|
8iuHTsR64LjoGktRnnMwZxGZQI7EC428zsliInuWMdXe//w2chLdkirqpSrIQwSZ
|
||||||
|
3jNWStqBXGYaRg5Z1ilBvHtXxkzDzbAlzRBzqfEwwQKBgQDqYdMRrzHJaXWLdsyU
|
||||||
|
6jAuNX9tLh7PcicjP93SbPujS6mWcNb+D/au+VhWD+dZQDPRZttXck7wvKY1lw1V
|
||||||
|
MK0TYI7ydf8h3DFx3Mi6ZD4JVSU1MH233C3fv/FHenDoOvMXXRjUZxaRmuzFJvzt
|
||||||
|
6QlKIfSvwT+1wrOACNfteXfZUQKBgQDmN3Uuk01qvsETPwtWBp5RNcYhS/zGEQ7o
|
||||||
|
Q4K+teU453r1v8BGsQrCqulIZ3clMkDru2UroeKn1pzyVAS2AgajgXzfXh3VeZh1
|
||||||
|
vHTLP91BBYZTTWggalEN4aAkf9bxX/hA+9Bw/dzZcQW2aNV7WrYuCSvp3SDCMina
|
||||||
|
anQq/PaSRQKBgHjw23HfnegZI89AENaydQQTFNqolrtiYvGcbgC7vakITMzVEwrr
|
||||||
|
/9VP0pYuBKmYKGTgF0RrNnKgVX+HnxibUmOSSpCv9GNrdJQVYfpT6XL1XYqxp91s
|
||||||
|
nrs7FuxUMNiUOoWOw1Yuj4W4lH4y3QaCXgnDtbfPFunaOrdRWOIv8HjRAoGAV3NT
|
||||||
|
mSitbNIfR69YIAqNky3JIJbb42VRc1tJzCYOd+o+pCF96ZyRCNehnDZpZQDM9n8N
|
||||||
|
9GAfWEBHCCpwS69DVFL422TGEnSJPJglCZwt8OgnWXd7CW05cvt1OMgzHyekhxLg
|
||||||
|
4Dse7J5pXBxAlAYmVCB5xPGR4xLpISX1EOtcwr0CgYEA5rA2IUfjZYb4mvFHMKyM
|
||||||
|
xWZuV9mnl3kg0ULttPeOl3ppwjgRbWpyNgOXl8nVMYzxwT/A+xCPA18P0EcgNAWc
|
||||||
|
frJqQYg3NMf+f0K1wSaswUSLEVrQOj25OZJNpb21JEiNfEd5DinVVj4BtVc6KSpS
|
||||||
|
kvjbn2WhEUatc3lPL3V0Fkw=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", # 5
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExM1oXDTIxMDEwMTAxNTExM1owFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1c5y
|
||||||
|
S9IZHF9MIuwdafzhMkgP37I3RVpHEbpnPwnLFqSWelS5m2eDkwWd5SkfGjrmQ5q0
|
||||||
|
PEpqLlh3zHGw9yQjnHS3CCS1PwQ1kmwvpIK3HM5y8GM7ry1zkam8ZR4iX6Y7VG9g
|
||||||
|
9mhiVVFoVhe1gHeiC/3Mp6XeNuEiD0buM+8qZx9B21I+iwzy4wva7Gw0fJeq9G1c
|
||||||
|
lq2rhpD1LlIEodimWOi7lOEkNmUiO1SvpdrGdxUDpTgbdg6r5pCGjOXLd74tAQHP
|
||||||
|
P/LuqRNJDXtwvHtLIVQnW6wjjy4oiWZ8DXOdc9SkepwQLIF5Wh8O7MzF5hrd6Cvw
|
||||||
|
SOD3EEsJbyycAob6RwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBDNcbKVUyGOAVm
|
||||||
|
k3iVuzkkkymlTAMm/gsIs6loLJrkSqNg160FdVKJoZFjQtqoqLgLrntdCJ377nZ9
|
||||||
|
1i+yzbZsA4DA7nxj0IEdnd7rRYgGLspGqWeKSTROATeT4faLTXenecm0v2Rpxqc7
|
||||||
|
dSyeZJXOd2OoUu+Q64hzXCDXC6LNM+xZufxV9qv+8d+CipV6idSQZaUWSVuqFCwD
|
||||||
|
PT0R4eWfkMMaM8QqtNot/hVCEaKT+9rG0mbpRe/b/qBy5SR0u+XgGEEIV+33L59T
|
||||||
|
FXY+DpI1Dpt/bJFoUrfj6XohxdTdqYVCn1F8in98TsRcFHyH1xlkS3Y0RIiznc1C
|
||||||
|
BwAoGZ4B
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVznJL0hkcX0wi
|
||||||
|
7B1p/OEySA/fsjdFWkcRumc/CcsWpJZ6VLmbZ4OTBZ3lKR8aOuZDmrQ8SmouWHfM
|
||||||
|
cbD3JCOcdLcIJLU/BDWSbC+kgrccznLwYzuvLXORqbxlHiJfpjtUb2D2aGJVUWhW
|
||||||
|
F7WAd6IL/cynpd424SIPRu4z7ypnH0HbUj6LDPLjC9rsbDR8l6r0bVyWrauGkPUu
|
||||||
|
UgSh2KZY6LuU4SQ2ZSI7VK+l2sZ3FQOlOBt2DqvmkIaM5ct3vi0BAc8/8u6pE0kN
|
||||||
|
e3C8e0shVCdbrCOPLiiJZnwNc51z1KR6nBAsgXlaHw7szMXmGt3oK/BI4PcQSwlv
|
||||||
|
LJwChvpHAgMBAAECggEBAK0KLeUBgIM++Y7WDCRInzYjrn08bpE5tIU7mO4jDfQg
|
||||||
|
dw1A3wtQZuOpyxW6B0siWlRis/aLv44M2cBkT3ZmEFBDAhOcKfh7fqQn3RNHG847
|
||||||
|
pDi8B4UKwxskBa7NCcLh9eirUA19hABLJ6dt/t6fdE5CNc2FZ+iAoyE8JfNwYKAd
|
||||||
|
6Fa3HqUBPNWt8ryj4ftgpMNBdfmLugEM4N20SXJA28hOq2lUcwNKQQ1xQrovl0ig
|
||||||
|
iMbMWytV4gUPKC9Wra66OYIkk/K8teiUNIYA4JwAUVTs1NEWoyfwUTz1onutCkMl
|
||||||
|
5vY7JAqRoDWoSUX6FI+IHUdyqPAMdOMhC37gjrxoo2ECgYEA7trDMu6xsOwEckDh
|
||||||
|
iz148kejMlnTTuCNetOFBw3njFgxISx0PrDLWmJmnHMxPv9AAjXYb2+UCCm3fj6Q
|
||||||
|
OB8o4ZJm0n504qbFHcb2aI22U5hZ99ERvqx8WBnJ2RarIBmg06y0ktxq8gFR2qxF
|
||||||
|
0hWAOcDn1DWQ8QI0XBiFFcJTGtcCgYEA5SdlIXRnVZDKi5YufMAORG9i74dXUi0Y
|
||||||
|
02UoVxJ+q8VFu+TT8wrC5UQehG3gX+79Cz7hthhDqOSCv6zTyE4Evb6vf9OLgnVe
|
||||||
|
E5iLF033zCxLSS9MgiZ+jTO+wK3RsapXDtGcSEk2P82Pj5seNf4Ei1GNCRlm1DbX
|
||||||
|
71wlikprHhECgYABqmLcExAIJM0vIsav2uDiB5/atQelMCmsZpcx4mXv85l8GrxA
|
||||||
|
x6jTW4ZNpvv77Xm7yjZVKJkGqYvPBI6q5YS6dfPjmeAkyHbtazrCpeJUmOZftQSD
|
||||||
|
qN5BGwTuT5sn4SXe9ABaWdEhGONCPBtMiLvZK0AymaEGHTbSQZWD/lPoBwKBgGhk
|
||||||
|
qg2zmd/BNoSgxkzOsbE7jTbR0VX+dXDYhKgmJM7b8AjJFkWCgYcwoTZzV+RcW6rj
|
||||||
|
2q+6HhizAV2QvmpiIIbQd+Mj3EpybYk/1R2ox1qcUy/j/FbOcpihGiVtCjqF/2Mg
|
||||||
|
2rGTqMMoQl6JrBmsvyU44adjixTiZz0EHZYCkQoBAoGBAMRdmoR4mgIIWFPgSNDM
|
||||||
|
ISLJxKvSFPYDLyAepLfo38NzKfPB/XuZrcOoMEWRBnLl6dNN0msuzXnPRcn1gc1t
|
||||||
|
TG7db+hivAyUoRkIW3dB8pRj9dDUqO9OohjKsJxJaQCyH5vPkQFSLbTIgWrHhU+3
|
||||||
|
oSPiK/YngDV1AOmPDH7i62po
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #6
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAojGu
|
||||||
|
fQaTVT9DJWJ/zogGfrryEJXYVy9c441O5MrLlRx7nCIWIUs2NEhHDJdqJjYOTdmk
|
||||||
|
K98VhdMpDPZwxjgvvZrh43lStBRIW3zZxv747rSl2VtpSqD/6UNWJe5u4SR7oga4
|
||||||
|
JfITOKHg/+ASxnOxp/iu6oT6jBL6T7KSPh6Rf2+it2rsjhktRreFDJ2hyroNq1w4
|
||||||
|
ZVNCcNPgUIyos8u9RQKAWRNchFh0p0FCS9xNrn3e+yHnt+p6mOOF2gMzfXT/M2hq
|
||||||
|
KQNmc5D3yNoH2smWoz7F3XsRjIB1Ie4VWoRRaGEy7RwcwiDfcaemD0rQug6iqH7N
|
||||||
|
oomF6f3R4DyvVVLUkQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB/8SX6qyKsOyex
|
||||||
|
v3wubgN3FPyU9PqMfEzrFM6X5sax0VMVbSnekZrrXpdnXYV+3FBu2GLLQc900ojj
|
||||||
|
vKD+409JIriTcwdFGdLrQPTCRWkEOae8TlXpTxuNqJfCPVNxFN0znoat1bSRsX1U
|
||||||
|
K0mfEETQ3ARwlTkrF9CM+jkU3k/pnc9MoCLif8P7OAF38AmIbuTUG6Gpzy8RytJn
|
||||||
|
m5AiA3sds5R0rpGUu8mFeBpT6jIA1QF2g+QNHKOQcfJdCdfqTjKw5y34hjFqbWG9
|
||||||
|
RxWGeGNZkhC/jADCt+m+R6+hlyboLuIcVp8NJw6CGbr1+k136z/Dj+Fdhm6FzF7B
|
||||||
|
qULeRQJ+
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiMa59BpNVP0Ml
|
||||||
|
Yn/OiAZ+uvIQldhXL1zjjU7kysuVHHucIhYhSzY0SEcMl2omNg5N2aQr3xWF0ykM
|
||||||
|
9nDGOC+9muHjeVK0FEhbfNnG/vjutKXZW2lKoP/pQ1Yl7m7hJHuiBrgl8hM4oeD/
|
||||||
|
4BLGc7Gn+K7qhPqMEvpPspI+HpF/b6K3auyOGS1Gt4UMnaHKug2rXDhlU0Jw0+BQ
|
||||||
|
jKizy71FAoBZE1yEWHSnQUJL3E2ufd77Iee36nqY44XaAzN9dP8zaGopA2ZzkPfI
|
||||||
|
2gfayZajPsXdexGMgHUh7hVahFFoYTLtHBzCIN9xp6YPStC6DqKofs2iiYXp/dHg
|
||||||
|
PK9VUtSRAgMBAAECggEANjn0A3rqUUr4UQxwfIV/3mj0O1VN4kBEhxOcd+PRUsYW
|
||||||
|
EapXycPSmII9ttj8tU/HUoHcYIqSMI7bn6jZJXxtga/BrALJAsnxMx031k8yvOQK
|
||||||
|
uvPT7Q6M4NkReVcRHRbMeuxSLuWTRZDhn8qznEPb9rOvD1tsRN6nb3PdbwVbUcZh
|
||||||
|
2F6JDrTyI/Df6nrYQAWOEe2ay7tzgrNYE4vh+DW7oVmyHRgFYA+DIG5Q+7OVWeW5
|
||||||
|
bwYYPKlo4/B0L+GfMKfMVZ+5TvFWAK0YD1e/CW1Gv+i/8dWm4O7UNGg5mTnrIcy1
|
||||||
|
g5wkKbyea02/np2B/XBsSWXDl6rTDHL7ay0rH2hjEQKBgQDMKSm3miQTIcL/F2kG
|
||||||
|
ieapmRtSc7cedP967IwUfjz4+pxPa4LiU47OCGp1bmUTuJAItyQyu/5O3uLpAriD
|
||||||
|
PTU+oVlhqt+lI6+SJ4SIYw01/iWI3EF2STwXVnohWG1EgzuFM/EqoB+mrodNONfG
|
||||||
|
UmP58vI9Is8fdugXgpTz4Yq9pQKBgQDLYJoyMVrYTvUn5oWft8ptsWZn6JZXt5Bd
|
||||||
|
aXh+YhNmtCrSORL3XjcH4yjlcn7X8Op33WQTbPo7QAJ1CumJzAI88BZ/8za638xb
|
||||||
|
nLueviZApCt0bNMEEdxDffxHFc5TyHE+obMKFfApbCnD0ggO6lrZ8jK9prArLOCp
|
||||||
|
mRU9SSRffQKBgAjoBszeqZI4F9SfBdLmMyzU5A89wxBOFFMdfKLsOua1sBn627PZ
|
||||||
|
51Hvpg1HaptoosfujWK1NsvkB0wY9UmsYuU/jrGnDaibnO4oUSzN/WaMlsCYszZg
|
||||||
|
zYFLIXrQ67tgajlOYcf1Qkw4MujYgPlC4N+njI/EM/rwagGUjcDx5uaNAoGASyqz
|
||||||
|
EuYG63eTSGH89SEaohw0+yaNmnHv23aF4EAjZ4wjX3tUtTSPJk0g6ly84Nbb8d1T
|
||||||
|
hZJ7kbaAsf2Mfy91jEw4JKYhjkP05c8x0OP6g12p6efmvdRUEmXX/fXjQjgNEtb0
|
||||||
|
sz+UedrOPN+9trWLSo4njsyyw+JcTpKTtQj5dokCgYEAg9Y3msg+GvR5t/rPVlKd
|
||||||
|
keZkrAp0xBJZgqG7CTPXWS1FjwbAPo7x4ZOwtsgjCuE52lar4j+r2Il+CDYeLfxN
|
||||||
|
h/Jfn6S9ThUh+B1PMvKMMnJUahg8cVL8uQuBcbAy8HPRK78WO2BTnje44wFAJwTc
|
||||||
|
0liuYqVxZIRlFLRl8nGqog8=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #7
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu9oO
|
||||||
|
cFlNukUcLfFrfkEaUiilcHLmn5OokQbj95CGd2ehQCCVwrkunYLBisthRaancFFb
|
||||||
|
/yM998B0IUsKTsoLi5DAN3/SkSm6GiQIGO05E4eBPljwJ61QQMxh8+1TwQ9HTun1
|
||||||
|
ZE1lhVN1aRmI9VsbyTQLjXh9OFNLSJEKb29mXsgzYwYwNOvo+idzXpy4bMyNoGxY
|
||||||
|
Y+s2FIKehNHHCv4ravDn8rf6DtDOvyN4d0/QyNws9FpAZMXmLwtBJ9exOqKFW43w
|
||||||
|
97NxgdNiTFyttrTKTi0b+9v3GVdcEZw5b2RMIKi6ZzPof6/0OlThK6C3xzFK3Bp4
|
||||||
|
PMjTfXw5yyRGVBnZZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA4Ms6LqzMu757z
|
||||||
|
bxISiErRls6fcnq0fpSmiPNHNKM7YwG9KHYwPT6A0UMt30zDwNOXCQBI19caGeeO
|
||||||
|
MLPWa7Gcqm2XZB2jQwvLRPeFSy9fm6RzJFeyhrh/uFEwUetwYmi/cqeIFDRDBQKn
|
||||||
|
bOaXkBk0AaSmI5nRYfuqpMMjaKOFIFcoADw4l9wWhv6DmnrqANzIdsvoSXi5m8RL
|
||||||
|
FcZQDZyHFlHh3P3tLkmQ7ErM2/JDwWWPEEJMlDm/q47FTOQSXZksTI3WRqbbKVv3
|
||||||
|
iQlJjpgi9yAuxZwoM3M4975iWH4LCZVMCSqmKCBt1h9wv4LxqX/3kfZhRdy1gG+j
|
||||||
|
41NOSwJ/
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC72g5wWU26RRwt
|
||||||
|
8Wt+QRpSKKVwcuafk6iRBuP3kIZ3Z6FAIJXCuS6dgsGKy2FFpqdwUVv/Iz33wHQh
|
||||||
|
SwpOyguLkMA3f9KRKboaJAgY7TkTh4E+WPAnrVBAzGHz7VPBD0dO6fVkTWWFU3Vp
|
||||||
|
GYj1WxvJNAuNeH04U0tIkQpvb2ZeyDNjBjA06+j6J3NenLhszI2gbFhj6zYUgp6E
|
||||||
|
0ccK/itq8Ofyt/oO0M6/I3h3T9DI3Cz0WkBkxeYvC0En17E6ooVbjfD3s3GB02JM
|
||||||
|
XK22tMpOLRv72/cZV1wRnDlvZEwgqLpnM+h/r/Q6VOEroLfHMUrcGng8yNN9fDnL
|
||||||
|
JEZUGdlnAgMBAAECggEALlZdlW0R9U6y4spYf65Dddy84n4VUWu0+wE+HoUyBiYz
|
||||||
|
6oOfLYdMbmIgp8H/XpT7XINVNBxXXtPEUaoXAtRoAKdWItqO8Gvgki4tKSjrGVwl
|
||||||
|
j2GU69SepT1FNExoiojgSCEB/RnyXu71WVWJKSyuL/V8nAsKqGgze9T7Q/2wvNQt
|
||||||
|
SQqLxZlrWF0P8WqaAiSrHV4GnDrdeF+k1KBo2+pSaDNv6cNwOyVG8EII9tqhF8kj
|
||||||
|
6nD6846ish6OqmlSisaSGopJZL1DCQzszFMxKd2+iBDY7Kn6hVIhRaNnaZUFhpKM
|
||||||
|
dNh6hBqOycMepAp0sz5pdo+fxpifkoR/cPWgyC3QkQKBgQDixe9VsiZ7u2joxF/9
|
||||||
|
JcAExKhqE28OUmIwt6/j+uzYShxN6Oo9FUo3ICtAPCCFsjhvb3Qum7FspmxrqtNy
|
||||||
|
fzclibZJPO8ey2PzqaiOfiVfgJmNSvoCOdgM4OqFLtRO6eSTzhJeI4VPrPcq/5la
|
||||||
|
0FuOi1WZs/Au9llqLqGSDH3UAwKBgQDUD/bSJbOk5SvNjFtFm0ClVJr66mJ5e4uN
|
||||||
|
4VGv8KGFAJ+ExIxujAukfKdwLjS1wEy2RePcshfT8Y9FVh/Q1KzzrQi3Gwmfq1G6
|
||||||
|
Dpu2HlJpaZl+9T81x2KS8GP3QNczWMe2nh7Lj+6st+b4F+6FYbVTFnHaae27sXrD
|
||||||
|
XPX15+uxzQKBgGy+pBWBF4kwBo/QU4NuTdU7hNNRPGkuwl1ASH1Xv6m8aDRII8Nk
|
||||||
|
6TDkITltW98g5oUxehI7oOpMKCO9SCZYsNY0YpBeQwCOYgDfc6/Y+A0C+x9RO/BD
|
||||||
|
UsJiPLPfD/pDmNPz9sTj3bKma+RXq29sCOujD0pkiiHLCnerotkJWnGHAoGAAkCJ
|
||||||
|
JoIv/jhQ1sX+0iZr8VWMr819bjzZppAWBgBQNtFi4E4WD7Z9CSopvQ9AkA2SwvzL
|
||||||
|
BrT9e8q88sePXvBjRdM4nHk1CPUQ0SEGllCMH4J3ltmT6kZLzbOv3BhcMLdop4/W
|
||||||
|
U+MbbcomMcxPRCtdeZxraR5m3+9qlliOZCYqYqECgYA5eLdxgyHxCS33QGFHRvXI
|
||||||
|
TLAHIrr7wK1xwgkmZlLzYSQ8Oqh1UEbgoMt4ulRczP2g7TCfvANw2Sw0H2Q5a6Fj
|
||||||
|
cnwVcXJ38DLg0GCPMwzE8dK7d8tKtV6kGiKy+KFvoKChPjE6uxhKKmCJaSwtQEPS
|
||||||
|
vsjX3iiIgUQPsSz8RrNFfQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #8
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DNu
|
||||||
|
CKhhl6wCbgoCkFemwJh3ATbAjhInHpvQWIFDfSK1USElCKxqosIxiBQCx3Zs2d/U
|
||||||
|
GeIA7QAM2atNdXaateacEaKMmGE9LEtO0Dg5lmT43WzmGkG9NmCwK3JjAekc5S9d
|
||||||
|
HKNtEQo7o8RKfj81zlDSq2kzliy98cimk24VBBGkS2Cn7Vy/mxMCqWjQazTXbpoS
|
||||||
|
lXw6LiY5wFXQmXOB5GTSHvqyCtBQbOSSbJB77z/fm7bufTDObufTbJIq53WPt00Y
|
||||||
|
f+JNnzkX1X0MaBCUztoZwoMaExWucMe/7xsQ46hDn6KB4b0lZk+gsK45QHxvPE1R
|
||||||
|
72+ZkkIrGS/ljIKahQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDib1653CneSmy2
|
||||||
|
gYzGeMlrI05Jqo3JuHNMQHzAjIrb4ee57VA4PTQa1ygGol/hVv6eTvZr3p2ospDS
|
||||||
|
5Kfwj1HLO4jSMX1Bnm1FG0naQogz2CD3xfYjbYOVRhAxpld1MNyRveIOhDRARY7N
|
||||||
|
XNAaNPZ1ALrwbENSYArr18xDzgGWe/dgyRCEpCFIsztiA+7jGvrmAZgceIE8K3h3
|
||||||
|
fkvNmXBH58ZHAGTiyRriBZqS+DXrBrQOztXSJwFnOZnRt6/efeBupt8j5hxVpBLW
|
||||||
|
vtjpBc23uUcbbHOY2AW2Bf+vIr4/LmJ/MheKV+maa2990vmC93tvWlFfc74mgUkW
|
||||||
|
HJfXDmR6
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDkM24IqGGXrAJu
|
||||||
|
CgKQV6bAmHcBNsCOEicem9BYgUN9IrVRISUIrGqiwjGIFALHdmzZ39QZ4gDtAAzZ
|
||||||
|
q011dpq15pwRooyYYT0sS07QODmWZPjdbOYaQb02YLArcmMB6RzlL10co20RCjuj
|
||||||
|
xEp+PzXOUNKraTOWLL3xyKaTbhUEEaRLYKftXL+bEwKpaNBrNNdumhKVfDouJjnA
|
||||||
|
VdCZc4HkZNIe+rIK0FBs5JJskHvvP9+btu59MM5u59NskirndY+3TRh/4k2fORfV
|
||||||
|
fQxoEJTO2hnCgxoTFa5wx7/vGxDjqEOfooHhvSVmT6CwrjlAfG88TVHvb5mSQisZ
|
||||||
|
L+WMgpqFAgMBAAECggEABTdPuo7uvCLIY2+DI319aEWT4sk3mYe8sSxqlLtPqZqT
|
||||||
|
fmk9iXc3cMTzkOK0NY71af19waGy17f6kzchLCAr5SCCTLzkbc87MLn/8S530oI4
|
||||||
|
VgdZMxxxkL6hCD0zGiYT7QEqJa9unMcZGeMwuLYFKtQaHKTo8vPO26n0dMY9YLxj
|
||||||
|
cNUxsKLcKk8dbfKKt4B4fZgB7nU0BG9YbKYZ3iZ7/3mG+6jA6u+VYc/WHYQjTmpL
|
||||||
|
oLFN7NOe3R7jIx/kJ1OqNWqsFoLpyiiWd1Mr0l3EdD1kCudptMgD8hd++nx2Yk2w
|
||||||
|
K4+CpOVIN/eCxDDaAOJgYjCtOayVwUkDAxRRt9VnAQKBgQD5s1j6RJtBNTlChVxS
|
||||||
|
W3WpcG4q8933AiUY/Chx0YTyopOiTi7AGUaA8AOLFBcO2npa+vzC+lvuOyrgOtVW
|
||||||
|
sD10H2v5jNKlbeBp+Q9rux2LAyp4TvzdXWKhVyZrdtITF0hn6vEYNp7MtyWRFb1O
|
||||||
|
3Ie5HQBPHtzllFOMynacjOdjpQKBgQDp9TrbfOmwGWmwPKmaFKuy8BKxjJM+ct0X
|
||||||
|
4Xs1uSy9Z9Y8QlDNbNaooI8DA1NY0jDVHwemiGC4bYsBNKNRcbI0s2nr0hQMft42
|
||||||
|
P/NpugHv0YXiVz+5bfim4woTiHHbfREqchlIGo3ryClAiDU9fYZwTOtb9jPIhX3G
|
||||||
|
9v+OsoMlYQKBgQDJUQW90S5zJlwh+69xXvfAQjswOimNCpeqSzK4gTn0/YqV4v7i
|
||||||
|
Nf6X2eqhaPMmMJNRYuYCtSMFMYLiAc0a9UC2rNa6/gSfB7VU+06phtTMzSKimNxa
|
||||||
|
BP6OIduB7Ox2I+Fmlw8GfJMPbeHF1YcpW7e5UV58a9+g4TNzYZC7qwarWQKBgQCA
|
||||||
|
FFaCbmHonCD18F/REFvm+/Lf7Ft3pp5PQouXH6bUkhIArzVZIKpramqgdaOdToSZ
|
||||||
|
SAGCM8rvbFja8hwurBWpMEdeaIW9SX8RJ/Vz/fateYDYJnemZgPoKQcNJnded5t8
|
||||||
|
Jzab+J2VZODgiTDMVvnQZOu8To6OyjXPRM0nK6cMQQKBgQDyX44PHRRhEXDgJFLU
|
||||||
|
qp2ODL54Qadc/thp2m+JmAvqmCCLwuYlGpRKVkLLuZW9W6RlVqarOC3VD3wX5PRZ
|
||||||
|
IsyCGLi+Jbrv9JIrYUXE80xNeQVNhrrf02OW0KHbqGxRaNOmp1THPw98VUGR2J/q
|
||||||
|
YAp6XUXU7LEBUrowye+Ty2o7Lg==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #9
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k2R
|
||||||
|
PWYihftppo3CoxeseFwgg7guxZVkP7aAur5uBzSeAB7sBG1G2bRrwMX71S4xPwot
|
||||||
|
zYiEoxUrTStUqEKjL2aozfHsXnHZ7kwwUgZFDZUg+ve2tZDA3HCUr4tLYKlyFqpx
|
||||||
|
2nCouc45MjQ4wAxRl4rQxIUG2uSTzvP+xXtjoJYMIEEyCpcsRXfqfVkEUe9nrPsF
|
||||||
|
0Ibzk7Cyt75HDI4uEzBuHux0DYuGy6R02jz/vf/dIZ4WepjSY06xpblTHZgieDRX
|
||||||
|
fU2+YOcvb0eDHyA8Q5p8ropK71MNIP5+kffFd90SVr4EkCA8S+cd6FdKQasRr+jF
|
||||||
|
9MUhMS4ObvlrYTG+hwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCy62MZ3+59/VpX
|
||||||
|
c9Hsmb4/BMWt0irLJit4w4SkuYKGFLCMKZI4LN4pEkXaiE1eqF2DNS1qOvl5luty
|
||||||
|
Zz4oggrqilwuFeH98o9Zeg9SYnouuypORVP/3DPbJF/jiQg5J8kJb1sy+BjRiT8I
|
||||||
|
5X6/cCBYT+MljFz5tpqWOtWTgA30e1BV8JFj8F4dgUcWsAVT/I4l9zgMLUnhcO6E
|
||||||
|
wLtEE0I6aT1RHJB28ndwJzj4La98Oirw7LAEAWbExWYB90ypLaGY+JVJe3f5fijC
|
||||||
|
fJpQ2mbs4syXDmb5bU2C2pGPTKZPcyx15iQrq1uHInD0facOw+pmllAFxuG96lA1
|
||||||
|
+o2VzKwP
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWTZE9ZiKF+2mm
|
||||||
|
jcKjF6x4XCCDuC7FlWQ/toC6vm4HNJ4AHuwEbUbZtGvAxfvVLjE/Ci3NiISjFStN
|
||||||
|
K1SoQqMvZqjN8execdnuTDBSBkUNlSD697a1kMDccJSvi0tgqXIWqnHacKi5zjky
|
||||||
|
NDjADFGXitDEhQba5JPO8/7Fe2OglgwgQTIKlyxFd+p9WQRR72es+wXQhvOTsLK3
|
||||||
|
vkcMji4TMG4e7HQNi4bLpHTaPP+9/90hnhZ6mNJjTrGluVMdmCJ4NFd9Tb5g5y9v
|
||||||
|
R4MfIDxDmnyuikrvUw0g/n6R98V33RJWvgSQIDxL5x3oV0pBqxGv6MX0xSExLg5u
|
||||||
|
+WthMb6HAgMBAAECggEAeCyRSNwQeg/NZD/UqP6qkegft52+ZMBssinWsGH/c3z3
|
||||||
|
KVwtwCHDfGvnjPe5TAeWSCKeIsbukkFZwfGNjLmppvgrqymCAkhYDICfDDBF4uMA
|
||||||
|
1pu40sJ01Gkxh+tV/sOmnb1BEVzh0Sgq/NM6C8ActR18CugKOw+5L3G2KeoSqUbT
|
||||||
|
2hcPUsnik10KwqW737GQW4LtEQEr/iRmQkxI3+HBzvPWjFZzjOcpUph+FW5TXtaU
|
||||||
|
T26mt1j+FjbdvvhCuRMY/VZBJ5h1RKU95r57F1AjW/C0RRJ8FxR1CeSy4IlmQBrh
|
||||||
|
6wAa3Tdm0k/n4ZspC9bF5eVTJEtb0AohiYZrIa8MuQKBgQD8yjCLYa41H304odCx
|
||||||
|
NwPRJcmlIk5YGxPrhHAT9GEgU6n/no7YMVx1L7fNLcMjAyx54jauEU7J19Aki7eV
|
||||||
|
SIdU9TwqmkOAFfM6TOEJZiOi66gABOxeK2yDyfmR6Apaw3caku4O058t4KVwHSCB
|
||||||
|
DanYCMzxCBqS9jUTTyAh0fMg6wKBgQDZBkIukg3FKPor5LzkUXIKnNHYPfHbERHw
|
||||||
|
piWS6GZwqhuWNlOCWxiBR4rEUU/RbFQZw/FCi5OuAk2lBC0LBmC0/Sz4/+xDdCbv
|
||||||
|
uNhMOTRcy9nFVpmpIWCx4N/KmXHEuFxli/JNXux7iki74AVC9VPrAt/kCvwf06Df
|
||||||
|
oDb8ljdR1QKBgQChVOD6c5Lc8IXYeN1Z3IShHH6+11AsxstFyjZFZff+y6Z5L1Z2
|
||||||
|
/7nESHoDhqs9Uy81cnv3R7CC/Ssnx8uYiLtmK0UE44Mk4d1jXeFZQEiKF+AWcw3v
|
||||||
|
Y8NTsLmItxC0sH75BMDN0Z2LiA3Nqaku8+trpuI1Cjj7hgqFkkAtlXKXlQKBgBMb
|
||||||
|
c/Q5s7CqHOyEZQUNDqdUiz0opwSMijHPzvsSLwK4V1lwSwXtE0k+jT8fkZF0oirq
|
||||||
|
j3E2bLgjR8bBiV2xIA6PQ8hgb+K4dT0h3xlG6A9Le07egwTbBXJjxBBIVjXlrWzb
|
||||||
|
V2fsdZGi6ShxXsU4aD0GscOYG/6JWV6W8oBmkVRJAoGAepIZ+OYmFjb7uxdh4EtP
|
||||||
|
hluEtx5bLOLuo6c0S149omUXUhbsuyzTZS6Ip9ySDMnK3954c4Q4WJ4yQKixQNVq
|
||||||
|
78aDfy4hP/8TE/Q9CRddUof2P33PJMhVNqzTRYMpqV+zxifvtw3hoDTLKHTQxCR2
|
||||||
|
M1+O4VvokU5pBqUpGXiMDfs=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #10
|
||||||
|
"""-----BEGIN CERTIFICATE-----
|
||||||
|
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
||||||
|
bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM
|
||||||
|
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnbCU
|
||||||
|
M37hG7zrCyyJEI6pZmOomnI+CozbP5KAhWSV5y7R5H6lcAEG2UDV+lCUxHT2ufOa
|
||||||
|
i1H16bXyBt7VoMTHIH50S58NUCUEXcuRWVR16tr8CzcTHQAkfIrmhY2XffPilX7h
|
||||||
|
aw35UkoVmXcqSDNNJD6jmvWexvmbhzVWW8Vt5Pivet2/leVuqPXB54/alSbkC74m
|
||||||
|
x6X5XKQc6eyPsb1xvNBuiSpFzdqbEn7lUwj6jFTkh9tlixgmgx+J0XoQXbawyrAg
|
||||||
|
rcIQcse/Ww+KBA1KSccFze+XBTbIull4boYhbJqkb6DW5bY7/me2nNxE9DRGwq+S
|
||||||
|
kBsKq3YKeCf8LEhfqQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAD+tWGFhINYsWT
|
||||||
|
ibKWlCGgBc5uB7611cLCevx1yAL6SaOECVCQXzaaXIaETSbyY03UO2yBy3Pl10FV
|
||||||
|
GYXLrAWTFZsNVJm55XIibTNw1UBPNwdIoCSzAYuOgMF0GHhTTQU0hNYWstOnnE2T
|
||||||
|
6lSAZQZFkaW4ZKs6sUp42Em9Bu99PehyIgnw14qb9NPg5qKdi2GAvkImZCrGpMdK
|
||||||
|
OF31U7Ob0XQ0lxykcNgG4LlUACd+QxLfNpmLBZUGfikexYa1VqBFm3oAvTt8ybNQ
|
||||||
|
qr7AKXDFnW75aCBaMpQWzrstA7yYZ3D9XCd5ZNf6d08lGM/oerDAIGnZOZPJgs5U
|
||||||
|
FaWPHdS9
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdsJQzfuEbvOsL
|
||||||
|
LIkQjqlmY6iacj4KjNs/koCFZJXnLtHkfqVwAQbZQNX6UJTEdPa585qLUfXptfIG
|
||||||
|
3tWgxMcgfnRLnw1QJQRdy5FZVHXq2vwLNxMdACR8iuaFjZd98+KVfuFrDflSShWZ
|
||||||
|
dypIM00kPqOa9Z7G+ZuHNVZbxW3k+K963b+V5W6o9cHnj9qVJuQLvibHpflcpBzp
|
||||||
|
7I+xvXG80G6JKkXN2psSfuVTCPqMVOSH22WLGCaDH4nRehBdtrDKsCCtwhByx79b
|
||||||
|
D4oEDUpJxwXN75cFNsi6WXhuhiFsmqRvoNbltjv+Z7ac3ET0NEbCr5KQGwqrdgp4
|
||||||
|
J/wsSF+pAgMBAAECggEAPSu1ofBTRN5ZU4FYPlsJLdX1Hsy4coFHv/aF8rkdSYwp
|
||||||
|
EflrFfLgBEEZgLvnqfoxh9sPFYKa4amaFL42ouIS2PEVDgzKLk/dzMDeRof0IkIG
|
||||||
|
yhb4TCS1ArcjS6WsociNGi8ZJN1L3Xctv9WxSkbUYv4Fm2Qyzr8fbSjssjb5NXwD
|
||||||
|
K11fsj6Pfy/mQrI0TSTlzWC7ARIlCMTWQ8G8zEU6bMFIG6DMjt2J4VgYVXUKetZA
|
||||||
|
VPuS+pwkH2obQe6FLRuiNxH4GitVAASYPea6foER4AggBMRp8q8F6+WssjoyEORb
|
||||||
|
0sJxmnxhznoTRMCuTsUj6XMgmOTOnA3lQXsIB0DYcQKBgQDO6mMRVVFWzgkE9Q5/
|
||||||
|
36n06KvGYF9TCRDL9vRC8kCqcGd1Hy6jRj0D8049KUHaN74pfWg6gsQjPkKzwKnC
|
||||||
|
vxNl72tVvLqm7Fo531BGfKK/46ZvxeWMMraNW4+9LhwMPu2LN5OEdwwCgyaURpxh
|
||||||
|
ktCp+RrGjz08Kn82X1jJPdwxDQKBgQDDGMvZ7ZUDGq5+RJkmHJ58lQtiaMZclmYV
|
||||||
|
R9YwOxJV6ino3EYrGOtUkqiemgAACdMWE/JMJlB1/JINawJwUsZ2XDp/9jNLPgLc
|
||||||
|
gphCmagaO34U/YMaJbJIK2gkCX7p8EcD+x45qWa0bEMPW38QfN/qQdUPjNmpuIiI
|
||||||
|
Zleyl1TqDQKBgQCvIoat0ighsAzETGN0aqzhJdrW8xVcJA06hpFi5MdFPBTldno0
|
||||||
|
KqxUXqj3badWe94SIhqJg8teBUHSAZ3uv2o82nRgQnk99km8OD8rGi1q+9YRP1C2
|
||||||
|
5OnNJhW4y4FkABNxxZ2v/k+FBNsvn8CXefvyEm3OaMks1s+MBxIQa7KnNQKBgFwX
|
||||||
|
HUo+GiN/+bPCf6P8yFa4J8qI+HEF0SPkZ9cWWx5QzP2M1FZNie++1nce7DcYbBo0
|
||||||
|
yh9lyn8W/H328AzDFckS2c5DEY1HtSQPRP3S+AWB5Y7U54h1GMV2L88q6ExWzb60
|
||||||
|
T10aeE9b9v+NydmniC5UatTPQIMbht8Tp/u18TAVAoGBAJphAfiqWWV2M5aBCSXq
|
||||||
|
WxLZ71AJ0PZBmRa/9iwtccwXQpMcW6wHK3YSQxci+sB97TElRa3/onlVSpohrUtg
|
||||||
|
VCvCwfSHX1LmrfWNSkoJZwCQt+YYuMqW86K0tzLzI1EMjIH9LgQvB6RR26PZQs+E
|
||||||
|
jr1ZvRc+wPTq6sxCF1h9ZAfN
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
""", #11
|
||||||
|
]
|
||||||
|
|
||||||
|
# To disable the pre-computed tub certs, uncomment this line.
|
||||||
|
# SYSTEM_TEST_CERTS = []
|
||||||
|
|
||||||
|
|
||||||
|
def flush_but_dont_ignore(res):
|
||||||
|
d = flushEventualQueue()
|
||||||
|
def _done(ignored):
|
||||||
|
return res
|
||||||
|
d.addCallback(_done)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _render_config(config):
|
||||||
|
"""
|
||||||
|
Convert a ``dict`` of ``dict`` of ``unicode`` to an ini-format string.
|
||||||
|
"""
|
||||||
|
return u"\n\n".join(list(
|
||||||
|
_render_config_section(k, v)
|
||||||
|
for (k, v)
|
||||||
|
in config.items()
|
||||||
|
))
|
||||||
|
|
||||||
|
def _render_config_section(heading, values):
|
||||||
|
"""
|
||||||
|
Convert a ``unicode`` heading and a ``dict`` of ``unicode`` to an ini-format
|
||||||
|
section as ``unicode``.
|
||||||
|
"""
|
||||||
|
return u"[{}]\n{}\n".format(
|
||||||
|
heading, _render_section_values(values)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _render_section_values(values):
|
||||||
|
"""
|
||||||
|
Convert a ``dict`` of ``unicode`` to the body of an ini-format section as
|
||||||
|
``unicode``.
|
||||||
|
"""
|
||||||
|
return u"\n".join(list(
|
||||||
|
u"{} = {}".format(k, v)
|
||||||
|
for (k, v)
|
||||||
|
in sorted(values.items())
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.port_assigner = SameProcessStreamEndpointAssigner()
|
||||||
|
self.port_assigner.setUp()
|
||||||
|
self.addCleanup(self.port_assigner.tearDown)
|
||||||
|
|
||||||
|
self.sparent = service.MultiService()
|
||||||
|
self.sparent.startService()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
log.msg("shutting down SystemTest services")
|
||||||
|
d = self.sparent.stopService()
|
||||||
|
d.addBoth(flush_but_dont_ignore)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def getdir(self, subdir):
|
||||||
|
return os.path.join(self.basedir, subdir)
|
||||||
|
|
||||||
|
def add_service(self, s):
|
||||||
|
s.setServiceParent(self.sparent)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _create_introducer(self):
|
||||||
|
"""
|
||||||
|
:returns: (via Deferred) an Introducer instance
|
||||||
|
"""
|
||||||
|
iv_dir = self.getdir("introducer")
|
||||||
|
if not os.path.isdir(iv_dir):
|
||||||
|
_, port_endpoint = self.port_assigner.assign(reactor)
|
||||||
|
introducer_config = (
|
||||||
|
u"[node]\n"
|
||||||
|
u"nickname = introducer \N{BLACK SMILING FACE}\n" +
|
||||||
|
u"web.port = {}\n".format(port_endpoint)
|
||||||
|
).encode("utf-8")
|
||||||
|
|
||||||
|
fileutil.make_dirs(iv_dir)
|
||||||
|
fileutil.write(
|
||||||
|
os.path.join(iv_dir, 'tahoe.cfg'),
|
||||||
|
introducer_config,
|
||||||
|
)
|
||||||
|
if SYSTEM_TEST_CERTS:
|
||||||
|
os.mkdir(os.path.join(iv_dir, "private"))
|
||||||
|
f = open(os.path.join(iv_dir, "private", "node.pem"), "w")
|
||||||
|
f.write(SYSTEM_TEST_CERTS[0])
|
||||||
|
f.close()
|
||||||
|
return create_introducer(basedir=iv_dir)
|
||||||
|
|
||||||
|
def _get_introducer_web(self):
|
||||||
|
with open(os.path.join(self.getdir("introducer"), "node.url"), "r") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def set_up_nodes(self, NUMCLIENTS=5):
|
||||||
|
"""
|
||||||
|
Create an introducer and ``NUMCLIENTS`` client nodes pointed at it. All
|
||||||
|
of the nodes are running in this process.
|
||||||
|
|
||||||
|
As a side-effect, set:
|
||||||
|
|
||||||
|
* ``numclients`` to ``NUMCLIENTS``
|
||||||
|
* ``introducer`` to the ``_IntroducerNode`` instance
|
||||||
|
* ``introweb_url`` to the introducer's HTTP API endpoint.
|
||||||
|
|
||||||
|
:param int NUMCLIENTS: The number of client nodes to create.
|
||||||
|
|
||||||
|
:return: A ``Deferred`` that fires when the nodes have connected to
|
||||||
|
each other.
|
||||||
|
"""
|
||||||
|
self.numclients = NUMCLIENTS
|
||||||
|
|
||||||
|
self.introducer = yield self._create_introducer()
|
||||||
|
self.add_service(self.introducer)
|
||||||
|
self.introweb_url = self._get_introducer_web()
|
||||||
|
yield self._set_up_client_nodes()
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def _set_up_client_nodes(self):
|
||||||
|
q = self.introducer
|
||||||
|
self.introducer_furl = q.introducer_url
|
||||||
|
self.clients = []
|
||||||
|
basedirs = []
|
||||||
|
for i in range(self.numclients):
|
||||||
|
basedirs.append((yield self._set_up_client_node(i)))
|
||||||
|
|
||||||
|
# start clients[0], wait for it's tub to be ready (at which point it
|
||||||
|
# will have registered the helper furl).
|
||||||
|
c = yield client.create_client(basedirs[0])
|
||||||
|
c.setServiceParent(self.sparent)
|
||||||
|
self.clients.append(c)
|
||||||
|
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
||||||
|
|
||||||
|
with open(os.path.join(basedirs[0],"private","helper.furl"), "r") as f:
|
||||||
|
helper_furl = f.read()
|
||||||
|
|
||||||
|
self.helper_furl = helper_furl
|
||||||
|
if self.numclients >= 4:
|
||||||
|
with open(os.path.join(basedirs[3], 'tahoe.cfg'), 'a+') as f:
|
||||||
|
f.write(
|
||||||
|
"[client]\n"
|
||||||
|
"helper.furl = {}\n".format(helper_furl)
|
||||||
|
)
|
||||||
|
|
||||||
|
# this starts the rest of the clients
|
||||||
|
for i in range(1, self.numclients):
|
||||||
|
c = yield client.create_client(basedirs[i])
|
||||||
|
c.setServiceParent(self.sparent)
|
||||||
|
self.clients.append(c)
|
||||||
|
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
||||||
|
log.msg("STARTING")
|
||||||
|
yield self.wait_for_connections()
|
||||||
|
log.msg("CONNECTED")
|
||||||
|
# now find out where the web port was
|
||||||
|
self.webish_url = self.clients[0].getServiceNamed("webish").getURL()
|
||||||
|
if self.numclients >=4:
|
||||||
|
# and the helper-using webport
|
||||||
|
self.helper_webish_url = self.clients[3].getServiceNamed("webish").getURL()
|
||||||
|
|
||||||
|
def _generate_config(self, which, basedir):
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
except1 = set(range(self.numclients)) - {1}
|
||||||
|
feature_matrix = {
|
||||||
|
("client", "nickname"): except1,
|
||||||
|
|
||||||
|
# client 1 has to auto-assign an address.
|
||||||
|
("node", "tub.port"): except1,
|
||||||
|
("node", "tub.location"): except1,
|
||||||
|
|
||||||
|
# client 0 runs a webserver and a helper
|
||||||
|
# client 3 runs a webserver but no helper
|
||||||
|
("node", "web.port"): {0, 3},
|
||||||
|
("node", "timeout.keepalive"): {0},
|
||||||
|
("node", "timeout.disconnect"): {3},
|
||||||
|
|
||||||
|
("helper", "enabled"): {0},
|
||||||
|
}
|
||||||
|
|
||||||
|
def setconf(config, which, section, feature, value):
|
||||||
|
if which in feature_matrix.get((section, feature), {which}):
|
||||||
|
config.setdefault(section, {})[feature] = value
|
||||||
|
|
||||||
|
setnode = partial(setconf, config, which, "node")
|
||||||
|
sethelper = partial(setconf, config, which, "helper")
|
||||||
|
|
||||||
|
setnode("nickname", u"client %d \N{BLACK SMILING FACE}" % (which,))
|
||||||
|
|
||||||
|
tub_location_hint, tub_port_endpoint = self.port_assigner.assign(reactor)
|
||||||
|
setnode("tub.port", tub_port_endpoint)
|
||||||
|
setnode("tub.location", tub_location_hint)
|
||||||
|
|
||||||
|
_, web_port_endpoint = self.port_assigner.assign(reactor)
|
||||||
|
setnode("web.port", web_port_endpoint)
|
||||||
|
setnode("timeout.keepalive", "600")
|
||||||
|
setnode("timeout.disconnect", "1800")
|
||||||
|
|
||||||
|
sethelper("enabled", "True")
|
||||||
|
|
||||||
|
iyaml = ("introducers:\n"
|
||||||
|
" petname2:\n"
|
||||||
|
" furl: %s\n") % self.introducer_furl
|
||||||
|
iyaml_fn = os.path.join(basedir, "private", "introducers.yaml")
|
||||||
|
fileutil.write(iyaml_fn, iyaml)
|
||||||
|
|
||||||
|
return _render_config(config)
|
||||||
|
|
||||||
|
def _set_up_client_node(self, which):
|
||||||
|
basedir = self.getdir("client%d" % (which,))
|
||||||
|
fileutil.make_dirs(os.path.join(basedir, "private"))
|
||||||
|
if len(SYSTEM_TEST_CERTS) > (which + 1):
|
||||||
|
f = open(os.path.join(basedir, "private", "node.pem"), "w")
|
||||||
|
f.write(SYSTEM_TEST_CERTS[which + 1])
|
||||||
|
f.close()
|
||||||
|
config = self._generate_config(which, basedir)
|
||||||
|
fileutil.write(os.path.join(basedir, 'tahoe.cfg'), config)
|
||||||
|
return basedir
|
||||||
|
|
||||||
|
def bounce_client(self, num):
|
||||||
|
c = self.clients[num]
|
||||||
|
d = c.disownServiceParent()
|
||||||
|
# I think windows requires a moment to let the connection really stop
|
||||||
|
# and the port number made available for re-use. TODO: examine the
|
||||||
|
# behavior, see if this is really the problem, see if we can do
|
||||||
|
# better than blindly waiting for a second.
|
||||||
|
d.addCallback(self.stall, 1.0)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _stopped(res):
|
||||||
|
new_c = yield client.create_client(self.getdir("client%d" % num))
|
||||||
|
self.clients[num] = new_c
|
||||||
|
new_c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
||||||
|
new_c.setServiceParent(self.sparent)
|
||||||
|
d.addCallback(_stopped)
|
||||||
|
d.addCallback(lambda res: self.wait_for_connections())
|
||||||
|
def _maybe_get_webport(res):
|
||||||
|
if num == 0:
|
||||||
|
# now find out where the web port was
|
||||||
|
self.webish_url = self.clients[0].getServiceNamed("webish").getURL()
|
||||||
|
d.addCallback(_maybe_get_webport)
|
||||||
|
return d
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def add_extra_node(self, client_num, helper_furl=None,
|
||||||
|
add_to_sparent=False):
|
||||||
|
# usually this node is *not* parented to our self.sparent, so we can
|
||||||
|
# shut it down separately from the rest, to exercise the
|
||||||
|
# connection-lost code
|
||||||
|
basedir = FilePath(self.getdir("client%d" % client_num))
|
||||||
|
basedir.makedirs()
|
||||||
|
config = "[client]\n"
|
||||||
|
if helper_furl:
|
||||||
|
config += "helper.furl = %s\n" % helper_furl
|
||||||
|
basedir.child("tahoe.cfg").setContent(config.encode("utf-8"))
|
||||||
|
private = basedir.child("private")
|
||||||
|
private.makedirs()
|
||||||
|
write_introducer(
|
||||||
|
basedir,
|
||||||
|
"default",
|
||||||
|
self.introducer_furl,
|
||||||
|
)
|
||||||
|
|
||||||
|
c = yield client.create_client(basedir.path)
|
||||||
|
self.clients.append(c)
|
||||||
|
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
||||||
|
self.numclients += 1
|
||||||
|
if add_to_sparent:
|
||||||
|
c.setServiceParent(self.sparent)
|
||||||
|
else:
|
||||||
|
c.startService()
|
||||||
|
yield self.wait_for_connections()
|
||||||
|
defer.returnValue(c)
|
||||||
|
|
||||||
|
def _check_connections(self):
|
||||||
|
for i, c in enumerate(self.clients):
|
||||||
|
if not c.connected_to_introducer():
|
||||||
|
log.msg("%s not connected to introducer yet" % (i,))
|
||||||
|
return False
|
||||||
|
sb = c.get_storage_broker()
|
||||||
|
connected_servers = sb.get_connected_servers()
|
||||||
|
connected_names = sorted(list(
|
||||||
|
connected.get_nickname()
|
||||||
|
for connected
|
||||||
|
in sb.get_known_servers()
|
||||||
|
if connected.is_connected()
|
||||||
|
))
|
||||||
|
if len(connected_servers) != self.numclients:
|
||||||
|
wanted = sorted(list(
|
||||||
|
client.nickname
|
||||||
|
for client
|
||||||
|
in self.clients
|
||||||
|
))
|
||||||
|
log.msg(
|
||||||
|
"client %s storage broker connected to %s, missing %s" % (
|
||||||
|
i,
|
||||||
|
connected_names,
|
||||||
|
set(wanted) - set(connected_names),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
log.msg("client %s storage broker connected to %s, happy" % (
|
||||||
|
i, connected_names,
|
||||||
|
))
|
||||||
|
up = c.getServiceNamed("uploader")
|
||||||
|
if up._helper_furl and not up._helper:
|
||||||
|
log.msg("Helper fURL but no helper")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def wait_for_connections(self, ignored=None):
|
||||||
|
return self.poll(self._check_connections, timeout=200)
|
@ -15,6 +15,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
from functools import (
|
||||||
|
partial,
|
||||||
|
)
|
||||||
from random import randrange
|
from random import randrange
|
||||||
if PY2:
|
if PY2:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
@ -98,7 +101,7 @@ def run_cli_native(verb, *args, **kwargs):
|
|||||||
args=args,
|
args=args,
|
||||||
nodeargs=nodeargs,
|
nodeargs=nodeargs,
|
||||||
)
|
)
|
||||||
argv = nodeargs + [verb] + list(args)
|
argv = ["tahoe"] + nodeargs + [verb] + list(args)
|
||||||
stdin = kwargs.get("stdin", "")
|
stdin = kwargs.get("stdin", "")
|
||||||
if PY2:
|
if PY2:
|
||||||
# The original behavior, the Python 2 behavior, is to accept either
|
# The original behavior, the Python 2 behavior, is to accept either
|
||||||
@ -128,10 +131,20 @@ def run_cli_native(verb, *args, **kwargs):
|
|||||||
stdout = TextIOWrapper(BytesIO(), encoding)
|
stdout = TextIOWrapper(BytesIO(), encoding)
|
||||||
stderr = TextIOWrapper(BytesIO(), encoding)
|
stderr = TextIOWrapper(BytesIO(), encoding)
|
||||||
d = defer.succeed(argv)
|
d = defer.succeed(argv)
|
||||||
d.addCallback(runner.parse_or_exit_with_explanation, stdout=stdout)
|
d.addCallback(
|
||||||
d.addCallback(runner.dispatch,
|
partial(
|
||||||
stdin=stdin,
|
runner.parse_or_exit,
|
||||||
stdout=stdout, stderr=stderr)
|
runner.Options(),
|
||||||
|
),
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr,
|
||||||
|
)
|
||||||
|
d.addCallback(
|
||||||
|
runner.dispatch,
|
||||||
|
stdin=stdin,
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr,
|
||||||
|
)
|
||||||
def _done(rc, stdout=stdout, stderr=stderr):
|
def _done(rc, stdout=stdout, stderr=stderr):
|
||||||
if return_bytes and PY3:
|
if return_bytes and PY3:
|
||||||
stdout = stdout.buffer
|
stdout = stdout.buffer
|
||||||
@ -301,6 +314,16 @@ class FakeCanary(object):
|
|||||||
def getPeer(self):
|
def getPeer(self):
|
||||||
return "<fake>"
|
return "<fake>"
|
||||||
|
|
||||||
|
def disconnected(self):
|
||||||
|
"""Disconnect the canary, to be called by test code.
|
||||||
|
|
||||||
|
Can only happen once.
|
||||||
|
"""
|
||||||
|
if self.disconnectors is not None:
|
||||||
|
for (f, args, kwargs) in list(self.disconnectors.values()):
|
||||||
|
f(*args, **kwargs)
|
||||||
|
self.disconnectors = None
|
||||||
|
|
||||||
|
|
||||||
class ShouldFailMixin(object):
|
class ShouldFailMixin(object):
|
||||||
|
|
||||||
|
@ -96,8 +96,14 @@ class FakeStorage(object):
|
|||||||
shares[shnum] = f.getvalue()
|
shares[shnum] = f.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
# This doesn't actually implement the whole interface, but adding a commented
|
||||||
|
# interface implementation annotation for grepping purposes.
|
||||||
|
#@implementer(RIStorageServer)
|
||||||
class FakeStorageServer(object):
|
class FakeStorageServer(object):
|
||||||
|
"""
|
||||||
|
A fake Foolscap remote object, implemented by overriding callRemote() to
|
||||||
|
call local methods.
|
||||||
|
"""
|
||||||
def __init__(self, peerid, storage):
|
def __init__(self, peerid, storage):
|
||||||
self.peerid = peerid
|
self.peerid = peerid
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
@ -143,7 +149,7 @@ class FakeStorageServer(object):
|
|||||||
readv = {}
|
readv = {}
|
||||||
for shnum, (testv, writev, new_length) in list(tw_vectors.items()):
|
for shnum, (testv, writev, new_length) in list(tw_vectors.items()):
|
||||||
for (offset, length, op, specimen) in testv:
|
for (offset, length, op, specimen) in testv:
|
||||||
assert op in (b"le", b"eq", b"ge")
|
assert op == b"eq"
|
||||||
# TODO: this isn't right, the read is controlled by read_vector,
|
# TODO: this isn't right, the read is controlled by read_vector,
|
||||||
# not by testv
|
# not by testv
|
||||||
readv[shnum] = [ specimen
|
readv[shnum] = [ specimen
|
||||||
|
870
src/allmydata/test/test_istorageserver.py
Normal file
870
src/allmydata/test/test_istorageserver.py
Normal file
@ -0,0 +1,870 @@
|
|||||||
|
"""
|
||||||
|
Tests for the ``IStorageServer`` interface.
|
||||||
|
|
||||||
|
Note that for performance, in the future we might want the same node to be
|
||||||
|
reused across tests, so each test should be careful to generate unique storage
|
||||||
|
indexes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from future.utils import PY2, bchr
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
# fmt: off
|
||||||
|
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
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
from random import Random
|
||||||
|
|
||||||
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||||
|
|
||||||
|
from foolscap.api import Referenceable, RemoteException
|
||||||
|
|
||||||
|
from allmydata.interfaces import IStorageServer
|
||||||
|
from .common_system import SystemTestMixin
|
||||||
|
from .common import AsyncTestCase
|
||||||
|
|
||||||
|
|
||||||
|
# Use random generator with known seed, so results are reproducible if tests
|
||||||
|
# are run in the same order.
|
||||||
|
_RANDOM = Random(0)
|
||||||
|
|
||||||
|
|
||||||
|
def _randbytes(length):
|
||||||
|
# type: (int) -> bytes
|
||||||
|
"""Return random bytes string of given length."""
|
||||||
|
return b"".join([bchr(_RANDOM.randrange(0, 256)) for _ in range(length)])
|
||||||
|
|
||||||
|
|
||||||
|
def new_storage_index():
|
||||||
|
# type: () -> bytes
|
||||||
|
"""Return a new random storage index."""
|
||||||
|
return _randbytes(16)
|
||||||
|
|
||||||
|
|
||||||
|
def new_secret():
|
||||||
|
# type: () -> bytes
|
||||||
|
"""Return a new random secret (for lease renewal or cancellation)."""
|
||||||
|
return _randbytes(32)
|
||||||
|
|
||||||
|
|
||||||
|
class IStorageServerSharedAPIsTestsMixin(object):
|
||||||
|
"""
|
||||||
|
Tests for ``IStorageServer``'s shared APIs.
|
||||||
|
|
||||||
|
``self.storage_server`` is expected to provide ``IStorageServer``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_version(self):
|
||||||
|
"""
|
||||||
|
``IStorageServer`` returns a dictionary where the key is an expected
|
||||||
|
protocol version.
|
||||||
|
"""
|
||||||
|
result = yield self.storage_server.get_version()
|
||||||
|
self.assertIsInstance(result, dict)
|
||||||
|
self.assertIn(b"http://allmydata.org/tahoe/protocols/storage/v1", result)
|
||||||
|
|
||||||
|
|
||||||
|
class IStorageServerImmutableAPIsTestsMixin(object):
|
||||||
|
"""
|
||||||
|
Tests for ``IStorageServer``'s immutable APIs.
|
||||||
|
|
||||||
|
``self.storage_server`` is expected to provide ``IStorageServer``.
|
||||||
|
|
||||||
|
``self.disconnect()`` should disconnect and then reconnect, creating a new
|
||||||
|
``self.storage_server``. Some implementations may wish to skip tests using
|
||||||
|
this; HTTP has no notion of disconnection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_allocate_buckets_new(self):
|
||||||
|
"""
|
||||||
|
allocate_buckets() with a new storage index returns the matching
|
||||||
|
shares.
|
||||||
|
"""
|
||||||
|
(already_got, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
new_storage_index(),
|
||||||
|
renew_secret=new_secret(),
|
||||||
|
cancel_secret=new_secret(),
|
||||||
|
sharenums=set(range(5)),
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
self.assertEqual(already_got, set())
|
||||||
|
self.assertEqual(set(allocated.keys()), set(range(5)))
|
||||||
|
# We validate the bucket objects' interface in a later test.
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_allocate_buckets_repeat(self):
|
||||||
|
"""
|
||||||
|
``IStorageServer.allocate_buckets()`` with the same storage index does not return
|
||||||
|
work-in-progress buckets, but will add any newly added buckets.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(already_got, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums=set(range(4)),
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
(already_got2, allocated2) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
set(range(5)),
|
||||||
|
1024,
|
||||||
|
Referenceable(),
|
||||||
|
)
|
||||||
|
self.assertEqual(already_got, already_got2)
|
||||||
|
self.assertEqual(set(allocated2.keys()), {4})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def abort_or_disconnect_half_way(self, abort_or_disconnect):
|
||||||
|
"""
|
||||||
|
If we disconnect/abort in the middle of writing to a bucket, all data
|
||||||
|
is wiped, and it's even possible to write different data to the bucket.
|
||||||
|
|
||||||
|
(In the real world one shouldn't do that, but writing different data is
|
||||||
|
a good way to test that the original data really was wiped.)
|
||||||
|
|
||||||
|
``abort_or_disconnect`` is a callback that takes a bucket and aborts up
|
||||||
|
load, or perhaps disconnects the whole connection.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums={0},
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bucket 1 is fully written in one go.
|
||||||
|
yield allocated[0].callRemote("write", 0, b"1" * 1024)
|
||||||
|
|
||||||
|
# Disconnect or abort, depending on the test:
|
||||||
|
yield abort_or_disconnect(allocated[0])
|
||||||
|
|
||||||
|
# Write different data with no complaint:
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums={0},
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
yield allocated[0].callRemote("write", 0, b"2" * 1024)
|
||||||
|
|
||||||
|
def test_disconnection(self):
|
||||||
|
"""
|
||||||
|
If we disconnect in the middle of writing to a bucket, all data is
|
||||||
|
wiped, and it's even possible to write different data to the bucket.
|
||||||
|
|
||||||
|
(In the real world one shouldn't do that, but writing different data is
|
||||||
|
a good way to test that the original data really was wiped.)
|
||||||
|
|
||||||
|
HTTP protocol should skip this test, since disconnection is meaningless
|
||||||
|
concept; this is more about testing implicit contract the Foolscap
|
||||||
|
implementation depends on doesn't change as we refactor things.
|
||||||
|
"""
|
||||||
|
return self.abort_or_disconnect_half_way(lambda _: self.disconnect())
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_written_shares_are_allocated(self):
|
||||||
|
"""
|
||||||
|
Shares that are fully written to show up as allocated in result from
|
||||||
|
``IStorageServer.allocate_buckets()``. Partially-written or empty
|
||||||
|
shares don't.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums=set(range(5)),
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bucket 1 is fully written in one go.
|
||||||
|
yield allocated[1].callRemote("write", 0, b"1" * 1024)
|
||||||
|
yield allocated[1].callRemote("close")
|
||||||
|
|
||||||
|
# Bucket 2 is fully written in two steps.
|
||||||
|
yield allocated[2].callRemote("write", 0, b"1" * 512)
|
||||||
|
yield allocated[2].callRemote("write", 512, b"2" * 512)
|
||||||
|
yield allocated[2].callRemote("close")
|
||||||
|
|
||||||
|
# Bucket 0 has partial write.
|
||||||
|
yield allocated[0].callRemote("write", 0, b"1" * 512)
|
||||||
|
|
||||||
|
(already_got, _) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums=set(range(5)),
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
self.assertEqual(already_got, {1, 2})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_written_shares_are_readable(self):
|
||||||
|
"""
|
||||||
|
Shares that are fully written to can be read.
|
||||||
|
|
||||||
|
The result is not affected by the order in which writes
|
||||||
|
happened, only by their offsets.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums=set(range(5)),
|
||||||
|
allocated_size=1024,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bucket 1 is fully written in order
|
||||||
|
yield allocated[1].callRemote("write", 0, b"1" * 512)
|
||||||
|
yield allocated[1].callRemote("write", 512, b"2" * 512)
|
||||||
|
yield allocated[1].callRemote("close")
|
||||||
|
|
||||||
|
# Bucket 2 is fully written in reverse.
|
||||||
|
yield allocated[2].callRemote("write", 512, b"4" * 512)
|
||||||
|
yield allocated[2].callRemote("write", 0, b"3" * 512)
|
||||||
|
yield allocated[2].callRemote("close")
|
||||||
|
|
||||||
|
buckets = yield self.storage_server.get_buckets(storage_index)
|
||||||
|
self.assertEqual(set(buckets.keys()), {1, 2})
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
(yield buckets[1].callRemote("read", 0, 1024)), b"1" * 512 + b"2" * 512
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(yield buckets[2].callRemote("read", 0, 1024)), b"3" * 512 + b"4" * 512
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_non_matching_overlapping_writes(self):
|
||||||
|
"""
|
||||||
|
When doing overlapping writes in immutable uploads, non-matching writes
|
||||||
|
fail.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums={0},
|
||||||
|
allocated_size=30,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
yield allocated[0].callRemote("write", 0, b"1" * 25)
|
||||||
|
# Overlapping write that doesn't match:
|
||||||
|
with self.assertRaises(RemoteException):
|
||||||
|
yield allocated[0].callRemote("write", 20, b"2" * 10)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_matching_overlapping_writes(self):
|
||||||
|
"""
|
||||||
|
When doing overlapping writes in immutable uploads, matching writes
|
||||||
|
succeed.
|
||||||
|
"""
|
||||||
|
storage_index, renew_secret, cancel_secret = (
|
||||||
|
new_storage_index(),
|
||||||
|
new_secret(),
|
||||||
|
new_secret(),
|
||||||
|
)
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret,
|
||||||
|
cancel_secret,
|
||||||
|
sharenums={0},
|
||||||
|
allocated_size=25,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
yield allocated[0].callRemote("write", 0, b"1" * 10)
|
||||||
|
# Overlapping write that matches:
|
||||||
|
yield allocated[0].callRemote("write", 5, b"1" * 20)
|
||||||
|
yield allocated[0].callRemote("close")
|
||||||
|
|
||||||
|
buckets = yield self.storage_server.get_buckets(storage_index)
|
||||||
|
self.assertEqual(set(buckets.keys()), {0})
|
||||||
|
|
||||||
|
self.assertEqual((yield buckets[0].callRemote("read", 0, 25)), b"1" * 25)
|
||||||
|
|
||||||
|
def test_abort(self):
|
||||||
|
"""
|
||||||
|
If we call ``abort`` on the ``RIBucketWriter`` to disconnect in the
|
||||||
|
middle of writing to a bucket, all data is wiped, and it's even
|
||||||
|
possible to write different data to the bucket.
|
||||||
|
|
||||||
|
(In the real world one probably wouldn't do that, but writing different
|
||||||
|
data is a good way to test that the original data really was wiped.)
|
||||||
|
"""
|
||||||
|
return self.abort_or_disconnect_half_way(
|
||||||
|
lambda bucket: bucket.callRemote("abort")
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_get_buckets_skips_unfinished_buckets(self):
|
||||||
|
"""
|
||||||
|
Buckets that are not fully written are not returned by
|
||||||
|
``IStorageServer.get_buckets()`` implementations.
|
||||||
|
"""
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret=new_secret(),
|
||||||
|
cancel_secret=new_secret(),
|
||||||
|
sharenums=set(range(5)),
|
||||||
|
allocated_size=10,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bucket 1 is fully written
|
||||||
|
yield allocated[1].callRemote("write", 0, b"1" * 10)
|
||||||
|
yield allocated[1].callRemote("close")
|
||||||
|
|
||||||
|
# Bucket 2 is partially written
|
||||||
|
yield allocated[2].callRemote("write", 0, b"1" * 5)
|
||||||
|
|
||||||
|
buckets = yield self.storage_server.get_buckets(storage_index)
|
||||||
|
self.assertEqual(set(buckets.keys()), {1})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_read_bucket_at_offset(self):
|
||||||
|
"""
|
||||||
|
Given a read bucket returned from ``IStorageServer.get_buckets()``, it
|
||||||
|
is possible to read at different offsets and lengths, with reads past
|
||||||
|
the end resulting in empty bytes.
|
||||||
|
"""
|
||||||
|
length = 256 * 17
|
||||||
|
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret=new_secret(),
|
||||||
|
cancel_secret=new_secret(),
|
||||||
|
sharenums=set(range(1)),
|
||||||
|
allocated_size=length,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
total_data = _randbytes(256 * 17)
|
||||||
|
yield allocated[0].callRemote("write", 0, total_data)
|
||||||
|
yield allocated[0].callRemote("close")
|
||||||
|
|
||||||
|
buckets = yield self.storage_server.get_buckets(storage_index)
|
||||||
|
bucket = buckets[0]
|
||||||
|
for start, to_read in [
|
||||||
|
(0, 250), # fraction
|
||||||
|
(0, length), # whole thing
|
||||||
|
(100, 1024), # offset fraction
|
||||||
|
(length + 1, 100), # completely out of bounds
|
||||||
|
(length - 100, 200), # partially out of bounds
|
||||||
|
]:
|
||||||
|
data = yield bucket.callRemote("read", start, to_read)
|
||||||
|
self.assertEqual(
|
||||||
|
data,
|
||||||
|
total_data[start : start + to_read],
|
||||||
|
"Didn't match for start {}, length {}".format(start, to_read),
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def create_share(self):
|
||||||
|
"""Create a share, return the storage index."""
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(_, allocated) = yield self.storage_server.allocate_buckets(
|
||||||
|
storage_index,
|
||||||
|
renew_secret=new_secret(),
|
||||||
|
cancel_secret=new_secret(),
|
||||||
|
sharenums=set(range(1)),
|
||||||
|
allocated_size=10,
|
||||||
|
canary=Referenceable(),
|
||||||
|
)
|
||||||
|
|
||||||
|
yield allocated[0].callRemote("write", 0, b"0123456789")
|
||||||
|
yield allocated[0].callRemote("close")
|
||||||
|
returnValue(storage_index)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_bucket_advise_corrupt_share(self):
|
||||||
|
"""
|
||||||
|
Calling ``advise_corrupt_share()`` on a bucket returned by
|
||||||
|
``IStorageServer.get_buckets()`` does not result in error (other
|
||||||
|
behavior is opaque at this level of abstraction).
|
||||||
|
"""
|
||||||
|
storage_index = yield self.create_share()
|
||||||
|
buckets = yield self.storage_server.get_buckets(storage_index)
|
||||||
|
yield buckets[0].callRemote("advise_corrupt_share", b"OH NO")
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_advise_corrupt_share(self):
|
||||||
|
"""
|
||||||
|
Calling ``advise_corrupt_share()`` on an immutable share does not
|
||||||
|
result in error (other behavior is opaque at this level of
|
||||||
|
abstraction).
|
||||||
|
"""
|
||||||
|
storage_index = yield self.create_share()
|
||||||
|
yield self.storage_server.advise_corrupt_share(
|
||||||
|
b"immutable", storage_index, 0, b"ono"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IStorageServerMutableAPIsTestsMixin(object):
|
||||||
|
"""
|
||||||
|
Tests for ``IStorageServer``'s mutable APIs.
|
||||||
|
|
||||||
|
``self.storage_server`` is expected to provide ``IStorageServer``.
|
||||||
|
|
||||||
|
``STARAW`` is short for ``slot_testv_and_readv_and_writev``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def new_secrets(self):
|
||||||
|
"""Return a 3-tuple of secrets for STARAW calls."""
|
||||||
|
return (new_secret(), new_secret(), new_secret())
|
||||||
|
|
||||||
|
def staraw(self, *args, **kwargs):
|
||||||
|
"""Like ``slot_testv_and_readv_and_writev``, but less typing."""
|
||||||
|
return self.storage_server.slot_testv_and_readv_and_writev(*args, **kwargs)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_STARAW_reads_after_write(self):
|
||||||
|
"""
|
||||||
|
When data is written with
|
||||||
|
``IStorageServer.slot_testv_and_readv_and_writev``, it can then be read
|
||||||
|
by a separate call using that API.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"abcdefg")], 7),
|
||||||
|
1: ([], [(0, b"0123"), (4, b"456")], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
# Whole thing, partial, going beyond the edge, completely outside
|
||||||
|
# range:
|
||||||
|
r_vector=[(0, 7), (2, 3), (6, 8), (100, 10)],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
reads,
|
||||||
|
{0: [b"abcdefg", b"cde", b"g", b""], 1: [b"0123456", b"234", b"6", b""]},
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_SATRAW_reads_happen_before_writes_in_single_query(self):
|
||||||
|
"""
|
||||||
|
If a ``IStorageServer.slot_testv_and_readv_and_writev`` command
|
||||||
|
contains both reads and writes, the read returns results that precede
|
||||||
|
the write.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"abcdefg")], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Read and write in same command; read happens before write:
|
||||||
|
(written, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"X" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
self.assertEqual(reads, {0: [b"abcdefg"]})
|
||||||
|
|
||||||
|
# The write is available in next read:
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {0: [b"X" * 7]})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_SATRAW_writes_happens_only_if_test_matches(self):
|
||||||
|
"""
|
||||||
|
If a ``IStorageServer.slot_testv_and_readv_and_writev`` includes both a
|
||||||
|
test and a write, the write succeeds if the test matches, and fails if
|
||||||
|
the test does not match.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"1" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Test matches, so write happens:
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: (
|
||||||
|
[(0, 3, b"1" * 3), (3, 4, b"1" * 4)],
|
||||||
|
[(0, b"2" * 7)],
|
||||||
|
7,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {0: [b"2" * 7]})
|
||||||
|
|
||||||
|
# Test does not match, so write does not happen:
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([(0, 7, b"1" * 7)], [(0, b"3" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, False)
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {0: [b"2" * 7]})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_SATRAW_tests_past_end_of_data(self):
|
||||||
|
"""
|
||||||
|
If a ``IStorageServer.slot_testv_and_readv_and_writev`` includes a test
|
||||||
|
vector that reads past the end of the data, the result is limited to
|
||||||
|
actual available data.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
|
||||||
|
# Since there is no data on server, the test vector will return empty
|
||||||
|
# string, which matches expected result, so write will succeed.
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([(0, 10, b"")], [(0, b"1" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Now the test vector is a 10-read off of a 7-byte value, but expected
|
||||||
|
# value is still 7 bytes, so the write will again succeed.
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([(0, 10, b"1" * 7)], [(0, b"2" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_SATRAW_reads_past_end_of_data(self):
|
||||||
|
"""
|
||||||
|
If a ``IStorageServer.slot_testv_and_readv_and_writev`` reads past the
|
||||||
|
end of the data, the result is limited to actual available data.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
|
||||||
|
# Write some data
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"12345")], 5),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Reads past end.
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 100), (2, 50)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {0: [b"12345", b"345"]})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_STARAW_write_enabler_must_match(self):
|
||||||
|
"""
|
||||||
|
If the write enabler secret passed to
|
||||||
|
``IStorageServer.slot_testv_and_readv_and_writev`` doesn't match
|
||||||
|
previous writes, the write fails.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"1" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Write enabler secret does not match, so write does not happen:
|
||||||
|
bad_secrets = (new_secret(),) + secrets[1:]
|
||||||
|
with self.assertRaises(RemoteException):
|
||||||
|
yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
bad_secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"2" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {0: [b"1" * 7]})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_STARAW_zero_new_length_deletes(self):
|
||||||
|
"""
|
||||||
|
A zero new length passed to
|
||||||
|
``IStorageServer.slot_testv_and_readv_and_writev`` deletes the share.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"1" * 7)], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# Write with new length of 0:
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"1" * 7)], 0),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
# It's gone!
|
||||||
|
(_, reads) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={},
|
||||||
|
r_vector=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(reads, {})
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_slot_readv(self):
|
||||||
|
"""
|
||||||
|
Data written with ``IStorageServer.slot_testv_and_readv_and_writev()``
|
||||||
|
can be read using ``IStorageServer.slot_readv()``. Reads can't go past
|
||||||
|
the end of the data.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"abcdefg")], 7),
|
||||||
|
1: ([], [(0, b"0123"), (4, b"456")], 7),
|
||||||
|
# This will never get read from, just here to show we only read
|
||||||
|
# from shares explicitly requested by slot_readv:
|
||||||
|
2: ([], [(0, b"XYZW")], 4),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
reads = yield self.storage_server.slot_readv(
|
||||||
|
storage_index,
|
||||||
|
shares=[0, 1],
|
||||||
|
# Whole thing, partial, going beyond the edge, completely outside
|
||||||
|
# range:
|
||||||
|
readv=[(0, 7), (2, 3), (6, 8), (100, 10)],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
reads,
|
||||||
|
{0: [b"abcdefg", b"cde", b"g", b""], 1: [b"0123456", b"234", b"6", b""]},
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_slot_readv_no_shares(self):
|
||||||
|
"""
|
||||||
|
With no shares given, ``IStorageServer.slot_readv()`` reads from all shares.
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"abcdefg")], 7),
|
||||||
|
1: ([], [(0, b"0123456")], 7),
|
||||||
|
2: ([], [(0, b"9876543")], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
reads = yield self.storage_server.slot_readv(
|
||||||
|
storage_index,
|
||||||
|
shares=[],
|
||||||
|
readv=[(0, 7)],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
reads,
|
||||||
|
{0: [b"abcdefg"], 1: [b"0123456"], 2: [b"9876543"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def test_advise_corrupt_share(self):
|
||||||
|
"""
|
||||||
|
Calling ``advise_corrupt_share()`` on a mutable share does not
|
||||||
|
result in error (other behavior is opaque at this level of
|
||||||
|
abstraction).
|
||||||
|
"""
|
||||||
|
secrets = self.new_secrets()
|
||||||
|
storage_index = new_storage_index()
|
||||||
|
(written, _) = yield self.staraw(
|
||||||
|
storage_index,
|
||||||
|
secrets,
|
||||||
|
tw_vectors={
|
||||||
|
0: ([], [(0, b"abcdefg")], 7),
|
||||||
|
},
|
||||||
|
r_vector=[],
|
||||||
|
)
|
||||||
|
self.assertEqual(written, True)
|
||||||
|
|
||||||
|
yield self.storage_server.advise_corrupt_share(
|
||||||
|
b"mutable", storage_index, 0, b"ono"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _FoolscapMixin(SystemTestMixin):
|
||||||
|
"""Run tests on Foolscap version of ``IStorageServer."""
|
||||||
|
|
||||||
|
def _get_native_server(self):
|
||||||
|
return next(iter(self.clients[0].storage_broker.get_known_servers()))
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def setUp(self):
|
||||||
|
AsyncTestCase.setUp(self)
|
||||||
|
self.basedir = "test_istorageserver/" + self.id()
|
||||||
|
yield SystemTestMixin.setUp(self)
|
||||||
|
yield self.set_up_nodes(1)
|
||||||
|
self.storage_server = self._get_native_server().get_storage_server()
|
||||||
|
self.assertTrue(IStorageServer.providedBy(self.storage_server))
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def tearDown(self):
|
||||||
|
AsyncTestCase.tearDown(self)
|
||||||
|
yield SystemTestMixin.tearDown(self)
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def disconnect(self):
|
||||||
|
"""
|
||||||
|
Disconnect and then reconnect with a new ``IStorageServer``.
|
||||||
|
"""
|
||||||
|
current = self.storage_server
|
||||||
|
yield self.bounce_client(0)
|
||||||
|
self.storage_server = self._get_native_server().get_storage_server()
|
||||||
|
assert self.storage_server is not current
|
||||||
|
|
||||||
|
|
||||||
|
class FoolscapSharedAPIsTests(
|
||||||
|
_FoolscapMixin, IStorageServerSharedAPIsTestsMixin, AsyncTestCase
|
||||||
|
):
|
||||||
|
"""Foolscap-specific tests for shared ``IStorageServer`` APIs."""
|
||||||
|
|
||||||
|
|
||||||
|
class FoolscapImmutableAPIsTests(
|
||||||
|
_FoolscapMixin, IStorageServerImmutableAPIsTestsMixin, AsyncTestCase
|
||||||
|
):
|
||||||
|
"""Foolscap-specific tests for immutable ``IStorageServer`` APIs."""
|
||||||
|
|
||||||
|
|
||||||
|
class FoolscapMutableAPIsTests(
|
||||||
|
_FoolscapMixin, IStorageServerMutableAPIsTestsMixin, AsyncTestCase
|
||||||
|
):
|
||||||
|
"""Foolscap-specific tests for immutable ``IStorageServer`` APIs."""
|
@ -6,7 +6,7 @@ from __future__ import division
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from future.utils import PY2, native_str
|
from future.utils import PY2
|
||||||
if 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 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
|
||||||
|
|
||||||
@ -530,6 +530,14 @@ def _stub_allocate_tcp_port():
|
|||||||
"""
|
"""
|
||||||
return 999
|
return 999
|
||||||
|
|
||||||
|
def _stub_none():
|
||||||
|
"""
|
||||||
|
A function like ``_stub_allocate_tcp`` or ``_stub_get_local_addresses_sync``
|
||||||
|
but that return an empty list since ``allmydata.node._tub_portlocation`` requires a
|
||||||
|
callable for paramter 1 and 2 counting from 0.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class TestMissingPorts(unittest.TestCase):
|
class TestMissingPorts(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
@ -550,7 +558,7 @@ class TestMissingPorts(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
config = config_from_string(self.basedir, "portnum", config_data)
|
config = config_from_string(self.basedir, "portnum", config_data)
|
||||||
with self.assertRaises(PortAssignmentRequired):
|
with self.assertRaises(PortAssignmentRequired):
|
||||||
_tub_portlocation(config, None, None)
|
_tub_portlocation(config, _stub_none, _stub_none)
|
||||||
|
|
||||||
def test_listen_on_zero_with_host(self):
|
def test_listen_on_zero_with_host(self):
|
||||||
"""
|
"""
|
||||||
@ -563,10 +571,7 @@ class TestMissingPorts(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
config = config_from_string(self.basedir, "portnum", config_data)
|
config = config_from_string(self.basedir, "portnum", config_data)
|
||||||
with self.assertRaises(PortAssignmentRequired):
|
with self.assertRaises(PortAssignmentRequired):
|
||||||
_tub_portlocation(config, None, None)
|
_tub_portlocation(config, _stub_none, _stub_none)
|
||||||
test_listen_on_zero_with_host.todo = native_str( # type: ignore
|
|
||||||
"https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3563"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_parsing_tcp(self):
|
def test_parsing_tcp(self):
|
||||||
"""
|
"""
|
||||||
|
273
src/allmydata/test/test_openmetrics.py
Normal file
273
src/allmydata/test/test_openmetrics.py
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
"""
|
||||||
|
Tests for ``/statistics?t=openmetrics``.
|
||||||
|
|
||||||
|
Ported to Python 3.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from future.utils import PY2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
# fmt: off
|
||||||
|
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
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
from prometheus_client.openmetrics import parser
|
||||||
|
|
||||||
|
from treq.testing import RequestTraversalAgent
|
||||||
|
|
||||||
|
from twisted.web.http import OK
|
||||||
|
from twisted.web.client import readBody
|
||||||
|
from twisted.web.resource import Resource
|
||||||
|
|
||||||
|
from testtools.twistedsupport import succeeded
|
||||||
|
from testtools.matchers import (
|
||||||
|
AfterPreprocessing,
|
||||||
|
Equals,
|
||||||
|
MatchesAll,
|
||||||
|
MatchesStructure,
|
||||||
|
MatchesPredicate,
|
||||||
|
)
|
||||||
|
from testtools.content import text_content
|
||||||
|
|
||||||
|
from allmydata.web.status import Statistics
|
||||||
|
from allmydata.test.common import SyncTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class FakeStatsProvider(object):
|
||||||
|
"""
|
||||||
|
A stats provider that hands backed a canned collection of performance
|
||||||
|
statistics.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_stats(self):
|
||||||
|
# Parsed into a dict from a running tahoe's /statistics?t=json
|
||||||
|
stats = {
|
||||||
|
"stats": {
|
||||||
|
"storage_server.latencies.get.99_9_percentile": None,
|
||||||
|
"storage_server.latencies.close.10_0_percentile": 0.00021910667419433594,
|
||||||
|
"storage_server.latencies.read.01_0_percentile": 2.8848648071289062e-05,
|
||||||
|
"storage_server.latencies.writev.99_9_percentile": None,
|
||||||
|
"storage_server.latencies.read.99_9_percentile": None,
|
||||||
|
"storage_server.latencies.allocate.99_0_percentile": 0.000988006591796875,
|
||||||
|
"storage_server.latencies.writev.mean": 0.00045332245070571654,
|
||||||
|
"storage_server.latencies.close.99_9_percentile": None,
|
||||||
|
"cpu_monitor.15min_avg": 0.00017592000079223033,
|
||||||
|
"storage_server.disk_free_for_root": 103289454592,
|
||||||
|
"storage_server.latencies.get.99_0_percentile": 0.000347137451171875,
|
||||||
|
"storage_server.latencies.get.mean": 0.00021158285060171353,
|
||||||
|
"storage_server.latencies.read.90_0_percentile": 8.893013000488281e-05,
|
||||||
|
"storage_server.latencies.write.01_0_percentile": 3.600120544433594e-05,
|
||||||
|
"storage_server.latencies.write.99_9_percentile": 0.00017690658569335938,
|
||||||
|
"storage_server.latencies.close.90_0_percentile": 0.00033211708068847656,
|
||||||
|
"storage_server.disk_total": 103497859072,
|
||||||
|
"storage_server.latencies.close.95_0_percentile": 0.0003509521484375,
|
||||||
|
"storage_server.latencies.readv.samplesize": 1000,
|
||||||
|
"storage_server.disk_free_for_nonroot": 103289454592,
|
||||||
|
"storage_server.latencies.close.mean": 0.0002715024480059103,
|
||||||
|
"storage_server.latencies.writev.95_0_percentile": 0.0007410049438476562,
|
||||||
|
"storage_server.latencies.readv.90_0_percentile": 0.0003781318664550781,
|
||||||
|
"storage_server.latencies.readv.99_0_percentile": 0.0004050731658935547,
|
||||||
|
"storage_server.latencies.allocate.mean": 0.0007128627429454784,
|
||||||
|
"storage_server.latencies.close.samplesize": 326,
|
||||||
|
"storage_server.latencies.get.50_0_percentile": 0.0001819133758544922,
|
||||||
|
"storage_server.latencies.write.50_0_percentile": 4.482269287109375e-05,
|
||||||
|
"storage_server.latencies.readv.01_0_percentile": 0.0002970695495605469,
|
||||||
|
"storage_server.latencies.get.10_0_percentile": 0.00015687942504882812,
|
||||||
|
"storage_server.latencies.allocate.90_0_percentile": 0.0008189678192138672,
|
||||||
|
"storage_server.latencies.get.samplesize": 472,
|
||||||
|
"storage_server.total_bucket_count": 393,
|
||||||
|
"storage_server.latencies.read.mean": 5.936201880959903e-05,
|
||||||
|
"storage_server.latencies.allocate.01_0_percentile": 0.0004208087921142578,
|
||||||
|
"storage_server.latencies.allocate.99_9_percentile": None,
|
||||||
|
"storage_server.latencies.readv.mean": 0.00034061360359191893,
|
||||||
|
"storage_server.disk_used": 208404480,
|
||||||
|
"storage_server.latencies.allocate.50_0_percentile": 0.0007410049438476562,
|
||||||
|
"storage_server.latencies.read.99_0_percentile": 0.00011992454528808594,
|
||||||
|
"node.uptime": 3805759.8545179367,
|
||||||
|
"storage_server.latencies.writev.10_0_percentile": 0.00035190582275390625,
|
||||||
|
"storage_server.latencies.writev.90_0_percentile": 0.0006821155548095703,
|
||||||
|
"storage_server.latencies.close.01_0_percentile": 0.00021505355834960938,
|
||||||
|
"storage_server.latencies.close.50_0_percentile": 0.0002579689025878906,
|
||||||
|
"cpu_monitor.1min_avg": 0.0002130000000003444,
|
||||||
|
"storage_server.latencies.writev.50_0_percentile": 0.0004138946533203125,
|
||||||
|
"storage_server.latencies.read.95_0_percentile": 9.107589721679688e-05,
|
||||||
|
"storage_server.latencies.readv.95_0_percentile": 0.0003859996795654297,
|
||||||
|
"storage_server.latencies.write.10_0_percentile": 3.719329833984375e-05,
|
||||||
|
"storage_server.accepting_immutable_shares": 1,
|
||||||
|
"storage_server.latencies.writev.samplesize": 309,
|
||||||
|
"storage_server.latencies.get.95_0_percentile": 0.0003190040588378906,
|
||||||
|
"storage_server.latencies.readv.10_0_percentile": 0.00032210350036621094,
|
||||||
|
"storage_server.latencies.get.90_0_percentile": 0.0002999305725097656,
|
||||||
|
"storage_server.latencies.get.01_0_percentile": 0.0001239776611328125,
|
||||||
|
"cpu_monitor.total": 641.4941180000001,
|
||||||
|
"storage_server.latencies.write.samplesize": 1000,
|
||||||
|
"storage_server.latencies.write.95_0_percentile": 9.489059448242188e-05,
|
||||||
|
"storage_server.latencies.read.50_0_percentile": 6.890296936035156e-05,
|
||||||
|
"storage_server.latencies.writev.01_0_percentile": 0.00033211708068847656,
|
||||||
|
"storage_server.latencies.read.10_0_percentile": 3.0994415283203125e-05,
|
||||||
|
"storage_server.latencies.allocate.10_0_percentile": 0.0004949569702148438,
|
||||||
|
"storage_server.reserved_space": 0,
|
||||||
|
"storage_server.disk_avail": 103289454592,
|
||||||
|
"storage_server.latencies.write.99_0_percentile": 0.00011301040649414062,
|
||||||
|
"storage_server.latencies.write.90_0_percentile": 9.083747863769531e-05,
|
||||||
|
"cpu_monitor.5min_avg": 0.0002370666691157502,
|
||||||
|
"storage_server.latencies.write.mean": 5.8008909225463864e-05,
|
||||||
|
"storage_server.latencies.readv.50_0_percentile": 0.00033020973205566406,
|
||||||
|
"storage_server.latencies.close.99_0_percentile": 0.0004038810729980469,
|
||||||
|
"storage_server.allocated": 0,
|
||||||
|
"storage_server.latencies.writev.99_0_percentile": 0.0007710456848144531,
|
||||||
|
"storage_server.latencies.readv.99_9_percentile": 0.0004780292510986328,
|
||||||
|
"storage_server.latencies.read.samplesize": 170,
|
||||||
|
"storage_server.latencies.allocate.samplesize": 406,
|
||||||
|
"storage_server.latencies.allocate.95_0_percentile": 0.0008411407470703125,
|
||||||
|
},
|
||||||
|
"counters": {
|
||||||
|
"storage_server.writev": 309,
|
||||||
|
"storage_server.bytes_added": 197836146,
|
||||||
|
"storage_server.close": 326,
|
||||||
|
"storage_server.readv": 14299,
|
||||||
|
"storage_server.allocate": 406,
|
||||||
|
"storage_server.read": 170,
|
||||||
|
"storage_server.write": 3775,
|
||||||
|
"storage_server.get": 472,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
class HackItResource(Resource, object):
|
||||||
|
"""
|
||||||
|
A bridge between ``RequestTraversalAgent`` and ``MultiFormatResource``
|
||||||
|
(used by ``Statistics``). ``MultiFormatResource`` expects the request
|
||||||
|
object to have a ``fields`` attribute but Twisted's ``IRequest`` has no
|
||||||
|
such attribute. Create it here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getChildWithDefault(self, path, request):
|
||||||
|
request.fields = None
|
||||||
|
return Resource.getChildWithDefault(self, path, request)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenMetrics(SyncTestCase):
|
||||||
|
"""
|
||||||
|
Tests for ``/statistics?t=openmetrics``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_spec_compliance(self):
|
||||||
|
"""
|
||||||
|
Does our output adhere to the `OpenMetrics <https://openmetrics.io/>` spec?
|
||||||
|
https://github.com/OpenObservability/OpenMetrics/
|
||||||
|
https://prometheus.io/docs/instrumenting/exposition_formats/
|
||||||
|
"""
|
||||||
|
root = HackItResource()
|
||||||
|
root.putChild(b"", Statistics(FakeStatsProvider()))
|
||||||
|
rta = RequestTraversalAgent(root)
|
||||||
|
d = rta.request(b"GET", b"http://localhost/?t=openmetrics")
|
||||||
|
self.assertThat(d, succeeded(matches_stats(self)))
|
||||||
|
|
||||||
|
|
||||||
|
def matches_stats(testcase):
|
||||||
|
"""
|
||||||
|
Create a matcher that matches a response that confirms to the OpenMetrics
|
||||||
|
specification.
|
||||||
|
|
||||||
|
* The ``Content-Type`` is **application/openmetrics-text; version=1.0.0; charset=utf-8**.
|
||||||
|
* The status is **OK**.
|
||||||
|
* The body can be parsed by an OpenMetrics parser.
|
||||||
|
* The metric families in the body are grouped and sorted.
|
||||||
|
* At least one of the expected families appears in the body.
|
||||||
|
|
||||||
|
:param testtools.TestCase testcase: The case to which to add detail about the matching process.
|
||||||
|
|
||||||
|
:return: A matcher.
|
||||||
|
"""
|
||||||
|
return MatchesAll(
|
||||||
|
MatchesStructure(
|
||||||
|
code=Equals(OK),
|
||||||
|
# "The content type MUST be..."
|
||||||
|
headers=has_header(
|
||||||
|
"content-type",
|
||||||
|
"application/openmetrics-text; version=1.0.0; charset=utf-8",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AfterPreprocessing(
|
||||||
|
readBodyText,
|
||||||
|
succeeded(
|
||||||
|
MatchesAll(
|
||||||
|
MatchesPredicate(add_detail(testcase, "response body"), "%s dummy"),
|
||||||
|
parses_as_openmetrics(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_detail(testcase, name):
|
||||||
|
"""
|
||||||
|
Create a matcher that always matches and as a side-effect adds the matched
|
||||||
|
value as detail to the testcase.
|
||||||
|
|
||||||
|
:param testtools.TestCase testcase: The case to which to add the detail.
|
||||||
|
|
||||||
|
:return: A matcher.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def predicate(value):
|
||||||
|
testcase.addDetail(name, text_content(value))
|
||||||
|
return True
|
||||||
|
|
||||||
|
return predicate
|
||||||
|
|
||||||
|
|
||||||
|
def readBodyText(response):
|
||||||
|
"""
|
||||||
|
Read the response body and decode it using UTF-8.
|
||||||
|
|
||||||
|
:param twisted.web.iweb.IResponse response: The response from which to
|
||||||
|
read the body.
|
||||||
|
|
||||||
|
:return: A ``Deferred`` that fires with the ``str`` body.
|
||||||
|
"""
|
||||||
|
d = readBody(response)
|
||||||
|
d.addCallback(lambda body: body.decode("utf-8"))
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def has_header(name, value):
|
||||||
|
"""
|
||||||
|
Create a matcher that matches a response object that includes the given
|
||||||
|
name / value pair.
|
||||||
|
|
||||||
|
:param str name: The name of the item in the HTTP header to match.
|
||||||
|
:param str value: The value of the item in the HTTP header to match by equality.
|
||||||
|
|
||||||
|
:return: A matcher.
|
||||||
|
"""
|
||||||
|
return AfterPreprocessing(
|
||||||
|
lambda headers: headers.getRawHeaders(name),
|
||||||
|
Equals([value]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parses_as_openmetrics():
|
||||||
|
"""
|
||||||
|
Create a matcher that matches a ``str`` string that can be parsed as an
|
||||||
|
OpenMetrics response and includes a certain well-known value expected by
|
||||||
|
the tests.
|
||||||
|
|
||||||
|
:return: A matcher.
|
||||||
|
"""
|
||||||
|
# The parser throws if it does not like its input.
|
||||||
|
# Wrapped in a list() to drain the generator.
|
||||||
|
return AfterPreprocessing(
|
||||||
|
lambda body: list(parser.text_string_to_metric_families(body)),
|
||||||
|
AfterPreprocessing(
|
||||||
|
lambda families: families[-1].name,
|
||||||
|
Equals("tahoe_stats_storage_server_total_bucket_count"),
|
||||||
|
),
|
||||||
|
)
|
@ -1,122 +0,0 @@
|
|||||||
"""
|
|
||||||
Tests related to the Python 3 porting effort itself.
|
|
||||||
|
|
||||||
This module has been ported to Python 3.
|
|
||||||
"""
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from future.utils import PY2, native_str
|
|
||||||
if PY2:
|
|
||||||
from 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 twisted.python.modules import (
|
|
||||||
getModule,
|
|
||||||
)
|
|
||||||
from twisted.trial.unittest import (
|
|
||||||
SynchronousTestCase,
|
|
||||||
)
|
|
||||||
|
|
||||||
from allmydata.util._python3 import PORTED_MODULES, PORTED_TEST_MODULES
|
|
||||||
|
|
||||||
|
|
||||||
class Python3PortingEffortTests(SynchronousTestCase):
|
|
||||||
|
|
||||||
def test_finished_porting(self):
|
|
||||||
"""
|
|
||||||
Tahoe-LAFS has been ported to Python 3.
|
|
||||||
|
|
||||||
Once
|
|
||||||
https://tahoe-lafs.org/trac/tahoe-lafs/milestone/Support%20Python%203
|
|
||||||
is completed this test should pass (and can be deleted!).
|
|
||||||
"""
|
|
||||||
tahoe_lafs_module_names = set(all_module_names("allmydata"))
|
|
||||||
ported_names = set(ported_module_names())
|
|
||||||
self.assertEqual(
|
|
||||||
tahoe_lafs_module_names - ported_names,
|
|
||||||
set(),
|
|
||||||
"Some unported modules remain: {}".format(
|
|
||||||
unported_report(
|
|
||||||
tahoe_lafs_module_names,
|
|
||||||
ported_names,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
test_finished_porting.todo = native_str( # type: ignore
|
|
||||||
"https://tahoe-lafs.org/trac/tahoe-lafs/milestone/Support%20Python%203 should be completed",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ported_modules_exist(self):
|
|
||||||
"""
|
|
||||||
All modules listed as ported exist and belong to Tahoe-LAFS.
|
|
||||||
"""
|
|
||||||
tahoe_lafs_module_names = set(all_module_names("allmydata"))
|
|
||||||
ported_names = set(ported_module_names())
|
|
||||||
unknown = ported_names - tahoe_lafs_module_names
|
|
||||||
self.assertEqual(
|
|
||||||
unknown,
|
|
||||||
set(),
|
|
||||||
"Some supposedly-ported modules weren't found: {}.".format(sorted(unknown)),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ported_modules_distinct(self):
|
|
||||||
"""
|
|
||||||
The ported modules list doesn't contain duplicates.
|
|
||||||
"""
|
|
||||||
ported_names_list = ported_module_names()
|
|
||||||
ported_names_list.sort()
|
|
||||||
ported_names_set = set(ported_names_list)
|
|
||||||
ported_names_unique_list = list(ported_names_set)
|
|
||||||
ported_names_unique_list.sort()
|
|
||||||
self.assertEqual(
|
|
||||||
ported_names_list,
|
|
||||||
ported_names_unique_list,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def all_module_names(toplevel):
|
|
||||||
"""
|
|
||||||
:param unicode toplevel: The name of a top-level Python package.
|
|
||||||
|
|
||||||
:return iterator[unicode]: An iterator of ``unicode`` giving the names of
|
|
||||||
all modules within the given top-level Python package.
|
|
||||||
"""
|
|
||||||
allmydata = getModule(toplevel)
|
|
||||||
for module in allmydata.walkModules():
|
|
||||||
name = module.name
|
|
||||||
if PY2:
|
|
||||||
name = name.decode("utf-8")
|
|
||||||
yield name
|
|
||||||
|
|
||||||
|
|
||||||
def ported_module_names():
|
|
||||||
"""
|
|
||||||
:return list[unicode]: A ``list`` of ``unicode`` giving the names of
|
|
||||||
Tahoe-LAFS modules which have been ported to Python 3.
|
|
||||||
"""
|
|
||||||
return PORTED_MODULES + PORTED_TEST_MODULES
|
|
||||||
|
|
||||||
|
|
||||||
def unported_report(tahoe_lafs_module_names, ported_names):
|
|
||||||
return """
|
|
||||||
Ported files: {} / {}
|
|
||||||
Ported lines: {} / {}
|
|
||||||
""".format(
|
|
||||||
len(ported_names),
|
|
||||||
len(tahoe_lafs_module_names),
|
|
||||||
sum(map(count_lines, ported_names)),
|
|
||||||
sum(map(count_lines, tahoe_lafs_module_names)),
|
|
||||||
)
|
|
||||||
|
|
||||||
def count_lines(module_name):
|
|
||||||
module = getModule(module_name)
|
|
||||||
try:
|
|
||||||
source = module.filePath.getContent()
|
|
||||||
except Exception as e:
|
|
||||||
print((module_name, e))
|
|
||||||
return 0
|
|
||||||
lines = source.splitlines()
|
|
||||||
nonblank = [_f for _f in lines if _f]
|
|
||||||
return len(nonblank)
|
|
@ -19,6 +19,21 @@ import os.path, re, sys
|
|||||||
from os import linesep
|
from os import linesep
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from testtools import (
|
||||||
|
skipUnless,
|
||||||
|
)
|
||||||
|
from testtools.matchers import (
|
||||||
|
MatchesListwise,
|
||||||
|
MatchesAny,
|
||||||
|
Contains,
|
||||||
|
Equals,
|
||||||
|
Always,
|
||||||
|
)
|
||||||
|
from testtools.twistedsupport import (
|
||||||
|
succeeded,
|
||||||
|
)
|
||||||
from eliot import (
|
from eliot import (
|
||||||
log_call,
|
log_call,
|
||||||
)
|
)
|
||||||
@ -39,6 +54,10 @@ from allmydata.util import fileutil, pollmixin
|
|||||||
from allmydata.util.encodingutil import unicode_to_argv
|
from allmydata.util.encodingutil import unicode_to_argv
|
||||||
from allmydata.test import common_util
|
from allmydata.test import common_util
|
||||||
import allmydata
|
import allmydata
|
||||||
|
from allmydata.scripts.runner import (
|
||||||
|
parse_options,
|
||||||
|
)
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
PIPE,
|
PIPE,
|
||||||
Popen,
|
Popen,
|
||||||
@ -46,6 +65,7 @@ from .common import (
|
|||||||
from .common_util import (
|
from .common_util import (
|
||||||
parse_cli,
|
parse_cli,
|
||||||
run_cli,
|
run_cli,
|
||||||
|
run_cli_unicode,
|
||||||
)
|
)
|
||||||
from .cli_node_api import (
|
from .cli_node_api import (
|
||||||
CLINodeAPI,
|
CLINodeAPI,
|
||||||
@ -56,6 +76,9 @@ from .cli_node_api import (
|
|||||||
from ..util.eliotutil import (
|
from ..util.eliotutil import (
|
||||||
inline_callbacks,
|
inline_callbacks,
|
||||||
)
|
)
|
||||||
|
from .common import (
|
||||||
|
SyncTestCase,
|
||||||
|
)
|
||||||
|
|
||||||
def get_root_from_file(src):
|
def get_root_from_file(src):
|
||||||
srcdir = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(src))))
|
srcdir = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(src))))
|
||||||
@ -74,6 +97,56 @@ srcfile = allmydata.__file__
|
|||||||
rootdir = get_root_from_file(srcfile)
|
rootdir = get_root_from_file(srcfile)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseOptionsTests(SyncTestCase):
|
||||||
|
"""
|
||||||
|
Tests for ``parse_options``.
|
||||||
|
"""
|
||||||
|
@skipUnless(six.PY2, "Only Python 2 exceptions must stringify to bytes.")
|
||||||
|
def test_nonascii_unknown_subcommand_python2(self):
|
||||||
|
"""
|
||||||
|
When ``parse_options`` is called with an argv indicating a subcommand that
|
||||||
|
does not exist and which also contains non-ascii characters, the
|
||||||
|
exception it raises includes the subcommand encoded as UTF-8.
|
||||||
|
"""
|
||||||
|
tricky = u"\u00F6"
|
||||||
|
try:
|
||||||
|
parse_options([tricky])
|
||||||
|
except usage.error as e:
|
||||||
|
self.assertEqual(
|
||||||
|
b"Unknown command: \\xf6",
|
||||||
|
b"{}".format(e),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseOrExitTests(SyncTestCase):
|
||||||
|
"""
|
||||||
|
Tests for ``parse_or_exit``.
|
||||||
|
"""
|
||||||
|
def test_nonascii_error_content(self):
|
||||||
|
"""
|
||||||
|
``parse_or_exit`` can report errors that include non-ascii content.
|
||||||
|
"""
|
||||||
|
tricky = u"\u00F6"
|
||||||
|
self.assertThat(
|
||||||
|
run_cli_unicode(tricky, [], encoding="utf-8"),
|
||||||
|
succeeded(
|
||||||
|
MatchesListwise([
|
||||||
|
# returncode
|
||||||
|
Equals(1),
|
||||||
|
# stdout
|
||||||
|
MatchesAny(
|
||||||
|
# Python 2
|
||||||
|
Contains(u"Unknown command: \\xf6"),
|
||||||
|
# Python 3
|
||||||
|
Contains(u"Unknown command: \xf6"),
|
||||||
|
),
|
||||||
|
# stderr,
|
||||||
|
Always()
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@log_call(action_type="run-bin-tahoe")
|
@log_call(action_type="run-bin-tahoe")
|
||||||
def run_bintahoe(extra_argv, python_options=None):
|
def run_bintahoe(extra_argv, python_options=None):
|
||||||
"""
|
"""
|
||||||
@ -110,8 +183,16 @@ class BinTahoe(common_util.SignalMixin, unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
tricky = u"\u00F6"
|
tricky = u"\u00F6"
|
||||||
out, err, returncode = run_bintahoe([tricky])
|
out, err, returncode = run_bintahoe([tricky])
|
||||||
|
if PY2:
|
||||||
|
expected = u"Unknown command: \\xf6"
|
||||||
|
else:
|
||||||
|
expected = u"Unknown command: \xf6"
|
||||||
self.assertEqual(returncode, 1)
|
self.assertEqual(returncode, 1)
|
||||||
self.assertIn(u"Unknown command: " + tricky, out)
|
self.assertIn(
|
||||||
|
expected,
|
||||||
|
out,
|
||||||
|
"expected {!r} not found in {!r}\nstderr: {!r}".format(expected, out, err),
|
||||||
|
)
|
||||||
|
|
||||||
def test_with_python_options(self):
|
def test_with_python_options(self):
|
||||||
"""
|
"""
|
||||||
@ -305,7 +386,12 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin):
|
|||||||
u"--hostname", u"127.0.0.1",
|
u"--hostname", u"127.0.0.1",
|
||||||
])
|
])
|
||||||
|
|
||||||
self.assertEqual(returncode, 0)
|
self.assertEqual(
|
||||||
|
returncode,
|
||||||
|
0,
|
||||||
|
"stdout: {!r}\n"
|
||||||
|
"stderr: {!r}\n",
|
||||||
|
)
|
||||||
|
|
||||||
# This makes sure that node.url is written, which allows us to
|
# This makes sure that node.url is written, which allows us to
|
||||||
# detect when the introducer restarts in _node_has_restarted below.
|
# detect when the introducer restarts in _node_has_restarted below.
|
||||||
|
@ -8,7 +8,7 @@ from __future__ import division
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from future.utils import native_str, PY2, bytes_to_native_str
|
from future.utils import native_str, PY2, bytes_to_native_str, bchr
|
||||||
if 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 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_str
|
from six import ensure_str
|
||||||
@ -19,20 +19,23 @@ import platform
|
|||||||
import stat
|
import stat
|
||||||
import struct
|
import struct
|
||||||
import shutil
|
import shutil
|
||||||
import gc
|
from uuid import uuid4
|
||||||
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from twisted.internet.task import Clock
|
||||||
|
|
||||||
|
from hypothesis import given, strategies
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
from allmydata import interfaces
|
from allmydata import interfaces
|
||||||
from allmydata.util import fileutil, hashutil, base32
|
from allmydata.util import fileutil, hashutil, base32
|
||||||
from allmydata.storage.server import StorageServer
|
from allmydata.storage.server import StorageServer, DEFAULT_RENEWAL_TIME
|
||||||
from allmydata.storage.shares import get_share_file
|
from allmydata.storage.shares import get_share_file
|
||||||
from allmydata.storage.mutable import MutableShareFile
|
from allmydata.storage.mutable import MutableShareFile
|
||||||
from allmydata.storage.immutable import BucketWriter, BucketReader, ShareFile
|
from allmydata.storage.immutable import BucketWriter, BucketReader, ShareFile
|
||||||
from allmydata.storage.common import DataTooLargeError, storage_index_to_dir, \
|
from allmydata.storage.common import storage_index_to_dir, \
|
||||||
UnknownMutableContainerVersionError, UnknownImmutableContainerVersionError, \
|
UnknownMutableContainerVersionError, UnknownImmutableContainerVersionError, \
|
||||||
si_b2a, si_a2b
|
si_b2a, si_a2b
|
||||||
from allmydata.storage.lease import LeaseInfo
|
from allmydata.storage.lease import LeaseInfo
|
||||||
@ -46,7 +49,9 @@ from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
|
|||||||
SIGNATURE_SIZE, \
|
SIGNATURE_SIZE, \
|
||||||
VERIFICATION_KEY_SIZE, \
|
VERIFICATION_KEY_SIZE, \
|
||||||
SHARE_HASH_CHAIN_SIZE
|
SHARE_HASH_CHAIN_SIZE
|
||||||
from allmydata.interfaces import BadWriteEnablerError
|
from allmydata.interfaces import (
|
||||||
|
BadWriteEnablerError, DataTooLargeError, ConflictingWriteError,
|
||||||
|
)
|
||||||
from allmydata.test.no_network import NoNetworkServer
|
from allmydata.test.no_network import NoNetworkServer
|
||||||
from allmydata.storage_client import (
|
from allmydata.storage_client import (
|
||||||
_StorageServer,
|
_StorageServer,
|
||||||
@ -123,8 +128,7 @@ class Bucket(unittest.TestCase):
|
|||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
incoming, final = self.make_workdir("test_create")
|
incoming, final = self.make_workdir("test_create")
|
||||||
bw = BucketWriter(self, incoming, final, 200, self.make_lease(),
|
bw = BucketWriter(self, incoming, final, 200, self.make_lease())
|
||||||
FakeCanary())
|
|
||||||
bw.remote_write(0, b"a"*25)
|
bw.remote_write(0, b"a"*25)
|
||||||
bw.remote_write(25, b"b"*25)
|
bw.remote_write(25, b"b"*25)
|
||||||
bw.remote_write(50, b"c"*25)
|
bw.remote_write(50, b"c"*25)
|
||||||
@ -133,8 +137,7 @@ class Bucket(unittest.TestCase):
|
|||||||
|
|
||||||
def test_readwrite(self):
|
def test_readwrite(self):
|
||||||
incoming, final = self.make_workdir("test_readwrite")
|
incoming, final = self.make_workdir("test_readwrite")
|
||||||
bw = BucketWriter(self, incoming, final, 200, self.make_lease(),
|
bw = BucketWriter(self, incoming, final, 200, self.make_lease())
|
||||||
FakeCanary())
|
|
||||||
bw.remote_write(0, b"a"*25)
|
bw.remote_write(0, b"a"*25)
|
||||||
bw.remote_write(25, b"b"*25)
|
bw.remote_write(25, b"b"*25)
|
||||||
bw.remote_write(50, b"c"*7) # last block may be short
|
bw.remote_write(50, b"c"*7) # last block may be short
|
||||||
@ -146,6 +149,88 @@ class Bucket(unittest.TestCase):
|
|||||||
self.failUnlessEqual(br.remote_read(25, 25), b"b"*25)
|
self.failUnlessEqual(br.remote_read(25, 25), b"b"*25)
|
||||||
self.failUnlessEqual(br.remote_read(50, 7), b"c"*7)
|
self.failUnlessEqual(br.remote_read(50, 7), b"c"*7)
|
||||||
|
|
||||||
|
def test_write_past_size_errors(self):
|
||||||
|
"""Writing beyond the size of the bucket throws an exception."""
|
||||||
|
for (i, (offset, length)) in enumerate([(0, 201), (10, 191), (202, 34)]):
|
||||||
|
incoming, final = self.make_workdir(
|
||||||
|
"test_write_past_size_errors-{}".format(i)
|
||||||
|
)
|
||||||
|
bw = BucketWriter(self, incoming, final, 200, self.make_lease())
|
||||||
|
with self.assertRaises(DataTooLargeError):
|
||||||
|
bw.remote_write(offset, b"a" * length)
|
||||||
|
|
||||||
|
@given(
|
||||||
|
maybe_overlapping_offset=strategies.integers(min_value=0, max_value=98),
|
||||||
|
maybe_overlapping_length=strategies.integers(min_value=1, max_value=100),
|
||||||
|
)
|
||||||
|
def test_overlapping_writes_ok_if_matching(
|
||||||
|
self, maybe_overlapping_offset, maybe_overlapping_length
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Writes that overlap with previous writes are OK when the content is the
|
||||||
|
same.
|
||||||
|
"""
|
||||||
|
length = 100
|
||||||
|
expected_data = b"".join(bchr(i) for i in range(100))
|
||||||
|
incoming, final = self.make_workdir("overlapping_writes_{}".format(uuid4()))
|
||||||
|
bw = BucketWriter(
|
||||||
|
self, incoming, final, length, self.make_lease(),
|
||||||
|
)
|
||||||
|
# Three writes: 10-19, 30-39, 50-59. This allows for a bunch of holes.
|
||||||
|
bw.remote_write(10, expected_data[10:20])
|
||||||
|
bw.remote_write(30, expected_data[30:40])
|
||||||
|
bw.remote_write(50, expected_data[50:60])
|
||||||
|
# Then, an overlapping write but with matching data:
|
||||||
|
bw.remote_write(
|
||||||
|
maybe_overlapping_offset,
|
||||||
|
expected_data[
|
||||||
|
maybe_overlapping_offset:maybe_overlapping_offset + maybe_overlapping_length
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Now fill in the holes:
|
||||||
|
bw.remote_write(0, expected_data[0:10])
|
||||||
|
bw.remote_write(20, expected_data[20:30])
|
||||||
|
bw.remote_write(40, expected_data[40:50])
|
||||||
|
bw.remote_write(60, expected_data[60:])
|
||||||
|
bw.remote_close()
|
||||||
|
|
||||||
|
br = BucketReader(self, bw.finalhome)
|
||||||
|
self.assertEqual(br.remote_read(0, length), expected_data)
|
||||||
|
|
||||||
|
|
||||||
|
@given(
|
||||||
|
maybe_overlapping_offset=strategies.integers(min_value=0, max_value=98),
|
||||||
|
maybe_overlapping_length=strategies.integers(min_value=1, max_value=100),
|
||||||
|
)
|
||||||
|
def test_overlapping_writes_not_ok_if_different(
|
||||||
|
self, maybe_overlapping_offset, maybe_overlapping_length
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Writes that overlap with previous writes fail with an exception if the
|
||||||
|
contents don't match.
|
||||||
|
"""
|
||||||
|
length = 100
|
||||||
|
incoming, final = self.make_workdir("overlapping_writes_{}".format(uuid4()))
|
||||||
|
bw = BucketWriter(
|
||||||
|
self, incoming, final, length, self.make_lease(),
|
||||||
|
)
|
||||||
|
# Three writes: 10-19, 30-39, 50-59. This allows for a bunch of holes.
|
||||||
|
bw.remote_write(10, b"1" * 10)
|
||||||
|
bw.remote_write(30, b"1" * 10)
|
||||||
|
bw.remote_write(50, b"1" * 10)
|
||||||
|
# Then, write something that might overlap with some of them, but
|
||||||
|
# conflicts. Then fill in holes left by first three writes. Conflict is
|
||||||
|
# inevitable.
|
||||||
|
with self.assertRaises(ConflictingWriteError):
|
||||||
|
bw.remote_write(
|
||||||
|
maybe_overlapping_offset,
|
||||||
|
b'X' * min(maybe_overlapping_length, length - maybe_overlapping_offset),
|
||||||
|
)
|
||||||
|
bw.remote_write(0, b"1" * 10)
|
||||||
|
bw.remote_write(20, b"1" * 10)
|
||||||
|
bw.remote_write(40, b"1" * 10)
|
||||||
|
bw.remote_write(60, b"1" * 40)
|
||||||
|
|
||||||
def test_read_past_end_of_share_data(self):
|
def test_read_past_end_of_share_data(self):
|
||||||
# test vector for immutable files (hard-coded contents of an immutable share
|
# test vector for immutable files (hard-coded contents of an immutable share
|
||||||
# file):
|
# file):
|
||||||
@ -168,7 +253,7 @@ class Bucket(unittest.TestCase):
|
|||||||
assert len(renewsecret) == 32
|
assert len(renewsecret) == 32
|
||||||
cancelsecret = b'THIS LETS ME KILL YOUR FILE HAHA'
|
cancelsecret = b'THIS LETS ME KILL YOUR FILE HAHA'
|
||||||
assert len(cancelsecret) == 32
|
assert len(cancelsecret) == 32
|
||||||
expirationtime = struct.pack('>L', 60*60*24*31) # 31 days in seconds
|
expirationtime = struct.pack('>L', DEFAULT_RENEWAL_TIME) # 31 days in seconds
|
||||||
|
|
||||||
lease_data = ownernumber + renewsecret + cancelsecret + expirationtime
|
lease_data = ownernumber + renewsecret + cancelsecret + expirationtime
|
||||||
|
|
||||||
@ -227,8 +312,7 @@ class BucketProxy(unittest.TestCase):
|
|||||||
final = os.path.join(basedir, "bucket")
|
final = os.path.join(basedir, "bucket")
|
||||||
fileutil.make_dirs(basedir)
|
fileutil.make_dirs(basedir)
|
||||||
fileutil.make_dirs(os.path.join(basedir, "tmp"))
|
fileutil.make_dirs(os.path.join(basedir, "tmp"))
|
||||||
bw = BucketWriter(self, incoming, final, size, self.make_lease(),
|
bw = BucketWriter(self, incoming, final, size, self.make_lease())
|
||||||
FakeCanary())
|
|
||||||
rb = RemoteBucket(bw)
|
rb = RemoteBucket(bw)
|
||||||
return bw, rb, final
|
return bw, rb, final
|
||||||
|
|
||||||
@ -354,10 +438,11 @@ class Server(unittest.TestCase):
|
|||||||
basedir = os.path.join("storage", "Server", name)
|
basedir = os.path.join("storage", "Server", name)
|
||||||
return basedir
|
return basedir
|
||||||
|
|
||||||
def create(self, name, reserved_space=0, klass=StorageServer):
|
def create(self, name, reserved_space=0, klass=StorageServer, get_current_time=time.time):
|
||||||
workdir = self.workdir(name)
|
workdir = self.workdir(name)
|
||||||
ss = klass(workdir, b"\x00" * 20, reserved_space=reserved_space,
|
ss = klass(workdir, b"\x00" * 20, reserved_space=reserved_space,
|
||||||
stats_provider=FakeStatsProvider())
|
stats_provider=FakeStatsProvider(),
|
||||||
|
get_current_time=get_current_time)
|
||||||
ss.setServiceParent(self.sparent)
|
ss.setServiceParent(self.sparent)
|
||||||
return ss
|
return ss
|
||||||
|
|
||||||
@ -384,8 +469,8 @@ class Server(unittest.TestCase):
|
|||||||
self.failUnlessIn(b'available-space', sv1)
|
self.failUnlessIn(b'available-space', sv1)
|
||||||
|
|
||||||
def allocate(self, ss, storage_index, sharenums, size, canary=None):
|
def allocate(self, ss, storage_index, sharenums, size, canary=None):
|
||||||
renew_secret = hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret))
|
renew_secret = hashutil.my_renewal_secret_hash(b"%d" % next(self._lease_secret))
|
||||||
cancel_secret = hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret))
|
cancel_secret = hashutil.my_cancel_secret_hash(b"%d" % next(self._lease_secret))
|
||||||
if not canary:
|
if not canary:
|
||||||
canary = FakeCanary()
|
canary = FakeCanary()
|
||||||
return ss.remote_allocate_buckets(storage_index,
|
return ss.remote_allocate_buckets(storage_index,
|
||||||
@ -577,26 +662,24 @@ class Server(unittest.TestCase):
|
|||||||
# the size we request.
|
# the size we request.
|
||||||
OVERHEAD = 3*4
|
OVERHEAD = 3*4
|
||||||
LEASE_SIZE = 4+32+32+4
|
LEASE_SIZE = 4+32+32+4
|
||||||
canary = FakeCanary(True)
|
canary = FakeCanary()
|
||||||
already, writers = self.allocate(ss, b"vid1", [0,1,2], 1000, canary)
|
already, writers = self.allocate(ss, b"vid1", [0,1,2], 1000, canary)
|
||||||
self.failUnlessEqual(len(writers), 3)
|
self.failUnlessEqual(len(writers), 3)
|
||||||
# now the StorageServer should have 3000 bytes provisionally
|
# now the StorageServer should have 3000 bytes provisionally
|
||||||
# allocated, allowing only 2000 more to be claimed
|
# allocated, allowing only 2000 more to be claimed
|
||||||
self.failUnlessEqual(len(ss._active_writers), 3)
|
self.failUnlessEqual(len(ss._bucket_writers), 3)
|
||||||
|
|
||||||
# allocating 1001-byte shares only leaves room for one
|
# allocating 1001-byte shares only leaves room for one
|
||||||
already2, writers2 = self.allocate(ss, b"vid2", [0,1,2], 1001, canary)
|
canary2 = FakeCanary()
|
||||||
|
already2, writers2 = self.allocate(ss, b"vid2", [0,1,2], 1001, canary2)
|
||||||
self.failUnlessEqual(len(writers2), 1)
|
self.failUnlessEqual(len(writers2), 1)
|
||||||
self.failUnlessEqual(len(ss._active_writers), 4)
|
self.failUnlessEqual(len(ss._bucket_writers), 4)
|
||||||
|
|
||||||
# we abandon the first set, so their provisional allocation should be
|
# we abandon the first set, so their provisional allocation should be
|
||||||
# returned
|
# returned
|
||||||
|
canary.disconnected()
|
||||||
|
|
||||||
del already
|
self.failUnlessEqual(len(ss._bucket_writers), 1)
|
||||||
del writers
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
self.failUnlessEqual(len(ss._active_writers), 1)
|
|
||||||
# now we have a provisional allocation of 1001 bytes
|
# now we have a provisional allocation of 1001 bytes
|
||||||
|
|
||||||
# and we close the second set, so their provisional allocation should
|
# and we close the second set, so their provisional allocation should
|
||||||
@ -605,25 +688,21 @@ class Server(unittest.TestCase):
|
|||||||
for bw in writers2.values():
|
for bw in writers2.values():
|
||||||
bw.remote_write(0, b"a"*25)
|
bw.remote_write(0, b"a"*25)
|
||||||
bw.remote_close()
|
bw.remote_close()
|
||||||
del already2
|
self.failUnlessEqual(len(ss._bucket_writers), 0)
|
||||||
del writers2
|
|
||||||
del bw
|
|
||||||
self.failUnlessEqual(len(ss._active_writers), 0)
|
|
||||||
|
|
||||||
# this also changes the amount reported as available by call_get_disk_stats
|
# this also changes the amount reported as available by call_get_disk_stats
|
||||||
allocated = 1001 + OVERHEAD + LEASE_SIZE
|
allocated = 1001 + OVERHEAD + LEASE_SIZE
|
||||||
|
|
||||||
# now there should be ALLOCATED=1001+12+72=1085 bytes allocated, and
|
# now there should be ALLOCATED=1001+12+72=1085 bytes allocated, and
|
||||||
# 5000-1085=3915 free, therefore we can fit 39 100byte shares
|
# 5000-1085=3915 free, therefore we can fit 39 100byte shares
|
||||||
already3, writers3 = self.allocate(ss, b"vid3", list(range(100)), 100, canary)
|
canary3 = FakeCanary()
|
||||||
|
already3, writers3 = self.allocate(ss, b"vid3", list(range(100)), 100, canary3)
|
||||||
self.failUnlessEqual(len(writers3), 39)
|
self.failUnlessEqual(len(writers3), 39)
|
||||||
self.failUnlessEqual(len(ss._active_writers), 39)
|
self.failUnlessEqual(len(ss._bucket_writers), 39)
|
||||||
|
|
||||||
del already3
|
canary3.disconnected()
|
||||||
del writers3
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
self.failUnlessEqual(len(ss._active_writers), 0)
|
self.failUnlessEqual(len(ss._bucket_writers), 0)
|
||||||
ss.disownServiceParent()
|
ss.disownServiceParent()
|
||||||
del ss
|
del ss
|
||||||
|
|
||||||
@ -646,6 +725,27 @@ class Server(unittest.TestCase):
|
|||||||
f2 = open(filename, "rb")
|
f2 = open(filename, "rb")
|
||||||
self.failUnlessEqual(f2.read(5), b"start")
|
self.failUnlessEqual(f2.read(5), b"start")
|
||||||
|
|
||||||
|
def create_bucket_5_shares(
|
||||||
|
self, ss, storage_index, expected_already=0, expected_writers=5
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Given a StorageServer, create a bucket with 5 shares and return renewal
|
||||||
|
and cancellation secrets.
|
||||||
|
"""
|
||||||
|
canary = FakeCanary()
|
||||||
|
sharenums = list(range(5))
|
||||||
|
size = 100
|
||||||
|
|
||||||
|
# Creating a bucket also creates a lease:
|
||||||
|
rs, cs = (hashutil.my_renewal_secret_hash(b"%d" % next(self._lease_secret)),
|
||||||
|
hashutil.my_cancel_secret_hash(b"%d" % next(self._lease_secret)))
|
||||||
|
already, writers = ss.remote_allocate_buckets(storage_index, rs, cs,
|
||||||
|
sharenums, size, canary)
|
||||||
|
self.failUnlessEqual(len(already), expected_already)
|
||||||
|
self.failUnlessEqual(len(writers), expected_writers)
|
||||||
|
for wb in writers.values():
|
||||||
|
wb.remote_close()
|
||||||
|
return rs, cs
|
||||||
|
|
||||||
def test_leases(self):
|
def test_leases(self):
|
||||||
ss = self.create("test_leases")
|
ss = self.create("test_leases")
|
||||||
@ -653,41 +753,23 @@ class Server(unittest.TestCase):
|
|||||||
sharenums = list(range(5))
|
sharenums = list(range(5))
|
||||||
size = 100
|
size = 100
|
||||||
|
|
||||||
rs0,cs0 = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
# Create a bucket:
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
rs0, cs0 = self.create_bucket_5_shares(ss, b"si0")
|
||||||
already,writers = ss.remote_allocate_buckets(b"si0", rs0, cs0,
|
|
||||||
sharenums, size, canary)
|
|
||||||
self.failUnlessEqual(len(already), 0)
|
|
||||||
self.failUnlessEqual(len(writers), 5)
|
|
||||||
for wb in writers.values():
|
|
||||||
wb.remote_close()
|
|
||||||
|
|
||||||
leases = list(ss.get_leases(b"si0"))
|
leases = list(ss.get_leases(b"si0"))
|
||||||
self.failUnlessEqual(len(leases), 1)
|
self.failUnlessEqual(len(leases), 1)
|
||||||
self.failUnlessEqual(set([l.renew_secret for l in leases]), set([rs0]))
|
self.failUnlessEqual(set([l.renew_secret for l in leases]), set([rs0]))
|
||||||
|
|
||||||
rs1,cs1 = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
rs1, cs1 = self.create_bucket_5_shares(ss, b"si1")
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
|
||||||
already,writers = ss.remote_allocate_buckets(b"si1", rs1, cs1,
|
|
||||||
sharenums, size, canary)
|
|
||||||
for wb in writers.values():
|
|
||||||
wb.remote_close()
|
|
||||||
|
|
||||||
# take out a second lease on si1
|
# take out a second lease on si1
|
||||||
rs2,cs2 = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
rs2, cs2 = self.create_bucket_5_shares(ss, b"si1", 5, 0)
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
|
||||||
already,writers = ss.remote_allocate_buckets(b"si1", rs2, cs2,
|
|
||||||
sharenums, size, canary)
|
|
||||||
self.failUnlessEqual(len(already), 5)
|
|
||||||
self.failUnlessEqual(len(writers), 0)
|
|
||||||
|
|
||||||
leases = list(ss.get_leases(b"si1"))
|
leases = list(ss.get_leases(b"si1"))
|
||||||
self.failUnlessEqual(len(leases), 2)
|
self.failUnlessEqual(len(leases), 2)
|
||||||
self.failUnlessEqual(set([l.renew_secret for l in leases]), set([rs1, rs2]))
|
self.failUnlessEqual(set([l.renew_secret for l in leases]), set([rs1, rs2]))
|
||||||
|
|
||||||
# and a third lease, using add-lease
|
# and a third lease, using add-lease
|
||||||
rs2a,cs2a = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
rs2a,cs2a = (hashutil.my_renewal_secret_hash(b"%d" % next(self._lease_secret)),
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
hashutil.my_cancel_secret_hash(b"%d" % next(self._lease_secret)))
|
||||||
ss.remote_add_lease(b"si1", rs2a, cs2a)
|
ss.remote_add_lease(b"si1", rs2a, cs2a)
|
||||||
leases = list(ss.get_leases(b"si1"))
|
leases = list(ss.get_leases(b"si1"))
|
||||||
self.failUnlessEqual(len(leases), 3)
|
self.failUnlessEqual(len(leases), 3)
|
||||||
@ -715,10 +797,10 @@ class Server(unittest.TestCase):
|
|||||||
"ss should not have a 'remote_cancel_lease' method/attribute")
|
"ss should not have a 'remote_cancel_lease' method/attribute")
|
||||||
|
|
||||||
# test overlapping uploads
|
# test overlapping uploads
|
||||||
rs3,cs3 = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
rs3,cs3 = (hashutil.my_renewal_secret_hash(b"%d" % next(self._lease_secret)),
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
hashutil.my_cancel_secret_hash(b"%d" % next(self._lease_secret)))
|
||||||
rs4,cs4 = (hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)),
|
rs4,cs4 = (hashutil.my_renewal_secret_hash(b"%d" % next(self._lease_secret)),
|
||||||
hashutil.tagged_hash(b"blah", b"%d" % next(self._lease_secret)))
|
hashutil.my_cancel_secret_hash(b"%d" % next(self._lease_secret)))
|
||||||
already,writers = ss.remote_allocate_buckets(b"si3", rs3, cs3,
|
already,writers = ss.remote_allocate_buckets(b"si3", rs3, cs3,
|
||||||
sharenums, size, canary)
|
sharenums, size, canary)
|
||||||
self.failUnlessEqual(len(already), 0)
|
self.failUnlessEqual(len(already), 0)
|
||||||
@ -741,6 +823,28 @@ class Server(unittest.TestCase):
|
|||||||
leases = list(ss.get_leases(b"si3"))
|
leases = list(ss.get_leases(b"si3"))
|
||||||
self.failUnlessEqual(len(leases), 2)
|
self.failUnlessEqual(len(leases), 2)
|
||||||
|
|
||||||
|
def test_immutable_add_lease_renews(self):
|
||||||
|
"""
|
||||||
|
Adding a lease on an already leased immutable with the same secret just
|
||||||
|
renews it.
|
||||||
|
"""
|
||||||
|
clock = Clock()
|
||||||
|
clock.advance(123)
|
||||||
|
ss = self.create("test_immutable_add_lease_renews", get_current_time=clock.seconds)
|
||||||
|
|
||||||
|
# Start out with single lease created with bucket:
|
||||||
|
renewal_secret, cancel_secret = self.create_bucket_5_shares(ss, b"si0")
|
||||||
|
[lease] = ss.get_leases(b"si0")
|
||||||
|
self.assertEqual(lease.expiration_time, 123 + DEFAULT_RENEWAL_TIME)
|
||||||
|
|
||||||
|
# Time passes:
|
||||||
|
clock.advance(123456)
|
||||||
|
|
||||||
|
# Adding a lease with matching renewal secret just renews it:
|
||||||
|
ss.remote_add_lease(b"si0", renewal_secret, cancel_secret)
|
||||||
|
[lease] = ss.get_leases(b"si0")
|
||||||
|
self.assertEqual(lease.expiration_time, 123 + 123456 + DEFAULT_RENEWAL_TIME)
|
||||||
|
|
||||||
def test_have_shares(self):
|
def test_have_shares(self):
|
||||||
"""By default the StorageServer has no shares."""
|
"""By default the StorageServer has no shares."""
|
||||||
workdir = self.workdir("test_have_shares")
|
workdir = self.workdir("test_have_shares")
|
||||||
@ -840,9 +944,10 @@ class MutableServer(unittest.TestCase):
|
|||||||
basedir = os.path.join("storage", "MutableServer", name)
|
basedir = os.path.join("storage", "MutableServer", name)
|
||||||
return basedir
|
return basedir
|
||||||
|
|
||||||
def create(self, name):
|
def create(self, name, get_current_time=time.time):
|
||||||
workdir = self.workdir(name)
|
workdir = self.workdir(name)
|
||||||
ss = StorageServer(workdir, b"\x00" * 20)
|
ss = StorageServer(workdir, b"\x00" * 20,
|
||||||
|
get_current_time=get_current_time)
|
||||||
ss.setServiceParent(self.sparent)
|
ss.setServiceParent(self.sparent)
|
||||||
return ss
|
return ss
|
||||||
|
|
||||||
@ -1046,23 +1151,6 @@ class MutableServer(unittest.TestCase):
|
|||||||
}))
|
}))
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
||||||
|
|
||||||
# as should this one
|
|
||||||
answer = write(b"si1", secrets,
|
|
||||||
{0: ([(10, 5, b"lt", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None),
|
|
||||||
},
|
|
||||||
[(10,5)],
|
|
||||||
)
|
|
||||||
self.failUnlessEqual(answer, (False,
|
|
||||||
{0: [b"11111"],
|
|
||||||
1: [b""],
|
|
||||||
2: [b""]},
|
|
||||||
))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
|
|
||||||
|
|
||||||
def test_operators(self):
|
def test_operators(self):
|
||||||
# test operators, the data we're comparing is '11111' in all cases.
|
# test operators, the data we're comparing is '11111' in all cases.
|
||||||
# test both fail+pass, reset data after each one.
|
# test both fail+pass, reset data after each one.
|
||||||
@ -1082,63 +1170,6 @@ class MutableServer(unittest.TestCase):
|
|||||||
|
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
# lt
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"lt", b"11110"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
self.failUnlessEqual(read(b"si1", [], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"lt", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"lt", b"11112"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
# le
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"le", b"11110"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"le", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"le", b"11112"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
# eq
|
# eq
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"eq", b"11112"),
|
answer = write(b"si1", secrets, {0: ([(10, 5, b"eq", b"11112"),
|
||||||
],
|
],
|
||||||
@ -1158,81 +1189,6 @@ class MutableServer(unittest.TestCase):
|
|||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
# ne
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"ne", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"ne", b"11112"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
# ge
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"ge", b"11110"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"ge", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"ge", b"11112"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
# gt
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"gt", b"11110"),
|
|
||||||
],
|
|
||||||
[(0, b"y"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (True, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [b"y"*100]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"gt", b"11111"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
answer = write(b"si1", secrets, {0: ([(10, 5, b"gt", b"11112"),
|
|
||||||
],
|
|
||||||
[(0, b"x"*100)],
|
|
||||||
None,
|
|
||||||
)}, [(10,5)])
|
|
||||||
self.failUnlessEqual(answer, (False, {0: [b"11111"]}))
|
|
||||||
self.failUnlessEqual(read(b"si1", [0], [(0,100)]), {0: [data]})
|
|
||||||
reset()
|
|
||||||
|
|
||||||
# finally, test some operators against empty shares
|
# finally, test some operators against empty shares
|
||||||
answer = write(b"si1", secrets, {1: ([(10, 5, b"eq", b"11112"),
|
answer = write(b"si1", secrets, {1: ([(10, 5, b"eq", b"11112"),
|
||||||
],
|
],
|
||||||
@ -1379,6 +1335,41 @@ class MutableServer(unittest.TestCase):
|
|||||||
{0: ([], [(500, b"make me really bigger")], None)}, [])
|
{0: ([], [(500, b"make me really bigger")], None)}, [])
|
||||||
self.compare_leases_without_timestamps(all_leases, list(s0.get_leases()))
|
self.compare_leases_without_timestamps(all_leases, list(s0.get_leases()))
|
||||||
|
|
||||||
|
def test_mutable_add_lease_renews(self):
|
||||||
|
"""
|
||||||
|
Adding a lease on an already leased mutable with the same secret just
|
||||||
|
renews it.
|
||||||
|
"""
|
||||||
|
clock = Clock()
|
||||||
|
clock.advance(235)
|
||||||
|
ss = self.create("test_mutable_add_lease_renews",
|
||||||
|
get_current_time=clock.seconds)
|
||||||
|
def secrets(n):
|
||||||
|
return ( self.write_enabler(b"we1"),
|
||||||
|
self.renew_secret(b"we1-%d" % n),
|
||||||
|
self.cancel_secret(b"we1-%d" % n) )
|
||||||
|
data = b"".join([ (b"%d" % i) * 10 for i in range(10) ])
|
||||||
|
write = ss.remote_slot_testv_and_readv_and_writev
|
||||||
|
write_enabler, renew_secret, cancel_secret = secrets(0)
|
||||||
|
rc = write(b"si1", (write_enabler, renew_secret, cancel_secret),
|
||||||
|
{0: ([], [(0,data)], None)}, [])
|
||||||
|
self.failUnlessEqual(rc, (True, {}))
|
||||||
|
|
||||||
|
bucket_dir = os.path.join(self.workdir("test_mutable_add_lease_renews"),
|
||||||
|
"shares", storage_index_to_dir(b"si1"))
|
||||||
|
s0 = MutableShareFile(os.path.join(bucket_dir, "0"))
|
||||||
|
[lease] = s0.get_leases()
|
||||||
|
self.assertEqual(lease.expiration_time, 235 + DEFAULT_RENEWAL_TIME)
|
||||||
|
|
||||||
|
# Time passes...
|
||||||
|
clock.advance(835)
|
||||||
|
|
||||||
|
# Adding a lease renews it:
|
||||||
|
ss.remote_add_lease(b"si1", renew_secret, cancel_secret)
|
||||||
|
[lease] = s0.get_leases()
|
||||||
|
self.assertEqual(lease.expiration_time,
|
||||||
|
235 + 835 + DEFAULT_RENEWAL_TIME)
|
||||||
|
|
||||||
def test_remove(self):
|
def test_remove(self):
|
||||||
ss = self.create("test_remove")
|
ss = self.create("test_remove")
|
||||||
self.allocate(ss, b"si1", b"we1", next(self._lease_secret),
|
self.allocate(ss, b"si1", b"we1", next(self._lease_secret),
|
||||||
|
@ -15,24 +15,19 @@ from past.builtins import chr as byteschr, long
|
|||||||
from six import ensure_text, ensure_str
|
from six import ensure_text, ensure_str
|
||||||
|
|
||||||
import os, re, sys, time, json
|
import os, re, sys, time, json
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from twisted.internet import reactor
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet.defer import inlineCallbacks
|
|
||||||
from twisted.application import service
|
|
||||||
|
|
||||||
from allmydata import client, uri
|
from allmydata import uri
|
||||||
from allmydata.introducer.server import create_introducer
|
|
||||||
from allmydata.storage.mutable import MutableShareFile
|
from allmydata.storage.mutable import MutableShareFile
|
||||||
from allmydata.storage.server import si_a2b
|
from allmydata.storage.server import si_a2b
|
||||||
from allmydata.immutable import offloaded, upload
|
from allmydata.immutable import offloaded, upload
|
||||||
from allmydata.immutable.literal import LiteralFileNode
|
from allmydata.immutable.literal import LiteralFileNode
|
||||||
from allmydata.immutable.filenode import ImmutableFileNode
|
from allmydata.immutable.filenode import ImmutableFileNode
|
||||||
from allmydata.util import idlib, mathutil, pollmixin, fileutil
|
from allmydata.util import idlib, mathutil
|
||||||
from allmydata.util import log, base32
|
from allmydata.util import log, base32
|
||||||
from allmydata.util.encodingutil import quote_output, unicode_to_argv
|
from allmydata.util.encodingutil import quote_output, unicode_to_argv
|
||||||
from allmydata.util.fileutil import abspath_expanduser_unicode
|
from allmydata.util.fileutil import abspath_expanduser_unicode
|
||||||
@ -43,32 +38,20 @@ from allmydata.monitor import Monitor
|
|||||||
from allmydata.mutable.common import NotWriteableError
|
from allmydata.mutable.common import NotWriteableError
|
||||||
from allmydata.mutable import layout as mutable_layout
|
from allmydata.mutable import layout as mutable_layout
|
||||||
from allmydata.mutable.publish import MutableData
|
from allmydata.mutable.publish import MutableData
|
||||||
from allmydata.scripts.runner import PYTHON_3_WARNING
|
|
||||||
|
|
||||||
from foolscap.api import DeadReferenceError, fireEventually, flushEventualQueue
|
from foolscap.api import DeadReferenceError, fireEventually
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from twisted.python.filepath import (
|
|
||||||
FilePath,
|
|
||||||
)
|
|
||||||
from twisted.internet.utils import (
|
from twisted.internet.utils import (
|
||||||
getProcessOutputAndValue,
|
getProcessOutputAndValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .common import (
|
|
||||||
TEST_RSA_KEY_SIZE,
|
|
||||||
SameProcessStreamEndpointAssigner,
|
|
||||||
)
|
|
||||||
from .common_web import do_http as do_http_bytes, Error
|
from .common_web import do_http as do_http_bytes, Error
|
||||||
from .web.common import (
|
from .web.common import (
|
||||||
assert_soup_has_tag_with_attributes
|
assert_soup_has_tag_with_attributes
|
||||||
)
|
)
|
||||||
|
from .common_system import SystemTestMixin
|
||||||
# TODO: move this to common or common_util
|
|
||||||
from . import common_util as testutil
|
|
||||||
from .common_util import run_cli_unicode
|
from .common_util import run_cli_unicode
|
||||||
from ..scripts.common import (
|
|
||||||
write_introducer,
|
|
||||||
)
|
|
||||||
|
|
||||||
class RunBinTahoeMixin(object):
|
class RunBinTahoeMixin(object):
|
||||||
def run_bintahoe(self, args, stdin=None, python_options=[], env=None):
|
def run_bintahoe(self, args, stdin=None, python_options=[], env=None):
|
||||||
@ -117,874 +100,6 @@ This is some data to publish to the remote grid.., which needs to be large
|
|||||||
enough to not fit inside a LIT uri.
|
enough to not fit inside a LIT uri.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# our system test uses the same Tub certificates each time, to avoid the
|
|
||||||
# overhead of key generation
|
|
||||||
SYSTEM_TEST_CERTS = [
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1iNV
|
|
||||||
z07PYwZwucl87QlL2TFZvDxD4flZ/p3BZE3DCT5Efn9w2NT4sHXL1e+R/qsDFuNG
|
|
||||||
bw1y1TRM0DGK6Wr0XRT2mLQULNgB8y/HrhcSdONsYRyWdj+LimyECKjwh0iSkApv
|
|
||||||
Yj/7IOuq6dOoh67YXPdf75OHLShm4+8q8fuwhBL+nuuO4NhZDJKupYHcnuCkcF88
|
|
||||||
LN77HKrrgbpyVmeghUkwJMLeJCewvYVlambgWRiuGGexFgAm6laS3rWetOcdm9eg
|
|
||||||
FoA9PKNN6xvPatbj99MPoLpBbzsI64M0yT/wTSw1pj/Nom3rwfMa2OH8Kk7c8R/r
|
|
||||||
U3xj4ZY1DTlGERvejQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAwyQjQ3ZgtJ3JW
|
|
||||||
r3/EPdqSUBamTfXIpOh9rXmRjPpbe+MvenqIzl4q+GnkL5mdEb1e1hdKQZgFQ5Q5
|
|
||||||
tbcNIz6h5C07KaNtbqhZCx5c/RUEH87VeXuAuOqZHbZWJ18q0tnk+YgWER2TOkgE
|
|
||||||
RI2AslcsJBt88UUOjHX6/7J3KjPFaAjW1QV3TTsHxk14aYDYJwPdz+ijchgbOPQ0
|
|
||||||
i+ilhzcB+qQnOC1s4xQSFo+zblTO7EgqM9KpupYfOVFh46P1Mak2W8EDvhz0livl
|
|
||||||
OROXJ6nR/13lmQdfVX6T45d+ITBwtmW2nGAh3oI3JlArGKHaW+7qnuHR72q9FSES
|
|
||||||
cEYA/wmk
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWI1XPTs9jBnC5
|
|
||||||
yXztCUvZMVm8PEPh+Vn+ncFkTcMJPkR+f3DY1PiwdcvV75H+qwMW40ZvDXLVNEzQ
|
|
||||||
MYrpavRdFPaYtBQs2AHzL8euFxJ042xhHJZ2P4uKbIQIqPCHSJKQCm9iP/sg66rp
|
|
||||||
06iHrthc91/vk4ctKGbj7yrx+7CEEv6e647g2FkMkq6lgdye4KRwXzws3vscquuB
|
|
||||||
unJWZ6CFSTAkwt4kJ7C9hWVqZuBZGK4YZ7EWACbqVpLetZ605x2b16AWgD08o03r
|
|
||||||
G89q1uP30w+gukFvOwjrgzTJP/BNLDWmP82ibevB8xrY4fwqTtzxH+tTfGPhljUN
|
|
||||||
OUYRG96NAgMBAAECggEAJ5xztBx0+nFnisZ9yG8uy6d4XPyc5gE1J4dRDdfgmyYc
|
|
||||||
j3XNjx6ePi4cHZ/qVryVnrc+AS7wrgW1q9FuS81QFKPbFdZB4SW3/p85BbgY3uxu
|
|
||||||
0Ovz3T3V9y4polx12eCP0/tKLVd+gdF2VTik9Sxfs5rC8VNN7wmJNuK4A/k15sgy
|
|
||||||
BIu/R8NlMNGQySNhtccp+dzB8uTyKx5zFZhVvnAK/3YX9BC2V4QBW9JxO4S8N0/9
|
|
||||||
48e9Sw/fGCfQ/EFPKGCvTvfuRqJ+4t5k10FygXJ+s+y70ifYi+aSsjJBuranbLJp
|
|
||||||
g5TwhuKnTWs8Nth3YRLbcJL4VBIOehjAWy8pDMMtlQKBgQD0O8cHb8cOTGW0BijC
|
|
||||||
NDofhA2GooQUUR3WL324PXWZq0DXuBDQhJVBKWO3AYonivhhd/qWO8lea9MEmU41
|
|
||||||
nKZ7maS4B8AJLJC08P8GL1uCIE/ezEXEi9JwC1zJiyl595Ap4lSAozH0DwjNvmGL
|
|
||||||
5mIdYg0BliqFXbloNJkNlb7INwKBgQDgdGEIWXc5Y1ncWNs6iDIV/t2MlL8vLrP0
|
|
||||||
hpkl/QiMndOQyD6JBo0+ZqvOQTSS4NTSxBROjPxvFbEJ3eH8Pmn8gHOf46fzP1OJ
|
|
||||||
wlYv0gYzkN4FE/tN6JnO2u9pN0euyyZLM1fnEcrMWColMN8JlWjtA7Gbxm8lkfa4
|
|
||||||
3vicaJtlWwKBgQCQYL4ZgVR0+Wit8W4qz+EEPHYafvwBXqp6sXxqa7qXawtb+q3F
|
|
||||||
9nqdGLCfwMNA+QA37ksugI1byfXmpBH902r/aiZbvAkj4zpwHH9F0r0PwbY1iSA9
|
|
||||||
PkLahX0Gj8OnHFgWynsVyGOBWVnk9oSHxVt+7zWtGG5uhKdUGLPZugocJQKBgB61
|
|
||||||
7bzduOFiRZ5PjhdxISE/UQL2Kz6Cbl7rt7Kp72yF/7eUnnHTMqoyFBnRdCcQmi4I
|
|
||||||
ZBrnUXbFigamlFAWHhxNWwSqeoVeychUjcRXQT/291nMhRsA02KpNA66YJV6+E9b
|
|
||||||
xBA6r/vLqGCUUkAWcFfVpIyC1xxV32MmJvAHpBN3AoGAPF3MUFiO0iKNZfst6Tm3
|
|
||||||
rzrldLawDo98DRZ7Yb2kWlWZYqUk/Nvryvo2cns75WGSMDYVbbRp+BY7kZmNYa9K
|
|
||||||
iQzKDL54ZRu6V+getJdeAO8yXoCmnZKxt5OHvOSrQMfAmFKSwLwxBbZBfXEyuune
|
|
||||||
yfusXLtCgajpreoVIa0xWdQ=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 0
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApDzW
|
|
||||||
4ZBeK9w4xpRaed6lXzeCO0Xmr3f0ynbueSdiZ89FWoAMgK+SiBIOViYV6hfm0Wah
|
|
||||||
lemSNzFGx5LvDSg2uwSqEP23DeM9O/SQPgIAiLeeEsYZJcgg2jz92YfFEaahsGdI
|
|
||||||
6qSP4XI2/5dgKRpPOYDGyw6R5PQR6w22Xq1WD1jBvImk/k09I9jHRn40pYbaJzbg
|
|
||||||
U2aIjvOruo2kqe4f6iDqE0piYimAZJUvemu1UoyV5NG590hGkDuWsMD77+d2FxCj
|
|
||||||
9Nzb+iuuG3ksnanHPyXi1hQmzp5OmzVWaevCHinNjWgsuSuLGO9H2SLf3wwp2UCs
|
|
||||||
EpKtzoKrnZdEg/anNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQChxtr67o1aZZMJ
|
|
||||||
A6gESPtFjZLw6wG0j50JsrWKLvoXVts1ToJ9u2nx01aFKjBwb4Yg+vdJfDgIIAEm
|
|
||||||
jS56h6H2DfJlkTWHmi8Vx1wuusWnrNwYMI53tdlRIpD2+Ne7yeoLQZcVN2wuPmxD
|
|
||||||
Mbksg4AI4csmbkU/NPX5DtMy4EzM/pFvIcxNIVRUMVTFzn5zxhKfhyPqrMI4fxw1
|
|
||||||
UhUbEKO+QgIqTNp/dZ0lTbFs5HJQn6yirWyyvQKBPmaaK+pKd0RST/T38OU2oJ/J
|
|
||||||
LojRs7ugCJ+bxJqegmQrdcVqZZGbpYeK4O/5eIn8KOlgh0nUza1MyjJJemgBBWf7
|
|
||||||
HoXB8Fge
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkPNbhkF4r3DjG
|
|
||||||
lFp53qVfN4I7Reavd/TKdu55J2Jnz0VagAyAr5KIEg5WJhXqF+bRZqGV6ZI3MUbH
|
|
||||||
ku8NKDa7BKoQ/bcN4z079JA+AgCIt54SxhklyCDaPP3Zh8URpqGwZ0jqpI/hcjb/
|
|
||||||
l2ApGk85gMbLDpHk9BHrDbZerVYPWMG8iaT+TT0j2MdGfjSlhtonNuBTZoiO86u6
|
|
||||||
jaSp7h/qIOoTSmJiKYBklS96a7VSjJXk0bn3SEaQO5awwPvv53YXEKP03Nv6K64b
|
|
||||||
eSydqcc/JeLWFCbOnk6bNVZp68IeKc2NaCy5K4sY70fZIt/fDCnZQKwSkq3Ogqud
|
|
||||||
l0SD9qc3AgMBAAECggEBAIu55uaIOFYASZ1IYaEFNpRHWVisI5Js76nAfSo9w46l
|
|
||||||
3E8eWYSx2mxBUEkipco/A3RraFVuHaMvHRR1gUMkT0vUsAs8jxwVk+cKLh1S/rlR
|
|
||||||
3f4C4yotlSWWdjE3PQXDShQWCwb1ciNPVFMmqfzOEVDOqlHe12h97TCYverWdT0f
|
|
||||||
3LZICLQsZd1WPKnPNXrsRRDCBuRLapdg+M0oJ+y6IiCdm+qM7Qvaoef6hlvm5ECz
|
|
||||||
LCM92db5BKTuPOQXMx2J8mjaBgU3aHxRV08IFgs7mI6q0t0FM7LlytIAJq1Hg5QU
|
|
||||||
36zDKo8tblkPijWZWlqlZCnlarrd3Ar/BiLEiuOGDMECgYEA1GOp0KHy0mbJeN13
|
|
||||||
+TDsgP7zhmqFcuJREu2xziNJPK2S06NfGYE8vuVqBGzBroLTQ3dK7rOJs9C6IjCE
|
|
||||||
mH7ZeHzfcKohpZnl443vHMSpgdh/bXTEO1aQZNbJ2hLYs8ie/VqqHR0u6YtpUqZL
|
|
||||||
LgaUA0U8GnlsO55B8kyCelckmDkCgYEAxfYQMPEEzg1tg2neqEfyoeY0qQTEJTeh
|
|
||||||
CPMztowSJpIyF1rQH6TaG0ZchkiAkw3W58RVDfvK72TuVlC5Kz00C2/uPnrqm0dX
|
|
||||||
iMPeML5rFlG3VGCrSTnAPI+az6P65q8zodqcTtA8xoxgPOlc/lINOxiTEMxLyeGF
|
|
||||||
8GyP+sCM2u8CgYEAvMBR05OJnEky9hJEpBZBqSZrQGL8dCwDh0HtCdi8JovPd/yx
|
|
||||||
8JW1aaWywXnx6uhjXoru8hJm54IxWV8rB+d716OKY7MfMfACqWejQDratgW0wY7L
|
|
||||||
MjztGGD2hLLJGYXLHjfsBPHBllaKZKRbHe1Er19hWdndQWKVEwPB1X4KjKkCgYEA
|
|
||||||
nWHmN3K2djbYtRyLR1CEBtDlVuaSJmCWp23q1BuCJqYeKtEpG69NM1f6IUws5Dyh
|
|
||||||
eXtuf4KKMU8V6QueW1D6OomPaJ8CO9c5MWM/F5ObwY/P58Y/ByVhvwQQeToONC5g
|
|
||||||
JzKNCF+nodZigKqrIwoKuMvtx/IT4vloKd+1jA5fLYMCgYBoT3HLCyATVdDSt1TZ
|
|
||||||
SbEDoLSYt23KRjQV93+INP949dYCagtgh/kTzxBopw5FljISLfdYizIRo2AzhhfP
|
|
||||||
WWpILlnt19kD+sNirJVqxJacfEZsu5baWTedI/yrCuVsAs/s3/EEY6q0Qywknxtp
|
|
||||||
Fwh1/8y5t14ib5fxOVhi8X1nEA==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 1
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwMTn
|
|
||||||
hXnpKHGAir3WYbOxefVrMA07OZNAsNa29nBwLA+NVIJNUFgquibMj7QYo8+M45oY
|
|
||||||
6LKr4yRcBryZVvyxfdr92xp8+kLeVApk2WLjkdBTRagHh9qdrY0hQmagCBN6/hLG
|
|
||||||
Xug8VksQUdhX3vu6ZyMvTLfKRkDOMRVkRGRGg/dOcvom7zpqMCGYenMG2FStr6UV
|
|
||||||
3s3dlCSZZTdTX5Uoq6yfUUJE3nITGKjpnpJKqIs3PWCIxdj7INIcjJKvIdUcavIV
|
|
||||||
2hEhh60A8ltmtdpQAXVBE+U7aZgS1fGAWS2A0a3UwuP2pkQp6OyKCUVHpZatbl9F
|
|
||||||
ahDN2QBzegv/rdJ1zwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAl4OQZ+FB9ZSUv
|
|
||||||
FL/KwLNt+ONU8Sve/xiX+8vKAvgKm2FrjyK+AZPwibnu+FSt2G4ndZBx4Wvpe5V+
|
|
||||||
gCsbzSXlh9cDn2SRXyprt2l/8Fj4eUMaThmLKOK200/N/s2SpmBtnuflBrhNaJpw
|
|
||||||
DEi2KEPuXsgvkuVzXN06j75cUHwn5LeWDAh0RalkVuGbEWBoFx9Hq8WECdlCy0YS
|
|
||||||
y09+yO01qz70y88C2rPThKw8kP4bX8aFZbvsnRHsLu/8nEQNlrELcfBarPVHjJ/9
|
|
||||||
imxOdymJkV152V58voiXP/PwXhynctQbF7e+0UZ+XEGdbAbZA0BMl7z+b09Z+jF2
|
|
||||||
afm4mVox
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAxOeFeekocYCK
|
|
||||||
vdZhs7F59WswDTs5k0Cw1rb2cHAsD41Ugk1QWCq6JsyPtBijz4zjmhjosqvjJFwG
|
|
||||||
vJlW/LF92v3bGnz6Qt5UCmTZYuOR0FNFqAeH2p2tjSFCZqAIE3r+EsZe6DxWSxBR
|
|
||||||
2Ffe+7pnIy9Mt8pGQM4xFWREZEaD905y+ibvOmowIZh6cwbYVK2vpRXezd2UJJll
|
|
||||||
N1NflSirrJ9RQkTechMYqOmekkqoizc9YIjF2Psg0hyMkq8h1Rxq8hXaESGHrQDy
|
|
||||||
W2a12lABdUET5TtpmBLV8YBZLYDRrdTC4/amRCno7IoJRUellq1uX0VqEM3ZAHN6
|
|
||||||
C/+t0nXPAgMBAAECggEAF+2ZK4lZdsq4AQDVhqUuh4v+NSW/T0NHCWxto6OLWPzJ
|
|
||||||
N09BV5LKIvdD9yaM1HCj9XCgXOooyfYuciuhARo20f+H+VWNY+c+/8GWiSFsTCJG
|
|
||||||
4+Oao7NwVSWqljp07Ou2Hamo9AjxzGhe6znmlmg62CiW63f45MWQkqksHA0yb5jg
|
|
||||||
/onJ2//I+OI+aTKNfjt1G6h2x7oxeGTU1jJ0Hb2xSh+Mpqx9NDfb/KZyOndhSG5N
|
|
||||||
xRVosQ6uV+9mqHxTTwTZurTG31uhZzarkMuqxhcHS94ub7berEc/OlqvbyMKNZ3A
|
|
||||||
lzuvq0NBZhEUhAVgORAIS17r/q2BvyG4u5LFbG2p0QKBgQDeyyOl+A7xc4lPE2OL
|
|
||||||
Z3KHJPP4RuUnHnWFC+bNdr5Ag8K7jcjZIcasyUom9rOR0Fpuw9wmXpp3+6fyp9bJ
|
|
||||||
y6Bi5VioR0ZFP5X+nXxIN3yvgypu6AZvkhHrEFer+heGHxPlbwNKCKMbPzDZPBTZ
|
|
||||||
vlC7g7xUUcpNmGhrOKr3Qq5FlwKBgQDdgCmRvsHUyzicn8TI3IJBAOcaQG0Yr/R2
|
|
||||||
FzBqNfHHx7fUZlJfKJsnu9R9VRZmBi4B7MA2xcvz4QrdZWEtY8uoYp8TAGILfW1u
|
|
||||||
CP4ZHrzfDo/67Uzk2uTMTd0+JOqSm/HiVNguRPvC8EWBoFls+h129GKThMvKR1hP
|
|
||||||
1oarfAGIiQKBgQCIMAq5gHm59JMhqEt4QqMKo3cS9FtNX1wdGRpbzFMd4q0dstzs
|
|
||||||
ha4Jnv3Z9YHtBzzQap9fQQMRht6yARDVx8hhy6o3K2J0IBtTSfdXubtZGkfNBb4x
|
|
||||||
Y0vaseG1uam5jbO+0u5iygbSN/1nPUfNln2JMkzkCh8s8ZYavMgdX0BiPwKBgChR
|
|
||||||
QL/Hog5yoy5XIoGRKaBdYrNzkKgStwObuvNKOGUt5DckHNA3Wu6DkOzzRO1zKIKv
|
|
||||||
LlmJ7VLJ3qln36VcaeCPevcBddczkGyb9GxsHOLZCroY4YsykLzjW2cJXy0qd3/E
|
|
||||||
A8mAQvc7ttsebciZSi2x1BOX82QxUlDN8ptaKglJAoGBAMnLN1TQB0xtWYDPGcGV
|
|
||||||
2IvgX7OTRRlMVrTvIOvP5Julux9z1r0x0cesl/jaXupsBUlLLicPyBMSBJrXlr24
|
|
||||||
mrgkodk4TdqO1VtBCZBqak97DHVezstMrbpCGlUD5jBnsHVRLERvS09QlGhqMeNL
|
|
||||||
jpNQbWH9VhutzbvpYquKrhvK
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 2
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAypqi
|
|
||||||
YTni3s60Uo8vgGcFvjWWkB5CD9Fx9pW/2KcxRJ/u137Y+BG8qWMA4lgII3ZIuvo4
|
|
||||||
6rLDiXnAnDZqUtrvZ90O/gH6RyQqX3AI4EwPvCnRIIe0okRcxnxYBL/LfBY54xuv
|
|
||||||
46JRYZP4c9IImqQH9QVo2/egtEzcpbmT/mfhpf6NGQWC3Xps2BqDT2SV/DrX/wPA
|
|
||||||
8P1atE1AxNp8ENxK/cjFAteEyDZOsDSa757ZHKAdM7L8rZ1Fd2xAA1Dq7IyYpTNE
|
|
||||||
IX72xytWxllcNvSUPLT+oicsSZBadc/p3moc3tR/rNdgrHKybedadru/f9Gwpa+v
|
|
||||||
0sllZlEcVPSYddAzWwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmk60Nj5FPvemx
|
|
||||||
DSSQjJPyJoIDpTxQ4luSzIq4hPwlUXw7dqrvHyCWgn2YVe9xZsGrT/+n376ecmgu
|
|
||||||
sw4s4qVhR9bzKkTMewjC2wUooTA5v9HYsNWZy3Ah7hHPbDHlMADYobjB5/XolNUP
|
|
||||||
bCM9xALEdM9DxpC4vjUZexlRKmjww9QKE22jIM+bqsK0zqDSq+zHpfHNGGcS3vva
|
|
||||||
OvI6FPc1fAr3pZpVzevMSN2zufIJwjL4FT5/uzwOCaSCwgR1ztD5CSbQLTLlwIsX
|
|
||||||
S7h2WF9078XumeRjKejdjEjyH4abKRq8+5LVLcjKEpg7OvktuRpPoGPCEToaAzuv
|
|
||||||
h+RSQwwY
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDKmqJhOeLezrRS
|
|
||||||
jy+AZwW+NZaQHkIP0XH2lb/YpzFEn+7Xftj4EbypYwDiWAgjdki6+jjqssOJecCc
|
|
||||||
NmpS2u9n3Q7+AfpHJCpfcAjgTA+8KdEgh7SiRFzGfFgEv8t8FjnjG6/jolFhk/hz
|
|
||||||
0giapAf1BWjb96C0TNyluZP+Z+Gl/o0ZBYLdemzYGoNPZJX8Otf/A8Dw/Vq0TUDE
|
|
||||||
2nwQ3Er9yMUC14TINk6wNJrvntkcoB0zsvytnUV3bEADUOrsjJilM0QhfvbHK1bG
|
|
||||||
WVw29JQ8tP6iJyxJkFp1z+neahze1H+s12CscrJt51p2u79/0bClr6/SyWVmURxU
|
|
||||||
9Jh10DNbAgMBAAECggEBALv7Q+Rf+C7wrQDZF6LUc9CrGfq4CGVy2IGJKgqT/jOF
|
|
||||||
DO9nI1rv4hNr55sbQNneWtcZaYvht2mrzNlj57zepDjDM7DcFuLBHIuWgLXT/NmC
|
|
||||||
FyZOo3vXYBlNr8EgT2XfnXAp9UWJCmc2CtUzsIYC4dsmXMeTd8kyc5tUl4r5ybTf
|
|
||||||
1g+RTck/IGgqdfzpuTsNl79FW2rP9z111Py6dbqgQzhuSAune9dnLFvZst8dyL8j
|
|
||||||
FStETMxBM6jrCF1UcKXzG7trDHiCdzJ8WUhx6opN/8OasQGndwpXto6FZuBy/AVP
|
|
||||||
4kVQNpUXImYcLEpva0MqGRHg+YN+c84C71CMchnF4aECgYEA7J2go4CkCcZNKCy5
|
|
||||||
R5XVCqNFYRHjekR+UwH8cnCa7pMKKfP+lTCiBrO2q8zwWwknRMyuycS5g/xbSpg1
|
|
||||||
L6hi92CV1YQy1/JhlQedekjejNTTuLOPKf78AFNSfc7axDnes2v4Bvcdp9gsbUIO
|
|
||||||
10cXh0tOSLE7P9y+yC86KQkFAPECgYEA2zO0M2nvbPHv2jjtymY3pflYm0HzhM/T
|
|
||||||
kPtue3GxOgbEPsHffBGssShBTE3yCOX3aAONXJucMrSAPL9iwUfgfGx6ADdkwBsA
|
|
||||||
OjDlkxvTbP/9trE6/lsSPtGpWRdJNHqXN4Hx7gXJizRwG7Ym+oHvIIh53aIjdFoE
|
|
||||||
HLQLpxObuQsCgYAuMQ99G83qQpYpc6GwAeYXL4yJyK453kky9z5LMQRt8rKXQhS/
|
|
||||||
F0FqQYc1vsplW0IZQkQVC5yT0Z4Yz+ICLcM0O9zEVAyA78ZxC42Io9UedSXn9tXK
|
|
||||||
Awc7IQkHmmxGxm1dZYSEB5X4gFEb+zted3h2ZxMfScohS3zLI70c6a/aYQKBgQCU
|
|
||||||
phRuxUkrTUpFZ1PCbN0R/ezbpLbaewFTEV7T8b6oxgvxLxI6FdZRcSYO89DNvf2w
|
|
||||||
GLCVe6VKMWPBTlxPDEostndpjCcTq3vU+nHE+BrBkTvh14BVGzddSFsaYpMvNm8z
|
|
||||||
ojiJHH2XnCDmefkm6lRacJKL/Tcj4SNmv6YjUEXLDwKBgF8WV9lzez3d/X5dphLy
|
|
||||||
2S7osRegH99iFanw0v5VK2HqDcYO9A7AD31D9nwX46QVYfgEwa6cHtVCZbpLeJpw
|
|
||||||
qXnYXe/hUU3yn5ipdNJ0Dm/ZhJPDD8TeqhnRRhxbZmsXs8EzfwB2tcUbASvjb3qA
|
|
||||||
vAaPlOSU1wXqhAsG9aVs8gtL
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 3
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUqQ
|
|
||||||
M08E7F2ZE99bFHvpsR6LmgIJOOoGMXacTcEUhRF63E6+730FjxER2a30synv9GGS
|
|
||||||
3G9FstUmfhyimufkbTumri8Novw5CWZQLiE1rmMBI5nPcR2wAzy9z2odR6bfAwms
|
|
||||||
yyc3IPYg1BEDBPZl0LCQrQRRU/rVOrbCf7IMq+ATazmBg01gXMzq2M953ieorkQX
|
|
||||||
MsHVR/kyW0Q0yzhYF1OtIqbXxrdiZ+laTLWNqivj/FdegiWPCf8OcqpcpbgEjlDW
|
|
||||||
gBcC/vre+0E+16nfUV8xHL5jseJMJqfT508OtHxAzp+2D7b54NvYNIvbOAP+F9gj
|
|
||||||
aXy5mOvjXclK+hNmDwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAjZzTFKG7uoXxm
|
|
||||||
BPHfQvsKHIB/Cx9zMKj6pLwJzCPHQBzKOMoUen09oq+fb77RM7WvdX0pvFgEXaJW
|
|
||||||
q/ImooRMo+paf8GOZAuPwdafb2/OGdHZGZ2Cbo/ICGo1wGDCdMvbxTxrDNq1Yae+
|
|
||||||
m+2epN2pXAO1rlc7ktRkojM/qi3zXtbLjTs3IoPDXWhYPHdI1ThkneRmvxpzB1rW
|
|
||||||
2SBqj2snvyI+/3k3RHmldcdOrTlgWQ9hq05jWR8IVtRUFFVn9A+yQC3gnnLIUhwP
|
|
||||||
HJWwTIPuYW25TuxFxYZXIbnAiluZL0UIjd3IAwxaafvB6uhI7v0K789DKj2vRUkY
|
|
||||||
E8ptxZH4
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDNSpAzTwTsXZkT
|
|
||||||
31sUe+mxHouaAgk46gYxdpxNwRSFEXrcTr7vfQWPERHZrfSzKe/0YZLcb0Wy1SZ+
|
|
||||||
HKKa5+RtO6auLw2i/DkJZlAuITWuYwEjmc9xHbADPL3Pah1Hpt8DCazLJzcg9iDU
|
|
||||||
EQME9mXQsJCtBFFT+tU6tsJ/sgyr4BNrOYGDTWBczOrYz3neJ6iuRBcywdVH+TJb
|
|
||||||
RDTLOFgXU60iptfGt2Jn6VpMtY2qK+P8V16CJY8J/w5yqlyluASOUNaAFwL++t77
|
|
||||||
QT7Xqd9RXzEcvmOx4kwmp9PnTw60fEDOn7YPtvng29g0i9s4A/4X2CNpfLmY6+Nd
|
|
||||||
yUr6E2YPAgMBAAECggEBAIiL6uQl0AmDrBj6vHMghGzp+0MBza6MgngOA6L4JTTp
|
|
||||||
ToYQ3pEe4D6rxOq7+QHeiBtNd0ilvn9XpVXGqCVOzrIVNiWvaGubRjjJU9WLA1Ct
|
|
||||||
y4kpekAr1fIhScMXOsh45ub3XXZ27AVBkM5dTlvTpB8uAd0C/TFVqtR10WLsQ99h
|
|
||||||
Zm9Jczgs/6InYTssnAaqdeCLAf1LbmO4zwFsJfJOeSGGT6WBwlpHwMAgPhg8OLEu
|
|
||||||
kVWG7BEJ0hxcODk/es/vce9SN7BSyIzNY+qHcGtsrx/o0eO2Av/Z7ltV4Sz6UN1K
|
|
||||||
0y0OTiDyT/l62U2OugSN3wQ4xPTwlrWl7ZUHJmvpEaECgYEA+w2JoB2i1OV2JTPl
|
|
||||||
Y0TKSKcZYdwn7Nwh4fxMAJNJ8UbpPqrZEo37nxqlWNJrY/jKX3wHVk4ESSTaxXgF
|
|
||||||
UY7yKT0gRuD9+vE0gCbUmJQJTwbceNJUu4XrJ6SBtf72WgmphL+MtyKdwV8XltVl
|
|
||||||
Yp0hkswGmxl+5+Js6Crh7WznPl8CgYEA0VYtKs2YaSmT1zraY6Fv3AIQZq012vdA
|
|
||||||
7nVxmQ6jKDdc401OWARmiv0PrZaVNiEJ1YV8KxaPrKTfwhWqxNegmEBgA1FZ66NN
|
|
||||||
SAm8P9OCbt8alEaVkcATveXTeOCvfpZUO3sqZdDOiYLiLCsokHblkcenK85n0yT6
|
|
||||||
CzhTbvzDllECgYEAu9mfVy2Vv5OK2b+BLsw0SDSwa2cegL8eo0fzXqLXOzCCKqAQ
|
|
||||||
GTAgTSbU/idEr+NjGhtmKg/qaQioogVyhVpenLjeQ+rqYDDHxfRIM3rhlD5gDg/j
|
|
||||||
0wUbtegEHrgOgcSlEW16zzWZsS2EKxq16BoHGx6K+tcS/FOShg5ASzWnuiUCgYEA
|
|
||||||
sMz+0tLX8aG7CqHbRyBW8FMR9RY/kRMY1Q1+Bw40wMeZfSSSkYYN8T9wWWT/2rqm
|
|
||||||
qp7V0zJ34BFUJoDUPPH84fok3Uh9EKZYpAoM4z9JP0jREwBWXMYEJnOQWtwxfFGN
|
|
||||||
DLumgF2Nwtg3G6TL2s+AbtJYH4hxagQl5woIdYmnyzECgYEAsLASpou16A3uXG5J
|
|
||||||
+5ZgF2appS9Yfrqfh6TKywMsGG/JuiH3djdYhbJFIRGeHIIDb4XEXOHrg/SFflas
|
|
||||||
If0IjFRh9WCvQxnoRha3/pKRSc3OEka1MR/ZREK/d/LQEPmsRJVzY6ABKqmPAMDD
|
|
||||||
5CnG6Hz/rP87BiEKd1+3PGp8GCw=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 4
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sap
|
|
||||||
75YbbkEL85LFava3FrO1jpgVteQ4NGxxy1Nu9w2hPfMMeCPWjB8UfAwFk+LVPyvW
|
|
||||||
LAXd1zWL5rGpQ2ytIVQlTraR5EnALA1sMcQYbFz1ISPTYB031bEN/Ch8JWYwCG5A
|
|
||||||
X2H4D6BC7NgT6YyWDt8vxQnqAisPHQ/OK4ABD15CwkTyPimek2/ufYN2dapg1xhG
|
|
||||||
IUD96gqetJv9bu0r869s688kADIComsYG+8KKfFN67S3rSHMIpZPuGTtoHGnVO89
|
|
||||||
XBm0vNe0UxQkJEGJzZPn0tdec0LTC4GNtTaz5JuCjx/VsJBqrnTnHHjx0wFz8pff
|
|
||||||
afCimRwA+LCopxPE1QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBOkAnpBb3nY+dG
|
|
||||||
mKCjiLqSsuEPqpNiBYR+ue/8aVDnOKLKqAyQuyRZttQ7bPpKHaw7pwyCZH8iHnt6
|
|
||||||
pMCLCftNSlV2Fa8msRmuf5AiGjUvR1M8VtHWNYE8pedWrJqUgBhF/405B99yd8CT
|
|
||||||
kQJXKF18LObj7YKNsWRoMkVgqlQzWDMEqbfmy9MhuLx2EZPsTB1L0BHNGGDVBd9o
|
|
||||||
cpPLUixcc12u+RPMKq8x3KgwsnUf5vX/pCnoGcCy4JahWdDgcZlf0hUKGT7PUem5
|
|
||||||
CWW8SMeqSWQX9XpE5Qlm1+W/QXdDXLbbHqDtvBeUy3iFQe3C9RSkp0qdutxkAlFk
|
|
||||||
f5QHXfJ7
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSxqnvlhtuQQvz
|
|
||||||
ksVq9rcWs7WOmBW15Dg0bHHLU273DaE98wx4I9aMHxR8DAWT4tU/K9YsBd3XNYvm
|
|
||||||
salDbK0hVCVOtpHkScAsDWwxxBhsXPUhI9NgHTfVsQ38KHwlZjAIbkBfYfgPoELs
|
|
||||||
2BPpjJYO3y/FCeoCKw8dD84rgAEPXkLCRPI+KZ6Tb+59g3Z1qmDXGEYhQP3qCp60
|
|
||||||
m/1u7Svzr2zrzyQAMgKiaxgb7wop8U3rtLetIcwilk+4ZO2gcadU7z1cGbS817RT
|
|
||||||
FCQkQYnNk+fS115zQtMLgY21NrPkm4KPH9WwkGqudOccePHTAXPyl99p8KKZHAD4
|
|
||||||
sKinE8TVAgMBAAECggEALU5EotoqJUXYEtAenUJQ0pFoWjE4oXNf3Wzd/O1/MZ19
|
|
||||||
ZjqDGKPjbxUTKyLOZB5i5gQ/MhFEwQiifMD9eB+5CyvyJPw7Wc28f/uWoQ/cjBZj
|
|
||||||
Hm979PHy2X0IW4Y8QTG462b/cUE2t+0j1ZMQnKf6bVHuC7V41mR5CC8oitMl5y5g
|
|
||||||
34yJmWXlIA0ep/WotLMqvil6DnSM/2V8Ch4SxjnzPpjbe4Kj+woucGNr4UKstZER
|
|
||||||
8iuHTsR64LjoGktRnnMwZxGZQI7EC428zsliInuWMdXe//w2chLdkirqpSrIQwSZ
|
|
||||||
3jNWStqBXGYaRg5Z1ilBvHtXxkzDzbAlzRBzqfEwwQKBgQDqYdMRrzHJaXWLdsyU
|
|
||||||
6jAuNX9tLh7PcicjP93SbPujS6mWcNb+D/au+VhWD+dZQDPRZttXck7wvKY1lw1V
|
|
||||||
MK0TYI7ydf8h3DFx3Mi6ZD4JVSU1MH233C3fv/FHenDoOvMXXRjUZxaRmuzFJvzt
|
|
||||||
6QlKIfSvwT+1wrOACNfteXfZUQKBgQDmN3Uuk01qvsETPwtWBp5RNcYhS/zGEQ7o
|
|
||||||
Q4K+teU453r1v8BGsQrCqulIZ3clMkDru2UroeKn1pzyVAS2AgajgXzfXh3VeZh1
|
|
||||||
vHTLP91BBYZTTWggalEN4aAkf9bxX/hA+9Bw/dzZcQW2aNV7WrYuCSvp3SDCMina
|
|
||||||
anQq/PaSRQKBgHjw23HfnegZI89AENaydQQTFNqolrtiYvGcbgC7vakITMzVEwrr
|
|
||||||
/9VP0pYuBKmYKGTgF0RrNnKgVX+HnxibUmOSSpCv9GNrdJQVYfpT6XL1XYqxp91s
|
|
||||||
nrs7FuxUMNiUOoWOw1Yuj4W4lH4y3QaCXgnDtbfPFunaOrdRWOIv8HjRAoGAV3NT
|
|
||||||
mSitbNIfR69YIAqNky3JIJbb42VRc1tJzCYOd+o+pCF96ZyRCNehnDZpZQDM9n8N
|
|
||||||
9GAfWEBHCCpwS69DVFL422TGEnSJPJglCZwt8OgnWXd7CW05cvt1OMgzHyekhxLg
|
|
||||||
4Dse7J5pXBxAlAYmVCB5xPGR4xLpISX1EOtcwr0CgYEA5rA2IUfjZYb4mvFHMKyM
|
|
||||||
xWZuV9mnl3kg0ULttPeOl3ppwjgRbWpyNgOXl8nVMYzxwT/A+xCPA18P0EcgNAWc
|
|
||||||
frJqQYg3NMf+f0K1wSaswUSLEVrQOj25OZJNpb21JEiNfEd5DinVVj4BtVc6KSpS
|
|
||||||
kvjbn2WhEUatc3lPL3V0Fkw=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", # 5
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExM1oXDTIxMDEwMTAxNTExM1owFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1c5y
|
|
||||||
S9IZHF9MIuwdafzhMkgP37I3RVpHEbpnPwnLFqSWelS5m2eDkwWd5SkfGjrmQ5q0
|
|
||||||
PEpqLlh3zHGw9yQjnHS3CCS1PwQ1kmwvpIK3HM5y8GM7ry1zkam8ZR4iX6Y7VG9g
|
|
||||||
9mhiVVFoVhe1gHeiC/3Mp6XeNuEiD0buM+8qZx9B21I+iwzy4wva7Gw0fJeq9G1c
|
|
||||||
lq2rhpD1LlIEodimWOi7lOEkNmUiO1SvpdrGdxUDpTgbdg6r5pCGjOXLd74tAQHP
|
|
||||||
P/LuqRNJDXtwvHtLIVQnW6wjjy4oiWZ8DXOdc9SkepwQLIF5Wh8O7MzF5hrd6Cvw
|
|
||||||
SOD3EEsJbyycAob6RwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBDNcbKVUyGOAVm
|
|
||||||
k3iVuzkkkymlTAMm/gsIs6loLJrkSqNg160FdVKJoZFjQtqoqLgLrntdCJ377nZ9
|
|
||||||
1i+yzbZsA4DA7nxj0IEdnd7rRYgGLspGqWeKSTROATeT4faLTXenecm0v2Rpxqc7
|
|
||||||
dSyeZJXOd2OoUu+Q64hzXCDXC6LNM+xZufxV9qv+8d+CipV6idSQZaUWSVuqFCwD
|
|
||||||
PT0R4eWfkMMaM8QqtNot/hVCEaKT+9rG0mbpRe/b/qBy5SR0u+XgGEEIV+33L59T
|
|
||||||
FXY+DpI1Dpt/bJFoUrfj6XohxdTdqYVCn1F8in98TsRcFHyH1xlkS3Y0RIiznc1C
|
|
||||||
BwAoGZ4B
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVznJL0hkcX0wi
|
|
||||||
7B1p/OEySA/fsjdFWkcRumc/CcsWpJZ6VLmbZ4OTBZ3lKR8aOuZDmrQ8SmouWHfM
|
|
||||||
cbD3JCOcdLcIJLU/BDWSbC+kgrccznLwYzuvLXORqbxlHiJfpjtUb2D2aGJVUWhW
|
|
||||||
F7WAd6IL/cynpd424SIPRu4z7ypnH0HbUj6LDPLjC9rsbDR8l6r0bVyWrauGkPUu
|
|
||||||
UgSh2KZY6LuU4SQ2ZSI7VK+l2sZ3FQOlOBt2DqvmkIaM5ct3vi0BAc8/8u6pE0kN
|
|
||||||
e3C8e0shVCdbrCOPLiiJZnwNc51z1KR6nBAsgXlaHw7szMXmGt3oK/BI4PcQSwlv
|
|
||||||
LJwChvpHAgMBAAECggEBAK0KLeUBgIM++Y7WDCRInzYjrn08bpE5tIU7mO4jDfQg
|
|
||||||
dw1A3wtQZuOpyxW6B0siWlRis/aLv44M2cBkT3ZmEFBDAhOcKfh7fqQn3RNHG847
|
|
||||||
pDi8B4UKwxskBa7NCcLh9eirUA19hABLJ6dt/t6fdE5CNc2FZ+iAoyE8JfNwYKAd
|
|
||||||
6Fa3HqUBPNWt8ryj4ftgpMNBdfmLugEM4N20SXJA28hOq2lUcwNKQQ1xQrovl0ig
|
|
||||||
iMbMWytV4gUPKC9Wra66OYIkk/K8teiUNIYA4JwAUVTs1NEWoyfwUTz1onutCkMl
|
|
||||||
5vY7JAqRoDWoSUX6FI+IHUdyqPAMdOMhC37gjrxoo2ECgYEA7trDMu6xsOwEckDh
|
|
||||||
iz148kejMlnTTuCNetOFBw3njFgxISx0PrDLWmJmnHMxPv9AAjXYb2+UCCm3fj6Q
|
|
||||||
OB8o4ZJm0n504qbFHcb2aI22U5hZ99ERvqx8WBnJ2RarIBmg06y0ktxq8gFR2qxF
|
|
||||||
0hWAOcDn1DWQ8QI0XBiFFcJTGtcCgYEA5SdlIXRnVZDKi5YufMAORG9i74dXUi0Y
|
|
||||||
02UoVxJ+q8VFu+TT8wrC5UQehG3gX+79Cz7hthhDqOSCv6zTyE4Evb6vf9OLgnVe
|
|
||||||
E5iLF033zCxLSS9MgiZ+jTO+wK3RsapXDtGcSEk2P82Pj5seNf4Ei1GNCRlm1DbX
|
|
||||||
71wlikprHhECgYABqmLcExAIJM0vIsav2uDiB5/atQelMCmsZpcx4mXv85l8GrxA
|
|
||||||
x6jTW4ZNpvv77Xm7yjZVKJkGqYvPBI6q5YS6dfPjmeAkyHbtazrCpeJUmOZftQSD
|
|
||||||
qN5BGwTuT5sn4SXe9ABaWdEhGONCPBtMiLvZK0AymaEGHTbSQZWD/lPoBwKBgGhk
|
|
||||||
qg2zmd/BNoSgxkzOsbE7jTbR0VX+dXDYhKgmJM7b8AjJFkWCgYcwoTZzV+RcW6rj
|
|
||||||
2q+6HhizAV2QvmpiIIbQd+Mj3EpybYk/1R2ox1qcUy/j/FbOcpihGiVtCjqF/2Mg
|
|
||||||
2rGTqMMoQl6JrBmsvyU44adjixTiZz0EHZYCkQoBAoGBAMRdmoR4mgIIWFPgSNDM
|
|
||||||
ISLJxKvSFPYDLyAepLfo38NzKfPB/XuZrcOoMEWRBnLl6dNN0msuzXnPRcn1gc1t
|
|
||||||
TG7db+hivAyUoRkIW3dB8pRj9dDUqO9OohjKsJxJaQCyH5vPkQFSLbTIgWrHhU+3
|
|
||||||
oSPiK/YngDV1AOmPDH7i62po
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #6
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAojGu
|
|
||||||
fQaTVT9DJWJ/zogGfrryEJXYVy9c441O5MrLlRx7nCIWIUs2NEhHDJdqJjYOTdmk
|
|
||||||
K98VhdMpDPZwxjgvvZrh43lStBRIW3zZxv747rSl2VtpSqD/6UNWJe5u4SR7oga4
|
|
||||||
JfITOKHg/+ASxnOxp/iu6oT6jBL6T7KSPh6Rf2+it2rsjhktRreFDJ2hyroNq1w4
|
|
||||||
ZVNCcNPgUIyos8u9RQKAWRNchFh0p0FCS9xNrn3e+yHnt+p6mOOF2gMzfXT/M2hq
|
|
||||||
KQNmc5D3yNoH2smWoz7F3XsRjIB1Ie4VWoRRaGEy7RwcwiDfcaemD0rQug6iqH7N
|
|
||||||
oomF6f3R4DyvVVLUkQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB/8SX6qyKsOyex
|
|
||||||
v3wubgN3FPyU9PqMfEzrFM6X5sax0VMVbSnekZrrXpdnXYV+3FBu2GLLQc900ojj
|
|
||||||
vKD+409JIriTcwdFGdLrQPTCRWkEOae8TlXpTxuNqJfCPVNxFN0znoat1bSRsX1U
|
|
||||||
K0mfEETQ3ARwlTkrF9CM+jkU3k/pnc9MoCLif8P7OAF38AmIbuTUG6Gpzy8RytJn
|
|
||||||
m5AiA3sds5R0rpGUu8mFeBpT6jIA1QF2g+QNHKOQcfJdCdfqTjKw5y34hjFqbWG9
|
|
||||||
RxWGeGNZkhC/jADCt+m+R6+hlyboLuIcVp8NJw6CGbr1+k136z/Dj+Fdhm6FzF7B
|
|
||||||
qULeRQJ+
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiMa59BpNVP0Ml
|
|
||||||
Yn/OiAZ+uvIQldhXL1zjjU7kysuVHHucIhYhSzY0SEcMl2omNg5N2aQr3xWF0ykM
|
|
||||||
9nDGOC+9muHjeVK0FEhbfNnG/vjutKXZW2lKoP/pQ1Yl7m7hJHuiBrgl8hM4oeD/
|
|
||||||
4BLGc7Gn+K7qhPqMEvpPspI+HpF/b6K3auyOGS1Gt4UMnaHKug2rXDhlU0Jw0+BQ
|
|
||||||
jKizy71FAoBZE1yEWHSnQUJL3E2ufd77Iee36nqY44XaAzN9dP8zaGopA2ZzkPfI
|
|
||||||
2gfayZajPsXdexGMgHUh7hVahFFoYTLtHBzCIN9xp6YPStC6DqKofs2iiYXp/dHg
|
|
||||||
PK9VUtSRAgMBAAECggEANjn0A3rqUUr4UQxwfIV/3mj0O1VN4kBEhxOcd+PRUsYW
|
|
||||||
EapXycPSmII9ttj8tU/HUoHcYIqSMI7bn6jZJXxtga/BrALJAsnxMx031k8yvOQK
|
|
||||||
uvPT7Q6M4NkReVcRHRbMeuxSLuWTRZDhn8qznEPb9rOvD1tsRN6nb3PdbwVbUcZh
|
|
||||||
2F6JDrTyI/Df6nrYQAWOEe2ay7tzgrNYE4vh+DW7oVmyHRgFYA+DIG5Q+7OVWeW5
|
|
||||||
bwYYPKlo4/B0L+GfMKfMVZ+5TvFWAK0YD1e/CW1Gv+i/8dWm4O7UNGg5mTnrIcy1
|
|
||||||
g5wkKbyea02/np2B/XBsSWXDl6rTDHL7ay0rH2hjEQKBgQDMKSm3miQTIcL/F2kG
|
|
||||||
ieapmRtSc7cedP967IwUfjz4+pxPa4LiU47OCGp1bmUTuJAItyQyu/5O3uLpAriD
|
|
||||||
PTU+oVlhqt+lI6+SJ4SIYw01/iWI3EF2STwXVnohWG1EgzuFM/EqoB+mrodNONfG
|
|
||||||
UmP58vI9Is8fdugXgpTz4Yq9pQKBgQDLYJoyMVrYTvUn5oWft8ptsWZn6JZXt5Bd
|
|
||||||
aXh+YhNmtCrSORL3XjcH4yjlcn7X8Op33WQTbPo7QAJ1CumJzAI88BZ/8za638xb
|
|
||||||
nLueviZApCt0bNMEEdxDffxHFc5TyHE+obMKFfApbCnD0ggO6lrZ8jK9prArLOCp
|
|
||||||
mRU9SSRffQKBgAjoBszeqZI4F9SfBdLmMyzU5A89wxBOFFMdfKLsOua1sBn627PZ
|
|
||||||
51Hvpg1HaptoosfujWK1NsvkB0wY9UmsYuU/jrGnDaibnO4oUSzN/WaMlsCYszZg
|
|
||||||
zYFLIXrQ67tgajlOYcf1Qkw4MujYgPlC4N+njI/EM/rwagGUjcDx5uaNAoGASyqz
|
|
||||||
EuYG63eTSGH89SEaohw0+yaNmnHv23aF4EAjZ4wjX3tUtTSPJk0g6ly84Nbb8d1T
|
|
||||||
hZJ7kbaAsf2Mfy91jEw4JKYhjkP05c8x0OP6g12p6efmvdRUEmXX/fXjQjgNEtb0
|
|
||||||
sz+UedrOPN+9trWLSo4njsyyw+JcTpKTtQj5dokCgYEAg9Y3msg+GvR5t/rPVlKd
|
|
||||||
keZkrAp0xBJZgqG7CTPXWS1FjwbAPo7x4ZOwtsgjCuE52lar4j+r2Il+CDYeLfxN
|
|
||||||
h/Jfn6S9ThUh+B1PMvKMMnJUahg8cVL8uQuBcbAy8HPRK78WO2BTnje44wFAJwTc
|
|
||||||
0liuYqVxZIRlFLRl8nGqog8=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #7
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu9oO
|
|
||||||
cFlNukUcLfFrfkEaUiilcHLmn5OokQbj95CGd2ehQCCVwrkunYLBisthRaancFFb
|
|
||||||
/yM998B0IUsKTsoLi5DAN3/SkSm6GiQIGO05E4eBPljwJ61QQMxh8+1TwQ9HTun1
|
|
||||||
ZE1lhVN1aRmI9VsbyTQLjXh9OFNLSJEKb29mXsgzYwYwNOvo+idzXpy4bMyNoGxY
|
|
||||||
Y+s2FIKehNHHCv4ravDn8rf6DtDOvyN4d0/QyNws9FpAZMXmLwtBJ9exOqKFW43w
|
|
||||||
97NxgdNiTFyttrTKTi0b+9v3GVdcEZw5b2RMIKi6ZzPof6/0OlThK6C3xzFK3Bp4
|
|
||||||
PMjTfXw5yyRGVBnZZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA4Ms6LqzMu757z
|
|
||||||
bxISiErRls6fcnq0fpSmiPNHNKM7YwG9KHYwPT6A0UMt30zDwNOXCQBI19caGeeO
|
|
||||||
MLPWa7Gcqm2XZB2jQwvLRPeFSy9fm6RzJFeyhrh/uFEwUetwYmi/cqeIFDRDBQKn
|
|
||||||
bOaXkBk0AaSmI5nRYfuqpMMjaKOFIFcoADw4l9wWhv6DmnrqANzIdsvoSXi5m8RL
|
|
||||||
FcZQDZyHFlHh3P3tLkmQ7ErM2/JDwWWPEEJMlDm/q47FTOQSXZksTI3WRqbbKVv3
|
|
||||||
iQlJjpgi9yAuxZwoM3M4975iWH4LCZVMCSqmKCBt1h9wv4LxqX/3kfZhRdy1gG+j
|
|
||||||
41NOSwJ/
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC72g5wWU26RRwt
|
|
||||||
8Wt+QRpSKKVwcuafk6iRBuP3kIZ3Z6FAIJXCuS6dgsGKy2FFpqdwUVv/Iz33wHQh
|
|
||||||
SwpOyguLkMA3f9KRKboaJAgY7TkTh4E+WPAnrVBAzGHz7VPBD0dO6fVkTWWFU3Vp
|
|
||||||
GYj1WxvJNAuNeH04U0tIkQpvb2ZeyDNjBjA06+j6J3NenLhszI2gbFhj6zYUgp6E
|
|
||||||
0ccK/itq8Ofyt/oO0M6/I3h3T9DI3Cz0WkBkxeYvC0En17E6ooVbjfD3s3GB02JM
|
|
||||||
XK22tMpOLRv72/cZV1wRnDlvZEwgqLpnM+h/r/Q6VOEroLfHMUrcGng8yNN9fDnL
|
|
||||||
JEZUGdlnAgMBAAECggEALlZdlW0R9U6y4spYf65Dddy84n4VUWu0+wE+HoUyBiYz
|
|
||||||
6oOfLYdMbmIgp8H/XpT7XINVNBxXXtPEUaoXAtRoAKdWItqO8Gvgki4tKSjrGVwl
|
|
||||||
j2GU69SepT1FNExoiojgSCEB/RnyXu71WVWJKSyuL/V8nAsKqGgze9T7Q/2wvNQt
|
|
||||||
SQqLxZlrWF0P8WqaAiSrHV4GnDrdeF+k1KBo2+pSaDNv6cNwOyVG8EII9tqhF8kj
|
|
||||||
6nD6846ish6OqmlSisaSGopJZL1DCQzszFMxKd2+iBDY7Kn6hVIhRaNnaZUFhpKM
|
|
||||||
dNh6hBqOycMepAp0sz5pdo+fxpifkoR/cPWgyC3QkQKBgQDixe9VsiZ7u2joxF/9
|
|
||||||
JcAExKhqE28OUmIwt6/j+uzYShxN6Oo9FUo3ICtAPCCFsjhvb3Qum7FspmxrqtNy
|
|
||||||
fzclibZJPO8ey2PzqaiOfiVfgJmNSvoCOdgM4OqFLtRO6eSTzhJeI4VPrPcq/5la
|
|
||||||
0FuOi1WZs/Au9llqLqGSDH3UAwKBgQDUD/bSJbOk5SvNjFtFm0ClVJr66mJ5e4uN
|
|
||||||
4VGv8KGFAJ+ExIxujAukfKdwLjS1wEy2RePcshfT8Y9FVh/Q1KzzrQi3Gwmfq1G6
|
|
||||||
Dpu2HlJpaZl+9T81x2KS8GP3QNczWMe2nh7Lj+6st+b4F+6FYbVTFnHaae27sXrD
|
|
||||||
XPX15+uxzQKBgGy+pBWBF4kwBo/QU4NuTdU7hNNRPGkuwl1ASH1Xv6m8aDRII8Nk
|
|
||||||
6TDkITltW98g5oUxehI7oOpMKCO9SCZYsNY0YpBeQwCOYgDfc6/Y+A0C+x9RO/BD
|
|
||||||
UsJiPLPfD/pDmNPz9sTj3bKma+RXq29sCOujD0pkiiHLCnerotkJWnGHAoGAAkCJ
|
|
||||||
JoIv/jhQ1sX+0iZr8VWMr819bjzZppAWBgBQNtFi4E4WD7Z9CSopvQ9AkA2SwvzL
|
|
||||||
BrT9e8q88sePXvBjRdM4nHk1CPUQ0SEGllCMH4J3ltmT6kZLzbOv3BhcMLdop4/W
|
|
||||||
U+MbbcomMcxPRCtdeZxraR5m3+9qlliOZCYqYqECgYA5eLdxgyHxCS33QGFHRvXI
|
|
||||||
TLAHIrr7wK1xwgkmZlLzYSQ8Oqh1UEbgoMt4ulRczP2g7TCfvANw2Sw0H2Q5a6Fj
|
|
||||||
cnwVcXJ38DLg0GCPMwzE8dK7d8tKtV6kGiKy+KFvoKChPjE6uxhKKmCJaSwtQEPS
|
|
||||||
vsjX3iiIgUQPsSz8RrNFfQ==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #8
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DNu
|
|
||||||
CKhhl6wCbgoCkFemwJh3ATbAjhInHpvQWIFDfSK1USElCKxqosIxiBQCx3Zs2d/U
|
|
||||||
GeIA7QAM2atNdXaateacEaKMmGE9LEtO0Dg5lmT43WzmGkG9NmCwK3JjAekc5S9d
|
|
||||||
HKNtEQo7o8RKfj81zlDSq2kzliy98cimk24VBBGkS2Cn7Vy/mxMCqWjQazTXbpoS
|
|
||||||
lXw6LiY5wFXQmXOB5GTSHvqyCtBQbOSSbJB77z/fm7bufTDObufTbJIq53WPt00Y
|
|
||||||
f+JNnzkX1X0MaBCUztoZwoMaExWucMe/7xsQ46hDn6KB4b0lZk+gsK45QHxvPE1R
|
|
||||||
72+ZkkIrGS/ljIKahQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDib1653CneSmy2
|
|
||||||
gYzGeMlrI05Jqo3JuHNMQHzAjIrb4ee57VA4PTQa1ygGol/hVv6eTvZr3p2ospDS
|
|
||||||
5Kfwj1HLO4jSMX1Bnm1FG0naQogz2CD3xfYjbYOVRhAxpld1MNyRveIOhDRARY7N
|
|
||||||
XNAaNPZ1ALrwbENSYArr18xDzgGWe/dgyRCEpCFIsztiA+7jGvrmAZgceIE8K3h3
|
|
||||||
fkvNmXBH58ZHAGTiyRriBZqS+DXrBrQOztXSJwFnOZnRt6/efeBupt8j5hxVpBLW
|
|
||||||
vtjpBc23uUcbbHOY2AW2Bf+vIr4/LmJ/MheKV+maa2990vmC93tvWlFfc74mgUkW
|
|
||||||
HJfXDmR6
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDkM24IqGGXrAJu
|
|
||||||
CgKQV6bAmHcBNsCOEicem9BYgUN9IrVRISUIrGqiwjGIFALHdmzZ39QZ4gDtAAzZ
|
|
||||||
q011dpq15pwRooyYYT0sS07QODmWZPjdbOYaQb02YLArcmMB6RzlL10co20RCjuj
|
|
||||||
xEp+PzXOUNKraTOWLL3xyKaTbhUEEaRLYKftXL+bEwKpaNBrNNdumhKVfDouJjnA
|
|
||||||
VdCZc4HkZNIe+rIK0FBs5JJskHvvP9+btu59MM5u59NskirndY+3TRh/4k2fORfV
|
|
||||||
fQxoEJTO2hnCgxoTFa5wx7/vGxDjqEOfooHhvSVmT6CwrjlAfG88TVHvb5mSQisZ
|
|
||||||
L+WMgpqFAgMBAAECggEABTdPuo7uvCLIY2+DI319aEWT4sk3mYe8sSxqlLtPqZqT
|
|
||||||
fmk9iXc3cMTzkOK0NY71af19waGy17f6kzchLCAr5SCCTLzkbc87MLn/8S530oI4
|
|
||||||
VgdZMxxxkL6hCD0zGiYT7QEqJa9unMcZGeMwuLYFKtQaHKTo8vPO26n0dMY9YLxj
|
|
||||||
cNUxsKLcKk8dbfKKt4B4fZgB7nU0BG9YbKYZ3iZ7/3mG+6jA6u+VYc/WHYQjTmpL
|
|
||||||
oLFN7NOe3R7jIx/kJ1OqNWqsFoLpyiiWd1Mr0l3EdD1kCudptMgD8hd++nx2Yk2w
|
|
||||||
K4+CpOVIN/eCxDDaAOJgYjCtOayVwUkDAxRRt9VnAQKBgQD5s1j6RJtBNTlChVxS
|
|
||||||
W3WpcG4q8933AiUY/Chx0YTyopOiTi7AGUaA8AOLFBcO2npa+vzC+lvuOyrgOtVW
|
|
||||||
sD10H2v5jNKlbeBp+Q9rux2LAyp4TvzdXWKhVyZrdtITF0hn6vEYNp7MtyWRFb1O
|
|
||||||
3Ie5HQBPHtzllFOMynacjOdjpQKBgQDp9TrbfOmwGWmwPKmaFKuy8BKxjJM+ct0X
|
|
||||||
4Xs1uSy9Z9Y8QlDNbNaooI8DA1NY0jDVHwemiGC4bYsBNKNRcbI0s2nr0hQMft42
|
|
||||||
P/NpugHv0YXiVz+5bfim4woTiHHbfREqchlIGo3ryClAiDU9fYZwTOtb9jPIhX3G
|
|
||||||
9v+OsoMlYQKBgQDJUQW90S5zJlwh+69xXvfAQjswOimNCpeqSzK4gTn0/YqV4v7i
|
|
||||||
Nf6X2eqhaPMmMJNRYuYCtSMFMYLiAc0a9UC2rNa6/gSfB7VU+06phtTMzSKimNxa
|
|
||||||
BP6OIduB7Ox2I+Fmlw8GfJMPbeHF1YcpW7e5UV58a9+g4TNzYZC7qwarWQKBgQCA
|
|
||||||
FFaCbmHonCD18F/REFvm+/Lf7Ft3pp5PQouXH6bUkhIArzVZIKpramqgdaOdToSZ
|
|
||||||
SAGCM8rvbFja8hwurBWpMEdeaIW9SX8RJ/Vz/fateYDYJnemZgPoKQcNJnded5t8
|
|
||||||
Jzab+J2VZODgiTDMVvnQZOu8To6OyjXPRM0nK6cMQQKBgQDyX44PHRRhEXDgJFLU
|
|
||||||
qp2ODL54Qadc/thp2m+JmAvqmCCLwuYlGpRKVkLLuZW9W6RlVqarOC3VD3wX5PRZ
|
|
||||||
IsyCGLi+Jbrv9JIrYUXE80xNeQVNhrrf02OW0KHbqGxRaNOmp1THPw98VUGR2J/q
|
|
||||||
YAp6XUXU7LEBUrowye+Ty2o7Lg==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #9
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k2R
|
|
||||||
PWYihftppo3CoxeseFwgg7guxZVkP7aAur5uBzSeAB7sBG1G2bRrwMX71S4xPwot
|
|
||||||
zYiEoxUrTStUqEKjL2aozfHsXnHZ7kwwUgZFDZUg+ve2tZDA3HCUr4tLYKlyFqpx
|
|
||||||
2nCouc45MjQ4wAxRl4rQxIUG2uSTzvP+xXtjoJYMIEEyCpcsRXfqfVkEUe9nrPsF
|
|
||||||
0Ibzk7Cyt75HDI4uEzBuHux0DYuGy6R02jz/vf/dIZ4WepjSY06xpblTHZgieDRX
|
|
||||||
fU2+YOcvb0eDHyA8Q5p8ropK71MNIP5+kffFd90SVr4EkCA8S+cd6FdKQasRr+jF
|
|
||||||
9MUhMS4ObvlrYTG+hwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCy62MZ3+59/VpX
|
|
||||||
c9Hsmb4/BMWt0irLJit4w4SkuYKGFLCMKZI4LN4pEkXaiE1eqF2DNS1qOvl5luty
|
|
||||||
Zz4oggrqilwuFeH98o9Zeg9SYnouuypORVP/3DPbJF/jiQg5J8kJb1sy+BjRiT8I
|
|
||||||
5X6/cCBYT+MljFz5tpqWOtWTgA30e1BV8JFj8F4dgUcWsAVT/I4l9zgMLUnhcO6E
|
|
||||||
wLtEE0I6aT1RHJB28ndwJzj4La98Oirw7LAEAWbExWYB90ypLaGY+JVJe3f5fijC
|
|
||||||
fJpQ2mbs4syXDmb5bU2C2pGPTKZPcyx15iQrq1uHInD0facOw+pmllAFxuG96lA1
|
|
||||||
+o2VzKwP
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWTZE9ZiKF+2mm
|
|
||||||
jcKjF6x4XCCDuC7FlWQ/toC6vm4HNJ4AHuwEbUbZtGvAxfvVLjE/Ci3NiISjFStN
|
|
||||||
K1SoQqMvZqjN8execdnuTDBSBkUNlSD697a1kMDccJSvi0tgqXIWqnHacKi5zjky
|
|
||||||
NDjADFGXitDEhQba5JPO8/7Fe2OglgwgQTIKlyxFd+p9WQRR72es+wXQhvOTsLK3
|
|
||||||
vkcMji4TMG4e7HQNi4bLpHTaPP+9/90hnhZ6mNJjTrGluVMdmCJ4NFd9Tb5g5y9v
|
|
||||||
R4MfIDxDmnyuikrvUw0g/n6R98V33RJWvgSQIDxL5x3oV0pBqxGv6MX0xSExLg5u
|
|
||||||
+WthMb6HAgMBAAECggEAeCyRSNwQeg/NZD/UqP6qkegft52+ZMBssinWsGH/c3z3
|
|
||||||
KVwtwCHDfGvnjPe5TAeWSCKeIsbukkFZwfGNjLmppvgrqymCAkhYDICfDDBF4uMA
|
|
||||||
1pu40sJ01Gkxh+tV/sOmnb1BEVzh0Sgq/NM6C8ActR18CugKOw+5L3G2KeoSqUbT
|
|
||||||
2hcPUsnik10KwqW737GQW4LtEQEr/iRmQkxI3+HBzvPWjFZzjOcpUph+FW5TXtaU
|
|
||||||
T26mt1j+FjbdvvhCuRMY/VZBJ5h1RKU95r57F1AjW/C0RRJ8FxR1CeSy4IlmQBrh
|
|
||||||
6wAa3Tdm0k/n4ZspC9bF5eVTJEtb0AohiYZrIa8MuQKBgQD8yjCLYa41H304odCx
|
|
||||||
NwPRJcmlIk5YGxPrhHAT9GEgU6n/no7YMVx1L7fNLcMjAyx54jauEU7J19Aki7eV
|
|
||||||
SIdU9TwqmkOAFfM6TOEJZiOi66gABOxeK2yDyfmR6Apaw3caku4O058t4KVwHSCB
|
|
||||||
DanYCMzxCBqS9jUTTyAh0fMg6wKBgQDZBkIukg3FKPor5LzkUXIKnNHYPfHbERHw
|
|
||||||
piWS6GZwqhuWNlOCWxiBR4rEUU/RbFQZw/FCi5OuAk2lBC0LBmC0/Sz4/+xDdCbv
|
|
||||||
uNhMOTRcy9nFVpmpIWCx4N/KmXHEuFxli/JNXux7iki74AVC9VPrAt/kCvwf06Df
|
|
||||||
oDb8ljdR1QKBgQChVOD6c5Lc8IXYeN1Z3IShHH6+11AsxstFyjZFZff+y6Z5L1Z2
|
|
||||||
/7nESHoDhqs9Uy81cnv3R7CC/Ssnx8uYiLtmK0UE44Mk4d1jXeFZQEiKF+AWcw3v
|
|
||||||
Y8NTsLmItxC0sH75BMDN0Z2LiA3Nqaku8+trpuI1Cjj7hgqFkkAtlXKXlQKBgBMb
|
|
||||||
c/Q5s7CqHOyEZQUNDqdUiz0opwSMijHPzvsSLwK4V1lwSwXtE0k+jT8fkZF0oirq
|
|
||||||
j3E2bLgjR8bBiV2xIA6PQ8hgb+K4dT0h3xlG6A9Le07egwTbBXJjxBBIVjXlrWzb
|
|
||||||
V2fsdZGi6ShxXsU4aD0GscOYG/6JWV6W8oBmkVRJAoGAepIZ+OYmFjb7uxdh4EtP
|
|
||||||
hluEtx5bLOLuo6c0S149omUXUhbsuyzTZS6Ip9ySDMnK3954c4Q4WJ4yQKixQNVq
|
|
||||||
78aDfy4hP/8TE/Q9CRddUof2P33PJMhVNqzTRYMpqV+zxifvtw3hoDTLKHTQxCR2
|
|
||||||
M1+O4VvokU5pBqUpGXiMDfs=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #10
|
|
||||||
"""-----BEGIN CERTIFICATE-----
|
|
||||||
MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp
|
|
||||||
bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM
|
|
||||||
bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnbCU
|
|
||||||
M37hG7zrCyyJEI6pZmOomnI+CozbP5KAhWSV5y7R5H6lcAEG2UDV+lCUxHT2ufOa
|
|
||||||
i1H16bXyBt7VoMTHIH50S58NUCUEXcuRWVR16tr8CzcTHQAkfIrmhY2XffPilX7h
|
|
||||||
aw35UkoVmXcqSDNNJD6jmvWexvmbhzVWW8Vt5Pivet2/leVuqPXB54/alSbkC74m
|
|
||||||
x6X5XKQc6eyPsb1xvNBuiSpFzdqbEn7lUwj6jFTkh9tlixgmgx+J0XoQXbawyrAg
|
|
||||||
rcIQcse/Ww+KBA1KSccFze+XBTbIull4boYhbJqkb6DW5bY7/me2nNxE9DRGwq+S
|
|
||||||
kBsKq3YKeCf8LEhfqQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAD+tWGFhINYsWT
|
|
||||||
ibKWlCGgBc5uB7611cLCevx1yAL6SaOECVCQXzaaXIaETSbyY03UO2yBy3Pl10FV
|
|
||||||
GYXLrAWTFZsNVJm55XIibTNw1UBPNwdIoCSzAYuOgMF0GHhTTQU0hNYWstOnnE2T
|
|
||||||
6lSAZQZFkaW4ZKs6sUp42Em9Bu99PehyIgnw14qb9NPg5qKdi2GAvkImZCrGpMdK
|
|
||||||
OF31U7Ob0XQ0lxykcNgG4LlUACd+QxLfNpmLBZUGfikexYa1VqBFm3oAvTt8ybNQ
|
|
||||||
qr7AKXDFnW75aCBaMpQWzrstA7yYZ3D9XCd5ZNf6d08lGM/oerDAIGnZOZPJgs5U
|
|
||||||
FaWPHdS9
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdsJQzfuEbvOsL
|
|
||||||
LIkQjqlmY6iacj4KjNs/koCFZJXnLtHkfqVwAQbZQNX6UJTEdPa585qLUfXptfIG
|
|
||||||
3tWgxMcgfnRLnw1QJQRdy5FZVHXq2vwLNxMdACR8iuaFjZd98+KVfuFrDflSShWZ
|
|
||||||
dypIM00kPqOa9Z7G+ZuHNVZbxW3k+K963b+V5W6o9cHnj9qVJuQLvibHpflcpBzp
|
|
||||||
7I+xvXG80G6JKkXN2psSfuVTCPqMVOSH22WLGCaDH4nRehBdtrDKsCCtwhByx79b
|
|
||||||
D4oEDUpJxwXN75cFNsi6WXhuhiFsmqRvoNbltjv+Z7ac3ET0NEbCr5KQGwqrdgp4
|
|
||||||
J/wsSF+pAgMBAAECggEAPSu1ofBTRN5ZU4FYPlsJLdX1Hsy4coFHv/aF8rkdSYwp
|
|
||||||
EflrFfLgBEEZgLvnqfoxh9sPFYKa4amaFL42ouIS2PEVDgzKLk/dzMDeRof0IkIG
|
|
||||||
yhb4TCS1ArcjS6WsociNGi8ZJN1L3Xctv9WxSkbUYv4Fm2Qyzr8fbSjssjb5NXwD
|
|
||||||
K11fsj6Pfy/mQrI0TSTlzWC7ARIlCMTWQ8G8zEU6bMFIG6DMjt2J4VgYVXUKetZA
|
|
||||||
VPuS+pwkH2obQe6FLRuiNxH4GitVAASYPea6foER4AggBMRp8q8F6+WssjoyEORb
|
|
||||||
0sJxmnxhznoTRMCuTsUj6XMgmOTOnA3lQXsIB0DYcQKBgQDO6mMRVVFWzgkE9Q5/
|
|
||||||
36n06KvGYF9TCRDL9vRC8kCqcGd1Hy6jRj0D8049KUHaN74pfWg6gsQjPkKzwKnC
|
|
||||||
vxNl72tVvLqm7Fo531BGfKK/46ZvxeWMMraNW4+9LhwMPu2LN5OEdwwCgyaURpxh
|
|
||||||
ktCp+RrGjz08Kn82X1jJPdwxDQKBgQDDGMvZ7ZUDGq5+RJkmHJ58lQtiaMZclmYV
|
|
||||||
R9YwOxJV6ino3EYrGOtUkqiemgAACdMWE/JMJlB1/JINawJwUsZ2XDp/9jNLPgLc
|
|
||||||
gphCmagaO34U/YMaJbJIK2gkCX7p8EcD+x45qWa0bEMPW38QfN/qQdUPjNmpuIiI
|
|
||||||
Zleyl1TqDQKBgQCvIoat0ighsAzETGN0aqzhJdrW8xVcJA06hpFi5MdFPBTldno0
|
|
||||||
KqxUXqj3badWe94SIhqJg8teBUHSAZ3uv2o82nRgQnk99km8OD8rGi1q+9YRP1C2
|
|
||||||
5OnNJhW4y4FkABNxxZ2v/k+FBNsvn8CXefvyEm3OaMks1s+MBxIQa7KnNQKBgFwX
|
|
||||||
HUo+GiN/+bPCf6P8yFa4J8qI+HEF0SPkZ9cWWx5QzP2M1FZNie++1nce7DcYbBo0
|
|
||||||
yh9lyn8W/H328AzDFckS2c5DEY1HtSQPRP3S+AWB5Y7U54h1GMV2L88q6ExWzb60
|
|
||||||
T10aeE9b9v+NydmniC5UatTPQIMbht8Tp/u18TAVAoGBAJphAfiqWWV2M5aBCSXq
|
|
||||||
WxLZ71AJ0PZBmRa/9iwtccwXQpMcW6wHK3YSQxci+sB97TElRa3/onlVSpohrUtg
|
|
||||||
VCvCwfSHX1LmrfWNSkoJZwCQt+YYuMqW86K0tzLzI1EMjIH9LgQvB6RR26PZQs+E
|
|
||||||
jr1ZvRc+wPTq6sxCF1h9ZAfN
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
""", #11
|
|
||||||
]
|
|
||||||
|
|
||||||
# To disable the pre-computed tub certs, uncomment this line.
|
|
||||||
# SYSTEM_TEST_CERTS = []
|
|
||||||
|
|
||||||
def flush_but_dont_ignore(res):
|
|
||||||
d = flushEventualQueue()
|
|
||||||
def _done(ignored):
|
|
||||||
return res
|
|
||||||
d.addCallback(_done)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def _render_config(config):
|
|
||||||
"""
|
|
||||||
Convert a ``dict`` of ``dict`` of ``unicode`` to an ini-format string.
|
|
||||||
"""
|
|
||||||
return u"\n\n".join(list(
|
|
||||||
_render_config_section(k, v)
|
|
||||||
for (k, v)
|
|
||||||
in config.items()
|
|
||||||
))
|
|
||||||
|
|
||||||
def _render_config_section(heading, values):
|
|
||||||
"""
|
|
||||||
Convert a ``unicode`` heading and a ``dict`` of ``unicode`` to an ini-format
|
|
||||||
section as ``unicode``.
|
|
||||||
"""
|
|
||||||
return u"[{}]\n{}\n".format(
|
|
||||||
heading, _render_section_values(values)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _render_section_values(values):
|
|
||||||
"""
|
|
||||||
Convert a ``dict`` of ``unicode`` to the body of an ini-format section as
|
|
||||||
``unicode``.
|
|
||||||
"""
|
|
||||||
return u"\n".join(list(
|
|
||||||
u"{} = {}".format(k, v)
|
|
||||||
for (k, v)
|
|
||||||
in sorted(values.items())
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.port_assigner = SameProcessStreamEndpointAssigner()
|
|
||||||
self.port_assigner.setUp()
|
|
||||||
self.addCleanup(self.port_assigner.tearDown)
|
|
||||||
|
|
||||||
self.sparent = service.MultiService()
|
|
||||||
self.sparent.startService()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
log.msg("shutting down SystemTest services")
|
|
||||||
d = self.sparent.stopService()
|
|
||||||
d.addBoth(flush_but_dont_ignore)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def getdir(self, subdir):
|
|
||||||
return os.path.join(self.basedir, subdir)
|
|
||||||
|
|
||||||
def add_service(self, s):
|
|
||||||
s.setServiceParent(self.sparent)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def _create_introducer(self):
|
|
||||||
"""
|
|
||||||
:returns: (via Deferred) an Introducer instance
|
|
||||||
"""
|
|
||||||
iv_dir = self.getdir("introducer")
|
|
||||||
if not os.path.isdir(iv_dir):
|
|
||||||
_, port_endpoint = self.port_assigner.assign(reactor)
|
|
||||||
introducer_config = (
|
|
||||||
u"[node]\n"
|
|
||||||
u"nickname = introducer \N{BLACK SMILING FACE}\n" +
|
|
||||||
u"web.port = {}\n".format(port_endpoint)
|
|
||||||
).encode("utf-8")
|
|
||||||
|
|
||||||
fileutil.make_dirs(iv_dir)
|
|
||||||
fileutil.write(
|
|
||||||
os.path.join(iv_dir, 'tahoe.cfg'),
|
|
||||||
introducer_config,
|
|
||||||
)
|
|
||||||
if SYSTEM_TEST_CERTS:
|
|
||||||
os.mkdir(os.path.join(iv_dir, "private"))
|
|
||||||
f = open(os.path.join(iv_dir, "private", "node.pem"), "w")
|
|
||||||
f.write(SYSTEM_TEST_CERTS[0])
|
|
||||||
f.close()
|
|
||||||
return create_introducer(basedir=iv_dir)
|
|
||||||
|
|
||||||
def _get_introducer_web(self):
|
|
||||||
with open(os.path.join(self.getdir("introducer"), "node.url"), "r") as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
@inlineCallbacks
|
|
||||||
def set_up_nodes(self, NUMCLIENTS=5):
|
|
||||||
"""
|
|
||||||
Create an introducer and ``NUMCLIENTS`` client nodes pointed at it. All
|
|
||||||
of the nodes are running in this process.
|
|
||||||
|
|
||||||
As a side-effect, set:
|
|
||||||
|
|
||||||
* ``numclients`` to ``NUMCLIENTS``
|
|
||||||
* ``introducer`` to the ``_IntroducerNode`` instance
|
|
||||||
* ``introweb_url`` to the introducer's HTTP API endpoint.
|
|
||||||
|
|
||||||
:param int NUMCLIENTS: The number of client nodes to create.
|
|
||||||
|
|
||||||
:return: A ``Deferred`` that fires when the nodes have connected to
|
|
||||||
each other.
|
|
||||||
"""
|
|
||||||
self.numclients = NUMCLIENTS
|
|
||||||
|
|
||||||
self.introducer = yield self._create_introducer()
|
|
||||||
self.add_service(self.introducer)
|
|
||||||
self.introweb_url = self._get_introducer_web()
|
|
||||||
yield self._set_up_client_nodes()
|
|
||||||
|
|
||||||
@inlineCallbacks
|
|
||||||
def _set_up_client_nodes(self):
|
|
||||||
q = self.introducer
|
|
||||||
self.introducer_furl = q.introducer_url
|
|
||||||
self.clients = []
|
|
||||||
basedirs = []
|
|
||||||
for i in range(self.numclients):
|
|
||||||
basedirs.append((yield self._set_up_client_node(i)))
|
|
||||||
|
|
||||||
# start clients[0], wait for it's tub to be ready (at which point it
|
|
||||||
# will have registered the helper furl).
|
|
||||||
c = yield client.create_client(basedirs[0])
|
|
||||||
c.setServiceParent(self.sparent)
|
|
||||||
self.clients.append(c)
|
|
||||||
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
|
||||||
|
|
||||||
with open(os.path.join(basedirs[0],"private","helper.furl"), "r") as f:
|
|
||||||
helper_furl = f.read()
|
|
||||||
|
|
||||||
self.helper_furl = helper_furl
|
|
||||||
if self.numclients >= 4:
|
|
||||||
with open(os.path.join(basedirs[3], 'tahoe.cfg'), 'a+') as f:
|
|
||||||
f.write(
|
|
||||||
"[client]\n"
|
|
||||||
"helper.furl = {}\n".format(helper_furl)
|
|
||||||
)
|
|
||||||
|
|
||||||
# this starts the rest of the clients
|
|
||||||
for i in range(1, self.numclients):
|
|
||||||
c = yield client.create_client(basedirs[i])
|
|
||||||
c.setServiceParent(self.sparent)
|
|
||||||
self.clients.append(c)
|
|
||||||
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
|
||||||
log.msg("STARTING")
|
|
||||||
yield self.wait_for_connections()
|
|
||||||
log.msg("CONNECTED")
|
|
||||||
# now find out where the web port was
|
|
||||||
self.webish_url = self.clients[0].getServiceNamed("webish").getURL()
|
|
||||||
if self.numclients >=4:
|
|
||||||
# and the helper-using webport
|
|
||||||
self.helper_webish_url = self.clients[3].getServiceNamed("webish").getURL()
|
|
||||||
|
|
||||||
def _generate_config(self, which, basedir):
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
except1 = set(range(self.numclients)) - {1}
|
|
||||||
feature_matrix = {
|
|
||||||
("client", "nickname"): except1,
|
|
||||||
|
|
||||||
# client 1 has to auto-assign an address.
|
|
||||||
("node", "tub.port"): except1,
|
|
||||||
("node", "tub.location"): except1,
|
|
||||||
|
|
||||||
# client 0 runs a webserver and a helper
|
|
||||||
# client 3 runs a webserver but no helper
|
|
||||||
("node", "web.port"): {0, 3},
|
|
||||||
("node", "timeout.keepalive"): {0},
|
|
||||||
("node", "timeout.disconnect"): {3},
|
|
||||||
|
|
||||||
("helper", "enabled"): {0},
|
|
||||||
}
|
|
||||||
|
|
||||||
def setconf(config, which, section, feature, value):
|
|
||||||
if which in feature_matrix.get((section, feature), {which}):
|
|
||||||
config.setdefault(section, {})[feature] = value
|
|
||||||
|
|
||||||
setnode = partial(setconf, config, which, "node")
|
|
||||||
sethelper = partial(setconf, config, which, "helper")
|
|
||||||
|
|
||||||
setnode("nickname", u"client %d \N{BLACK SMILING FACE}" % (which,))
|
|
||||||
|
|
||||||
tub_location_hint, tub_port_endpoint = self.port_assigner.assign(reactor)
|
|
||||||
setnode("tub.port", tub_port_endpoint)
|
|
||||||
setnode("tub.location", tub_location_hint)
|
|
||||||
|
|
||||||
_, web_port_endpoint = self.port_assigner.assign(reactor)
|
|
||||||
setnode("web.port", web_port_endpoint)
|
|
||||||
setnode("timeout.keepalive", "600")
|
|
||||||
setnode("timeout.disconnect", "1800")
|
|
||||||
|
|
||||||
sethelper("enabled", "True")
|
|
||||||
|
|
||||||
iyaml = ("introducers:\n"
|
|
||||||
" petname2:\n"
|
|
||||||
" furl: %s\n") % self.introducer_furl
|
|
||||||
iyaml_fn = os.path.join(basedir, "private", "introducers.yaml")
|
|
||||||
fileutil.write(iyaml_fn, iyaml)
|
|
||||||
|
|
||||||
return _render_config(config)
|
|
||||||
|
|
||||||
def _set_up_client_node(self, which):
|
|
||||||
basedir = self.getdir("client%d" % (which,))
|
|
||||||
fileutil.make_dirs(os.path.join(basedir, "private"))
|
|
||||||
if len(SYSTEM_TEST_CERTS) > (which + 1):
|
|
||||||
f = open(os.path.join(basedir, "private", "node.pem"), "w")
|
|
||||||
f.write(SYSTEM_TEST_CERTS[which + 1])
|
|
||||||
f.close()
|
|
||||||
config = self._generate_config(which, basedir)
|
|
||||||
fileutil.write(os.path.join(basedir, 'tahoe.cfg'), config)
|
|
||||||
return basedir
|
|
||||||
|
|
||||||
def bounce_client(self, num):
|
|
||||||
c = self.clients[num]
|
|
||||||
d = c.disownServiceParent()
|
|
||||||
# I think windows requires a moment to let the connection really stop
|
|
||||||
# and the port number made available for re-use. TODO: examine the
|
|
||||||
# behavior, see if this is really the problem, see if we can do
|
|
||||||
# better than blindly waiting for a second.
|
|
||||||
d.addCallback(self.stall, 1.0)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def _stopped(res):
|
|
||||||
new_c = yield client.create_client(self.getdir("client%d" % num))
|
|
||||||
self.clients[num] = new_c
|
|
||||||
new_c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
|
||||||
new_c.setServiceParent(self.sparent)
|
|
||||||
d.addCallback(_stopped)
|
|
||||||
d.addCallback(lambda res: self.wait_for_connections())
|
|
||||||
def _maybe_get_webport(res):
|
|
||||||
if num == 0:
|
|
||||||
# now find out where the web port was
|
|
||||||
self.webish_url = self.clients[0].getServiceNamed("webish").getURL()
|
|
||||||
d.addCallback(_maybe_get_webport)
|
|
||||||
return d
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def add_extra_node(self, client_num, helper_furl=None,
|
|
||||||
add_to_sparent=False):
|
|
||||||
# usually this node is *not* parented to our self.sparent, so we can
|
|
||||||
# shut it down separately from the rest, to exercise the
|
|
||||||
# connection-lost code
|
|
||||||
basedir = FilePath(self.getdir("client%d" % client_num))
|
|
||||||
basedir.makedirs()
|
|
||||||
config = "[client]\n"
|
|
||||||
if helper_furl:
|
|
||||||
config += "helper.furl = %s\n" % helper_furl
|
|
||||||
basedir.child("tahoe.cfg").setContent(config.encode("utf-8"))
|
|
||||||
private = basedir.child("private")
|
|
||||||
private.makedirs()
|
|
||||||
write_introducer(
|
|
||||||
basedir,
|
|
||||||
"default",
|
|
||||||
self.introducer_furl,
|
|
||||||
)
|
|
||||||
|
|
||||||
c = yield client.create_client(basedir.path)
|
|
||||||
self.clients.append(c)
|
|
||||||
c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE)
|
|
||||||
self.numclients += 1
|
|
||||||
if add_to_sparent:
|
|
||||||
c.setServiceParent(self.sparent)
|
|
||||||
else:
|
|
||||||
c.startService()
|
|
||||||
yield self.wait_for_connections()
|
|
||||||
defer.returnValue(c)
|
|
||||||
|
|
||||||
def _check_connections(self):
|
|
||||||
for i, c in enumerate(self.clients):
|
|
||||||
if not c.connected_to_introducer():
|
|
||||||
log.msg("%s not connected to introducer yet" % (i,))
|
|
||||||
return False
|
|
||||||
sb = c.get_storage_broker()
|
|
||||||
connected_servers = sb.get_connected_servers()
|
|
||||||
connected_names = sorted(list(
|
|
||||||
connected.get_nickname()
|
|
||||||
for connected
|
|
||||||
in sb.get_known_servers()
|
|
||||||
if connected.is_connected()
|
|
||||||
))
|
|
||||||
if len(connected_servers) != self.numclients:
|
|
||||||
wanted = sorted(list(
|
|
||||||
client.nickname
|
|
||||||
for client
|
|
||||||
in self.clients
|
|
||||||
))
|
|
||||||
log.msg(
|
|
||||||
"client %s storage broker connected to %s, missing %s" % (
|
|
||||||
i,
|
|
||||||
connected_names,
|
|
||||||
set(wanted) - set(connected_names),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
log.msg("client %s storage broker connected to %s, happy" % (
|
|
||||||
i, connected_names,
|
|
||||||
))
|
|
||||||
up = c.getServiceNamed("uploader")
|
|
||||||
if up._helper_furl and not up._helper:
|
|
||||||
log.msg("Helper fURL but no helper")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def wait_for_connections(self, ignored=None):
|
|
||||||
return self.poll(self._check_connections, timeout=200)
|
|
||||||
|
|
||||||
class CountingDataUploadable(upload.Data):
|
class CountingDataUploadable(upload.Data):
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
@ -999,6 +114,7 @@ class CountingDataUploadable(upload.Data):
|
|||||||
self.interrupt_after_d.callback(self)
|
self.interrupt_after_d.callback(self)
|
||||||
return upload.Data.read(self, length)
|
return upload.Data.read(self, length)
|
||||||
|
|
||||||
|
|
||||||
class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
|
class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
|
||||||
|
|
||||||
timeout = 180
|
timeout = 180
|
||||||
@ -2632,18 +1748,16 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
|
|||||||
newargs = ["--node-directory", self.getdir("client0"), verb] + list(args)
|
newargs = ["--node-directory", self.getdir("client0"), verb] + list(args)
|
||||||
return self.run_bintahoe(newargs, stdin=stdin, env=env)
|
return self.run_bintahoe(newargs, stdin=stdin, env=env)
|
||||||
|
|
||||||
def _check_succeeded(res, check_stderr=True):
|
def _check_succeeded(res):
|
||||||
out, err, rc_or_sig = res
|
out, err, rc_or_sig = res
|
||||||
self.failUnlessEqual(rc_or_sig, 0, str(res))
|
self.failUnlessEqual(rc_or_sig, 0, str(res))
|
||||||
if check_stderr:
|
|
||||||
self.assertIn(err.strip(), (b"", PYTHON_3_WARNING.encode("ascii")))
|
|
||||||
|
|
||||||
d.addCallback(_run_in_subprocess, "create-alias", "newalias")
|
d.addCallback(_run_in_subprocess, "create-alias", "newalias")
|
||||||
d.addCallback(_check_succeeded)
|
d.addCallback(_check_succeeded)
|
||||||
|
|
||||||
STDIN_DATA = b"This is the file to upload from stdin."
|
STDIN_DATA = b"This is the file to upload from stdin."
|
||||||
d.addCallback(_run_in_subprocess, "put", "-", "newalias:tahoe-file", stdin=STDIN_DATA)
|
d.addCallback(_run_in_subprocess, "put", "-", "newalias:tahoe-file", stdin=STDIN_DATA)
|
||||||
d.addCallback(_check_succeeded, check_stderr=False)
|
d.addCallback(_check_succeeded)
|
||||||
|
|
||||||
def _mv_with_http_proxy(ign):
|
def _mv_with_http_proxy(ign):
|
||||||
env = os.environ
|
env = os.environ
|
||||||
@ -2656,7 +1770,6 @@ class SystemTest(SystemTestMixin, RunBinTahoeMixin, unittest.TestCase):
|
|||||||
def _check_ls(res):
|
def _check_ls(res):
|
||||||
out, err, rc_or_sig = res
|
out, err, rc_or_sig = res
|
||||||
self.failUnlessEqual(rc_or_sig, 0, str(res))
|
self.failUnlessEqual(rc_or_sig, 0, str(res))
|
||||||
self.assertIn(err.strip(), (b"", PYTHON_3_WARNING.encode("ascii")))
|
|
||||||
self.failUnlessIn(b"tahoe-moved", out)
|
self.failUnlessIn(b"tahoe-moved", out)
|
||||||
self.failIfIn(b"tahoe-file", out)
|
self.failIfIn(b"tahoe-file", out)
|
||||||
d.addCallback(_check_ls)
|
d.addCallback(_check_ls)
|
||||||
|
@ -122,7 +122,15 @@ class SetDEPMixin(object):
|
|||||||
}
|
}
|
||||||
self.node.encoding_params = p
|
self.node.encoding_params = p
|
||||||
|
|
||||||
|
|
||||||
|
# This doesn't actually implement the whole interface, but adding a commented
|
||||||
|
# interface implementation annotation for grepping purposes.
|
||||||
|
#@implementer(RIStorageServer)
|
||||||
class FakeStorageServer(object):
|
class FakeStorageServer(object):
|
||||||
|
"""
|
||||||
|
A fake Foolscap remote object, implemented by overriding callRemote() to
|
||||||
|
call local methods.
|
||||||
|
"""
|
||||||
def __init__(self, mode, reactor=None):
|
def __init__(self, mode, reactor=None):
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.allocated = []
|
self.allocated = []
|
||||||
|
@ -15,9 +15,14 @@ from os.path import join
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from twisted.trial import unittest
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from testtools.twistedsupport import succeeded
|
||||||
|
|
||||||
|
from ..common import (
|
||||||
|
SyncTestCase,
|
||||||
|
AsyncTestCase,
|
||||||
|
)
|
||||||
|
|
||||||
from foolscap.api import (
|
from foolscap.api import (
|
||||||
fireEventually,
|
fireEventually,
|
||||||
@ -53,6 +58,11 @@ from ..common_web import (
|
|||||||
render,
|
render,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from testtools.matchers import (
|
||||||
|
Equals,
|
||||||
|
AfterPreprocessing,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_introducer_webish(reactor, port_assigner, basedir):
|
def create_introducer_webish(reactor, port_assigner, basedir):
|
||||||
@ -86,11 +96,10 @@ def create_introducer_webish(reactor, port_assigner, basedir):
|
|||||||
|
|
||||||
yield fireEventually(None)
|
yield fireEventually(None)
|
||||||
intro_node.startService()
|
intro_node.startService()
|
||||||
|
|
||||||
defer.returnValue((intro_node, ws))
|
defer.returnValue((intro_node, ws))
|
||||||
|
|
||||||
|
|
||||||
class IntroducerWeb(unittest.TestCase):
|
class IntroducerWeb(AsyncTestCase):
|
||||||
"""
|
"""
|
||||||
Tests for web-facing functionality of an introducer node.
|
Tests for web-facing functionality of an introducer node.
|
||||||
"""
|
"""
|
||||||
@ -102,6 +111,7 @@ class IntroducerWeb(unittest.TestCase):
|
|||||||
# Anything using Foolscap leaves some timer trash in the reactor that
|
# Anything using Foolscap leaves some timer trash in the reactor that
|
||||||
# we have to arrange to have cleaned up.
|
# we have to arrange to have cleaned up.
|
||||||
self.addCleanup(lambda: flushEventualQueue(None))
|
self.addCleanup(lambda: flushEventualQueue(None))
|
||||||
|
return super(IntroducerWeb, self).setUp()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_welcome(self):
|
def test_welcome(self):
|
||||||
@ -187,7 +197,7 @@ class IntroducerWeb(unittest.TestCase):
|
|||||||
self.assertEqual(data["announcement_summary"], {})
|
self.assertEqual(data["announcement_summary"], {})
|
||||||
|
|
||||||
|
|
||||||
class IntroducerRootTests(unittest.TestCase):
|
class IntroducerRootTests(SyncTestCase):
|
||||||
"""
|
"""
|
||||||
Tests for ``IntroducerRoot``.
|
Tests for ``IntroducerRoot``.
|
||||||
"""
|
"""
|
||||||
@ -223,15 +233,11 @@ class IntroducerRootTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
resource = IntroducerRoot(introducer_node)
|
resource = IntroducerRoot(introducer_node)
|
||||||
response = json.loads(
|
response = render(resource, {b"t": [b"json"]})
|
||||||
self.successResultOf(
|
expected = {
|
||||||
render(resource, {b"t": [b"json"]}),
|
u"subscription_summary": {"arbitrary": 2},
|
||||||
),
|
u"announcement_summary": {"arbitrary": 1},
|
||||||
)
|
}
|
||||||
self.assertEqual(
|
self.assertThat(
|
||||||
response,
|
response,
|
||||||
{
|
succeeded(AfterPreprocessing(json.loads, Equals(expected))))
|
||||||
u"subscription_summary": {"arbitrary": 2},
|
|
||||||
u"announcement_summary": {"arbitrary": 1},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
@ -1,311 +0,0 @@
|
|||||||
"""
|
|
||||||
Track the port to Python 3.
|
|
||||||
|
|
||||||
At this point all unit tests have been ported to Python 3, so you can just run
|
|
||||||
them normally.
|
|
||||||
|
|
||||||
This module has been ported to Python 3.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
PORTED_INTEGRATION_TESTS = [
|
|
||||||
"integration.test_aaa_aardvark",
|
|
||||||
"integration.test_servers_of_happiness",
|
|
||||||
"integration.test_sftp",
|
|
||||||
"integration.test_streaming_logs",
|
|
||||||
"integration.test_tor",
|
|
||||||
"integration.test_web",
|
|
||||||
]
|
|
||||||
|
|
||||||
PORTED_INTEGRATION_MODULES = [
|
|
||||||
"integration",
|
|
||||||
"integration.conftest",
|
|
||||||
"integration.util",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Keep these sorted alphabetically, to reduce merge conflicts:
|
|
||||||
PORTED_MODULES = [
|
|
||||||
"allmydata",
|
|
||||||
"allmydata.__main__",
|
|
||||||
"allmydata._auto_deps",
|
|
||||||
"allmydata._monkeypatch",
|
|
||||||
"allmydata.blacklist",
|
|
||||||
"allmydata.check_results",
|
|
||||||
"allmydata.client",
|
|
||||||
"allmydata.codec",
|
|
||||||
"allmydata.control",
|
|
||||||
"allmydata.crypto",
|
|
||||||
"allmydata.crypto.aes",
|
|
||||||
"allmydata.crypto.ed25519",
|
|
||||||
"allmydata.crypto.error",
|
|
||||||
"allmydata.crypto.rsa",
|
|
||||||
"allmydata.crypto.util",
|
|
||||||
"allmydata.deep_stats",
|
|
||||||
"allmydata.dirnode",
|
|
||||||
"allmydata.frontends",
|
|
||||||
"allmydata.frontends.auth",
|
|
||||||
"allmydata.frontends.sftpd",
|
|
||||||
"allmydata.hashtree",
|
|
||||||
"allmydata.history",
|
|
||||||
"allmydata.immutable",
|
|
||||||
"allmydata.immutable.checker",
|
|
||||||
"allmydata.immutable.downloader",
|
|
||||||
"allmydata.immutable.downloader.common",
|
|
||||||
"allmydata.immutable.downloader.fetcher",
|
|
||||||
"allmydata.immutable.downloader.finder",
|
|
||||||
"allmydata.immutable.downloader.node",
|
|
||||||
"allmydata.immutable.downloader.segmentation",
|
|
||||||
"allmydata.immutable.downloader.share",
|
|
||||||
"allmydata.immutable.downloader.status",
|
|
||||||
"allmydata.immutable.encode",
|
|
||||||
"allmydata.immutable.filenode",
|
|
||||||
"allmydata.immutable.happiness_upload",
|
|
||||||
"allmydata.immutable.layout",
|
|
||||||
"allmydata.immutable.literal",
|
|
||||||
"allmydata.immutable.offloaded",
|
|
||||||
"allmydata.immutable.repairer",
|
|
||||||
"allmydata.immutable.upload",
|
|
||||||
"allmydata.interfaces",
|
|
||||||
"allmydata.introducer",
|
|
||||||
"allmydata.introducer.client",
|
|
||||||
"allmydata.introducer.common",
|
|
||||||
"allmydata.introducer.interfaces",
|
|
||||||
"allmydata.introducer.server",
|
|
||||||
"allmydata.monitor",
|
|
||||||
"allmydata.mutable",
|
|
||||||
"allmydata.mutable.checker",
|
|
||||||
"allmydata.mutable.common",
|
|
||||||
"allmydata.mutable.filenode",
|
|
||||||
"allmydata.mutable.layout",
|
|
||||||
"allmydata.mutable.publish",
|
|
||||||
"allmydata.mutable.repairer",
|
|
||||||
"allmydata.mutable.retrieve",
|
|
||||||
"allmydata.mutable.servermap",
|
|
||||||
"allmydata.node",
|
|
||||||
"allmydata.nodemaker",
|
|
||||||
"allmydata.scripts",
|
|
||||||
"allmydata.scripts.admin",
|
|
||||||
"allmydata.scripts.backupdb",
|
|
||||||
"allmydata.scripts.cli",
|
|
||||||
"allmydata.scripts.common_http",
|
|
||||||
"allmydata.scripts.common",
|
|
||||||
"allmydata.scripts.create_node",
|
|
||||||
"allmydata.scripts.debug",
|
|
||||||
"allmydata.scripts.default_nodedir",
|
|
||||||
"allmydata.scripts.runner",
|
|
||||||
"allmydata.scripts.slow_operation",
|
|
||||||
"allmydata.scripts.tahoe_add_alias",
|
|
||||||
"allmydata.scripts.tahoe_backup",
|
|
||||||
"allmydata.scripts.tahoe_check",
|
|
||||||
"allmydata.scripts.tahoe_cp",
|
|
||||||
"allmydata.scripts.tahoe_get",
|
|
||||||
"allmydata.scripts.tahoe_invite",
|
|
||||||
"allmydata.scripts.tahoe_ls",
|
|
||||||
"allmydata.scripts.tahoe_manifest",
|
|
||||||
"allmydata.scripts.tahoe_mkdir",
|
|
||||||
"allmydata.scripts.tahoe_mv",
|
|
||||||
"allmydata.scripts.tahoe_put",
|
|
||||||
"allmydata.scripts.tahoe_run",
|
|
||||||
"allmydata.scripts.tahoe_status",
|
|
||||||
"allmydata.scripts.tahoe_unlink",
|
|
||||||
"allmydata.scripts.tahoe_webopen",
|
|
||||||
"allmydata.scripts.types_",
|
|
||||||
"allmydata.stats",
|
|
||||||
"allmydata.storage_client",
|
|
||||||
"allmydata.storage",
|
|
||||||
"allmydata.storage.common",
|
|
||||||
"allmydata.storage.crawler",
|
|
||||||
"allmydata.storage.expirer",
|
|
||||||
"allmydata.storage.immutable",
|
|
||||||
"allmydata.storage.lease",
|
|
||||||
"allmydata.storage.mutable",
|
|
||||||
"allmydata.storage.server",
|
|
||||||
"allmydata.storage.shares",
|
|
||||||
"allmydata.test",
|
|
||||||
"allmydata.test.cli",
|
|
||||||
"allmydata.test.cli.common",
|
|
||||||
"allmydata.test.cli_node_api",
|
|
||||||
"allmydata.test.common",
|
|
||||||
"allmydata.test.common_util",
|
|
||||||
"allmydata.test.common_web",
|
|
||||||
"allmydata.test.eliotutil",
|
|
||||||
"allmydata.test.no_network",
|
|
||||||
"allmydata.test.matchers",
|
|
||||||
"allmydata.test.mutable",
|
|
||||||
"allmydata.test.mutable.util",
|
|
||||||
"allmydata.test.storage_plugin",
|
|
||||||
"allmydata.test.strategies",
|
|
||||||
"allmydata.test.web",
|
|
||||||
"allmydata.test.web.common",
|
|
||||||
"allmydata.test.web.matchers",
|
|
||||||
"allmydata.testing",
|
|
||||||
"allmydata.testing.web",
|
|
||||||
"allmydata.unknown",
|
|
||||||
"allmydata.uri",
|
|
||||||
"allmydata.util",
|
|
||||||
"allmydata.util._python3",
|
|
||||||
"allmydata.util.abbreviate",
|
|
||||||
"allmydata.util.assertutil",
|
|
||||||
"allmydata.util.base32",
|
|
||||||
"allmydata.util.base62",
|
|
||||||
"allmydata.util.configutil",
|
|
||||||
"allmydata.util.connection_status",
|
|
||||||
"allmydata.util.consumer",
|
|
||||||
"allmydata.util.dbutil",
|
|
||||||
"allmydata.util.deferredutil",
|
|
||||||
"allmydata.util.dictutil",
|
|
||||||
"allmydata.util.eliotutil",
|
|
||||||
"allmydata.util.encodingutil",
|
|
||||||
"allmydata.util.fileutil",
|
|
||||||
"allmydata.util.gcutil",
|
|
||||||
"allmydata.util.happinessutil",
|
|
||||||
"allmydata.util.hashutil",
|
|
||||||
"allmydata.util.humanreadable",
|
|
||||||
"allmydata.util.i2p_provider",
|
|
||||||
"allmydata.util.idlib",
|
|
||||||
"allmydata.util.iputil",
|
|
||||||
"allmydata.util.jsonbytes",
|
|
||||||
"allmydata.util.log",
|
|
||||||
"allmydata.util.mathutil",
|
|
||||||
"allmydata.util.namespace",
|
|
||||||
"allmydata.util.netstring",
|
|
||||||
"allmydata.util.observer",
|
|
||||||
"allmydata.util.pipeline",
|
|
||||||
"allmydata.util.pollmixin",
|
|
||||||
"allmydata.util.rrefutil",
|
|
||||||
"allmydata.util.spans",
|
|
||||||
"allmydata.util.statistics",
|
|
||||||
"allmydata.util.time_format",
|
|
||||||
"allmydata.util.tor_provider",
|
|
||||||
"allmydata.util.yamlutil",
|
|
||||||
"allmydata.web",
|
|
||||||
"allmydata.web.check_results",
|
|
||||||
"allmydata.web.common",
|
|
||||||
"allmydata.web.directory",
|
|
||||||
"allmydata.web.filenode",
|
|
||||||
"allmydata.web.info",
|
|
||||||
"allmydata.web.introweb",
|
|
||||||
"allmydata.web.logs",
|
|
||||||
"allmydata.web.operations",
|
|
||||||
"allmydata.web.private",
|
|
||||||
"allmydata.web.root",
|
|
||||||
"allmydata.web.status",
|
|
||||||
"allmydata.web.storage",
|
|
||||||
"allmydata.web.storage_plugins",
|
|
||||||
"allmydata.web.unlinked",
|
|
||||||
"allmydata.webish",
|
|
||||||
"allmydata.windows",
|
|
||||||
]
|
|
||||||
|
|
||||||
PORTED_TEST_MODULES = [
|
|
||||||
"allmydata.test.cli.test_alias",
|
|
||||||
"allmydata.test.cli.test_backup",
|
|
||||||
"allmydata.test.cli.test_backupdb",
|
|
||||||
"allmydata.test.cli.test_check",
|
|
||||||
"allmydata.test.cli.test_cli",
|
|
||||||
"allmydata.test.cli.test_cp",
|
|
||||||
"allmydata.test.cli.test_create",
|
|
||||||
"allmydata.test.cli.test_create_alias",
|
|
||||||
"allmydata.test.cli.test_invite",
|
|
||||||
"allmydata.test.cli.test_list",
|
|
||||||
"allmydata.test.cli.test_mv",
|
|
||||||
"allmydata.test.cli.test_put",
|
|
||||||
"allmydata.test.cli.test_run",
|
|
||||||
"allmydata.test.cli.test_status",
|
|
||||||
|
|
||||||
"allmydata.test.mutable.test_checker",
|
|
||||||
"allmydata.test.mutable.test_datahandle",
|
|
||||||
"allmydata.test.mutable.test_different_encoding",
|
|
||||||
"allmydata.test.mutable.test_exceptions",
|
|
||||||
"allmydata.test.mutable.test_filehandle",
|
|
||||||
"allmydata.test.mutable.test_filenode",
|
|
||||||
"allmydata.test.mutable.test_interoperability",
|
|
||||||
"allmydata.test.mutable.test_multiple_encodings",
|
|
||||||
"allmydata.test.mutable.test_multiple_versions",
|
|
||||||
"allmydata.test.mutable.test_problems",
|
|
||||||
"allmydata.test.mutable.test_repair",
|
|
||||||
"allmydata.test.mutable.test_roundtrip",
|
|
||||||
"allmydata.test.mutable.test_servermap",
|
|
||||||
"allmydata.test.mutable.test_update",
|
|
||||||
"allmydata.test.mutable.test_version",
|
|
||||||
"allmydata.test.test_abbreviate",
|
|
||||||
"allmydata.test.test_auth",
|
|
||||||
"allmydata.test.test_base32",
|
|
||||||
"allmydata.test.test_base62",
|
|
||||||
"allmydata.test.test_checker",
|
|
||||||
"allmydata.test.test_client",
|
|
||||||
"allmydata.test.test_codec",
|
|
||||||
"allmydata.test.test_common_util",
|
|
||||||
"allmydata.test.test_configutil",
|
|
||||||
"allmydata.test.test_connections",
|
|
||||||
"allmydata.test.test_connection_status",
|
|
||||||
"allmydata.test.test_consumer",
|
|
||||||
"allmydata.test.test_crawler",
|
|
||||||
"allmydata.test.test_crypto",
|
|
||||||
"allmydata.test.test_deepcheck",
|
|
||||||
"allmydata.test.test_deferredutil",
|
|
||||||
"allmydata.test.test_dictutil",
|
|
||||||
"allmydata.test.test_dirnode",
|
|
||||||
"allmydata.test.test_download",
|
|
||||||
"allmydata.test.test_eliotutil",
|
|
||||||
"allmydata.test.test_encode",
|
|
||||||
"allmydata.test.test_encodingutil",
|
|
||||||
"allmydata.test.test_filenode",
|
|
||||||
"allmydata.test.test_happiness",
|
|
||||||
"allmydata.test.test_hashtree",
|
|
||||||
"allmydata.test.test_hashutil",
|
|
||||||
"allmydata.test.test_helper",
|
|
||||||
"allmydata.test.test_humanreadable",
|
|
||||||
"allmydata.test.test_hung_server",
|
|
||||||
"allmydata.test.test_i2p_provider",
|
|
||||||
"allmydata.test.test_immutable",
|
|
||||||
"allmydata.test.test_introducer",
|
|
||||||
"allmydata.test.test_iputil",
|
|
||||||
"allmydata.test.test_json_metadata",
|
|
||||||
"allmydata.test.test_log",
|
|
||||||
"allmydata.test.test_monitor",
|
|
||||||
"allmydata.test.test_multi_introducers",
|
|
||||||
"allmydata.test.test_netstring",
|
|
||||||
"allmydata.test.test_no_network",
|
|
||||||
"allmydata.test.test_node",
|
|
||||||
"allmydata.test.test_observer",
|
|
||||||
"allmydata.test.test_pipeline",
|
|
||||||
"allmydata.test.test_python2_regressions",
|
|
||||||
"allmydata.test.test_python3",
|
|
||||||
"allmydata.test.test_repairer",
|
|
||||||
"allmydata.test.test_runner",
|
|
||||||
"allmydata.test.test_sftp",
|
|
||||||
"allmydata.test.test_spans",
|
|
||||||
"allmydata.test.test_statistics",
|
|
||||||
"allmydata.test.test_stats",
|
|
||||||
"allmydata.test.test_storage",
|
|
||||||
"allmydata.test.test_storage_client",
|
|
||||||
"allmydata.test.test_storage_web",
|
|
||||||
"allmydata.test.test_system",
|
|
||||||
"allmydata.test.test_testing",
|
|
||||||
"allmydata.test.test_time_format",
|
|
||||||
"allmydata.test.test_tor_provider",
|
|
||||||
"allmydata.test.test_upload",
|
|
||||||
"allmydata.test.test_uri",
|
|
||||||
"allmydata.test.test_util",
|
|
||||||
"allmydata.test.web.test_common",
|
|
||||||
"allmydata.test.web.test_grid",
|
|
||||||
"allmydata.test.web.test_introducer",
|
|
||||||
"allmydata.test.web.test_logs",
|
|
||||||
"allmydata.test.web.test_private",
|
|
||||||
"allmydata.test.web.test_root",
|
|
||||||
"allmydata.test.web.test_status",
|
|
||||||
"allmydata.test.web.test_util",
|
|
||||||
"allmydata.test.web.test_web",
|
|
||||||
"allmydata.test.web.test_webish",
|
|
||||||
"allmydata.test.test_windows",
|
|
||||||
]
|
|
@ -127,7 +127,7 @@ def argv_to_abspath(s, **kwargs):
|
|||||||
return abspath_expanduser_unicode(decoded, **kwargs)
|
return abspath_expanduser_unicode(decoded, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def unicode_to_argv(s, mangle=False):
|
def unicode_to_argv(s):
|
||||||
"""
|
"""
|
||||||
Make the given unicode string suitable for use in an argv list.
|
Make the given unicode string suitable for use in an argv list.
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from past.builtins import long
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import re
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.python.filepath import FilePath
|
from twisted.python.filepath import FilePath
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
@ -1551,6 +1552,37 @@ class Statistics(MultiFormatResource):
|
|||||||
req.setHeader("content-type", "text/plain")
|
req.setHeader("content-type", "text/plain")
|
||||||
return json.dumps(stats, indent=1) + "\n"
|
return json.dumps(stats, indent=1) + "\n"
|
||||||
|
|
||||||
|
@render_exception
|
||||||
|
def render_OPENMETRICS(self, req):
|
||||||
|
"""
|
||||||
|
Render our stats in `OpenMetrics <https://openmetrics.io/>` format.
|
||||||
|
For example Prometheus and Victoriametrics can parse this.
|
||||||
|
Point the scraper to ``/statistics?t=openmetrics`` (instead of the
|
||||||
|
default ``/metrics``).
|
||||||
|
"""
|
||||||
|
req.setHeader("content-type", "application/openmetrics-text; version=1.0.0; charset=utf-8")
|
||||||
|
stats = self._provider.get_stats()
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
def mangle_name(name):
|
||||||
|
return re.sub(
|
||||||
|
u"_(\d\d)_(\d)_percentile",
|
||||||
|
u'{quantile="0.\g<1>\g<2>"}',
|
||||||
|
name.replace(u".", u"_")
|
||||||
|
)
|
||||||
|
|
||||||
|
def mangle_value(val):
|
||||||
|
return str(val) if val is not None else u"NaN"
|
||||||
|
|
||||||
|
for (k, v) in sorted(stats['counters'].items()):
|
||||||
|
ret.append(u"tahoe_counters_%s %s" % (mangle_name(k), mangle_value(v)))
|
||||||
|
for (k, v) in sorted(stats['stats'].items()):
|
||||||
|
ret.append(u"tahoe_stats_%s %s" % (mangle_name(k), mangle_value(v)))
|
||||||
|
|
||||||
|
ret.append(u"# EOF\n")
|
||||||
|
|
||||||
|
return u"\n".join(ret)
|
||||||
|
|
||||||
class StatisticsElement(Element):
|
class StatisticsElement(Element):
|
||||||
|
|
||||||
loader = XMLFile(FilePath(__file__).sibling("statistics.xhtml"))
|
loader = XMLFile(FilePath(__file__).sibling("statistics.xhtml"))
|
||||||
|
@ -188,7 +188,17 @@ def initialize():
|
|||||||
# for example, the Python interpreter or any options passed to it, or runner
|
# for example, the Python interpreter or any options passed to it, or runner
|
||||||
# scripts such as 'coverage run'. It works even if there are no such arguments,
|
# scripts such as 'coverage run'. It works even if there are no such arguments,
|
||||||
# as in the case of a frozen executable created by bb-freeze or similar.
|
# as in the case of a frozen executable created by bb-freeze or similar.
|
||||||
sys.argv = argv[-len(sys.argv):]
|
#
|
||||||
|
# Also, modify sys.argv in place. If any code has already taken a
|
||||||
|
# reference to the original argument list object then this ensures that
|
||||||
|
# code sees the new values. This reliance on mutation of shared state is,
|
||||||
|
# of course, awful. Why does this function even modify sys.argv? Why not
|
||||||
|
# have a function that *returns* the properly initialized argv as a new
|
||||||
|
# list? I don't know.
|
||||||
|
#
|
||||||
|
# At least Python 3 gets sys.argv correct so before very much longer we
|
||||||
|
# should be able to fix this bad design by deleting it.
|
||||||
|
sys.argv[:] = argv[-len(sys.argv):]
|
||||||
|
|
||||||
|
|
||||||
def a_console(handle):
|
def a_console(handle):
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
"~",
|
"~",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[tool.towncrier.type]]
|
||||||
|
directory = "security"
|
||||||
|
name = "Security-related Changes"
|
||||||
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "incompat"
|
directory = "incompat"
|
||||||
name = "Backwards Incompatible Changes"
|
name = "Backwards Incompatible Changes"
|
||||||
|
11
tox.ini
11
tox.ini
@ -74,6 +74,8 @@ commands =
|
|||||||
|
|
||||||
tahoe --version
|
tahoe --version
|
||||||
|
|
||||||
|
python -c "import sys; print('sys.stdout.encoding:', sys.stdout.encoding)"
|
||||||
|
|
||||||
# Run tests with -b to catch bugs like `"%s" % (some_bytes,)`. -b makes
|
# Run tests with -b to catch bugs like `"%s" % (some_bytes,)`. -b makes
|
||||||
# Python emit BytesWarnings, and warnings configuration in
|
# Python emit BytesWarnings, and warnings configuration in
|
||||||
# src/allmydata/tests/__init__.py turns allmydata's BytesWarnings into
|
# src/allmydata/tests/__init__.py turns allmydata's BytesWarnings into
|
||||||
@ -114,13 +116,6 @@ commands =
|
|||||||
[testenv:codechecks]
|
[testenv:codechecks]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
setenv =
|
setenv =
|
||||||
# Workaround an error when towncrier is run under the VCS hook,
|
|
||||||
# https://stackoverflow.com/a/4027726/624787:
|
|
||||||
# File "/home/rpatterson/src/work/sfu/tahoe-lafs/.tox/codechecks/lib/python2.7/site-packages/towncrier/check.py", line 44, in __main
|
|
||||||
# .decode(getattr(sys.stdout, "encoding", "utf8"))
|
|
||||||
# `TypeError: decode() argument 1 must be string, not None`
|
|
||||||
PYTHONIOENCODING=utf_8
|
|
||||||
|
|
||||||
# If no positional arguments are given, try to run the checks on the
|
# If no positional arguments are given, try to run the checks on the
|
||||||
# entire codebase, including various pieces of supporting code.
|
# entire codebase, including various pieces of supporting code.
|
||||||
DEFAULT_FILES=src integration static misc setup.py
|
DEFAULT_FILES=src integration static misc setup.py
|
||||||
@ -190,7 +185,7 @@ passenv = TAHOE_LAFS_* PIP_* SUBUNITREPORTER_* USERPROFILE HOMEDRIVE HOMEPATH HO
|
|||||||
whitelist_externals =
|
whitelist_externals =
|
||||||
git
|
git
|
||||||
deps =
|
deps =
|
||||||
# see comment in [testenv] about "certifi"
|
# see comment in [testenv] about "certifi"
|
||||||
certifi
|
certifi
|
||||||
towncrier==21.3.0
|
towncrier==21.3.0
|
||||||
commands =
|
commands =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user