This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 7942310a5 test(services/etcd): add etcd integration test (#2719)
7942310a5 is described below
commit 7942310a5811e745feb92dc446e467a336a1f342
Author: G-XD <[email protected]>
AuthorDate: Thu Jul 27 15:57:50 2023 +0800
test(services/etcd): add etcd integration test (#2719)
* feat(services/etcd)!: del list support
* test(services/etcd): add etcd integration test
* ci(services/etcd): add certificate files
* ci(services/etcd): add etcd single node job & use github action services
* refactor: del unused import
---------
Co-authored-by: GXD <[email protected]>
---
.env.example | 9 +
.github/workflows/service_test_etcd.yml | 215 +++++++++++++++++++++
core/src/services/etcd/backend.rs | 18 --
core/src/services/etcd/fixtures/ca-key.pem | 27 +++
core/src/services/etcd/fixtures/ca.pem | 22 +++
core/src/services/etcd/fixtures/client-key.pem | 5 +
core/src/services/etcd/fixtures/client.pem | 19 ++
core/src/services/etcd/fixtures/etcd1/etcd-key.pem | 5 +
core/src/services/etcd/fixtures/etcd1/etcd.pem | 20 ++
core/src/services/etcd/fixtures/etcd2/etcd-key.pem | 5 +
core/src/services/etcd/fixtures/etcd2/etcd.pem | 20 ++
core/src/services/etcd/fixtures/etcd3/etcd-key.pem | 5 +
core/src/services/etcd/fixtures/etcd3/etcd.pem | 20 ++
core/src/services/etcd/fixtures/server-key.pem | 5 +
core/src/services/etcd/fixtures/server.pem | 20 ++
core/tests/behavior/main.rs | 2 +
16 files changed, 399 insertions(+), 18 deletions(-)
diff --git a/.env.example b/.env.example
index d763a2549..8a4710cb3 100644
--- a/.env.example
+++ b/.env.example
@@ -135,3 +135,12 @@ OPENDAL_DROPBOX_ACCESS_TOKEN=<access_token>
OPENDAL_DROPBOX_REFRESH_TOKEN=<refresh_token>
OPENDAL_DROPBOX_CLIENT_ID=<client_id>
OPENDAL_DROPBOX_CLIENT_SECRET=<client_secret>
+# etcd
+OPENDAL_ETCD_TEST=false
+OPENDAL_ETCD_ENDPOINTS=127.0.0.1:2379
+OPENDAL_ETCD_ROOT=/tmp/opendal/
+OPENDAL_ETCD_USERNAME=<username>
+OPENDAL_ETCD_PASSWORD=<password>
+OPENDAL_ETCD_CA_PATH=<ca_path>
+OPENDAL_ETCD_CERT_PATH=<cert_path>
+OPENDAL_ETCD_KEY_PATH=<key_path>
\ No newline at end of file
diff --git a/.github/workflows/service_test_etcd.yml
b/.github/workflows/service_test_etcd.yml
new file mode 100644
index 000000000..f2782ff95
--- /dev/null
+++ b/.github/workflows/service_test_etcd.yml
@@ -0,0 +1,215 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Service Test Etcd
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ paths:
+ - "core/src/**"
+ - "core/tests/**"
+ - "!core/src/docs/**"
+ - "!core/src/services/**"
+ - "core/src/services/etcd/**"
+ - ".github/workflows/service_test_etcd.yml"
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
+ cancel-in-progress: true
+
+jobs:
+ etcd:
+ runs-on: ubuntu-latest
+
+ services:
+ etcd:
+ image: bitnami/etcd:latest
+ ports:
+ - "2379:2379"
+ - "2380:2380"
+ env:
+ ALLOW_NONE_AUTHENTICATION: yes
+ ETCD_NAME: etcd
+ ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
+ ETCD_ADVERTISE_CLIENT_URLS: http://etcd:2379
+ ETCD_MAX_REQUEST_BYTES: 10485760
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup Rust toolchain
+ uses: ./.github/actions/setup
+ with:
+ need-protoc: true
+ need-nextest: true
+ - name: Test
+ shell: bash
+ working-directory: core
+ run: cargo nextest run etcd --features services-etcd
+ env:
+ OPENDAL_ETCD_TEST: on
+ OPENDAL_ETCD_ENDPOINTS: http://127.0.0.1:2379
+ OPENDAL_ETCD_ROOT: /tmp/opendal
+
+ etcd-cluster:
+ runs-on: ubuntu-latest
+
+ services:
+ etcd1:
+ image: bitnami/etcd:latest
+ ports:
+ - "23790:2379"
+ - "23800:2380"
+ env:
+ ALLOW_NONE_AUTHENTICATION: yes
+ ETCD_NAME: etcd1
+ ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd1:2380
+ ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
+ ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
+ ETCD_ADVERTISE_CLIENT_URLS: http://etcd1:2379
+ ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster
+ ETCD_INITIAL_CLUSTER:
etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
+ ETCD_INITIAL_CLUSTER_STATE: new
+ ETCD_MAX_REQUEST_BYTES: 10485760
+ etcd2:
+ image: bitnami/etcd:latest
+ ports:
+ - "23791:2379"
+ - "23801:2380"
+ env:
+ ALLOW_NONE_AUTHENTICATION: yes
+ ETCD_NAME: etcd2
+ ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd2:2380
+ ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
+ ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
+ ETCD_ADVERTISE_CLIENT_URLS: http://etcd2:2379
+ ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster
+ ETCD_INITIAL_CLUSTER:
etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
+ ETCD_INITIAL_CLUSTER_STATE: new
+ ETCD_MAX_REQUEST_BYTES: 10485760
+ etcd3:
+ image: bitnami/etcd:latest
+ ports:
+ - "23792:2379"
+ - "23802:2380"
+ env:
+ ALLOW_NONE_AUTHENTICATION: yes
+ ETCD_NAME: etcd3
+ ETCD_INITIAL_ADVERTISE_PEER_URLS: http://etcd3:2380
+ ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
+ ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
+ ETCD_ADVERTISE_CLIENT_URLS: http://etcd3:2379
+ ETCD_INITIAL_CLUSTER_TOKEN: etcd-cluster
+ ETCD_INITIAL_CLUSTER:
etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
+ ETCD_INITIAL_CLUSTER_STATE: new
+ ETCD_MAX_REQUEST_BYTES: 10485760
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup Rust toolchain
+ uses: ./.github/actions/setup
+ with:
+ need-protoc: true
+ need-nextest: true
+ - name: Test
+ shell: bash
+ working-directory: core
+ run: cargo nextest run etcd --features services-etcd
+ env:
+ OPENDAL_ETCD_TEST: on
+ OPENDAL_ETCD_ENDPOINTS:
http://127.0.0.1:23790,http://127.0.0.1:23791,http://127.0.0.1:23792
+ OPENDAL_ETCD_ROOT: /tmp/opendal
+
+ etcd-cluster-tls:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Copy Etcd Certificate Files
+ shell: bash
+ working-directory: core
+ run: |
+ mkdir -p /tmp/etcd
+ cp -r `pwd`/src/services/etcd/fixtures/* /tmp/etcd
+
+ - name: Configure Etcd Service With TLS
+ # ETCD will use ports: 23790,23791,23792,23800,23801,23802
+ run: |
+ # Create docker network
+ docker network create -d bridge --subnet=176.16.12.0/24
--gateway=176.16.12.1 etcd-tls-net
+
+ # Launch etcd
+ for no in `seq 0 2`; do \
+ num=$((no+1))
+ ip=$((no+2))
+
+ docker run -d \
+ -p 2379${no}:2379 \
+ -p 2380${no}:2380 \
+ --network etcd-tls-net \
+ --name etcd-tls-${num} \
+ --ip 176.16.12.${ip} \
+ -v /tmp/etcd/ca.pem:/opt/bitnami/etcd/conf/ca.pem \
+ -v /tmp/etcd/server.pem:/opt/bitnami/etcd/conf/server.pem \
+ -v
/tmp/etcd/server-key.pem:/opt/bitnami/etcd/conf/server-key.pem \
+ -v /tmp/etcd/client-key.pem:/opt/bitnami/etcd/client-key.pem \
+ -v /tmp/etcd/client.pem:/opt/bitnami/etcd/client.pem \
+ -v
/tmp/etcd/etcd${num}/etcd.pem:/opt/bitnami/etcd/conf/peer.pem \
+ -v
/tmp/etcd/etcd${num}/etcd-key.pem:/opt/bitnami/etcd/conf/peer-key.pem \
+ -e ETCD_ROOT_PASSWORD=opendal \
+ -e ETCD_CLIENT_CERT_AUTH=true \
+ -e ETCD_PEER_CLIENT_CERT_AUTH=true \
+ -e ETCD_NAME=etcd${num} \
+ -e
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://176.16.12.${ip}:2380 \
+ -e ETCD_LISTEN_PEER_URLS=https://0.0.0.0:2380 \
+ -e ETCD_LISTEN_CLIENT_URLS=https://0.0.0.0:2379 \
+ -e ETCD_ADVERTISE_CLIENT_URLS=https://176.16.12.${ip}:2379 \
+ -e ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster \
+ -e
ETCD_INITIAL_CLUSTER=etcd1=https://176.16.12.2:2380,etcd2=https://176.16.12.3:2380,etcd3=https://176.16.12.4:2380
\
+ -e ETCD_INITIAL_CLUSTER_STATE=new \
+ -e ETCD_MAX_REQUEST_BYTES=10485760 \
+ -e ETCD_TRUSTED_CA_FILE=/opt/bitnami/etcd/conf/ca.pem \
+ -e ETCD_KEY_FILE=/opt/bitnami/etcd/conf/server-key.pem \
+ -e ETCD_CERT_FILE=/opt/bitnami/etcd/conf/server.pem \
+ -e ETCD_PEER_TRUSTED_CA_FILE=/opt/bitnami/etcd/conf/ca.pem \
+ -e ETCD_PEER_KEY_FILE=/opt/bitnami/etcd/conf/peer-key.pem \
+ -e ETCD_PEER_CERT_FILE=/opt/bitnami/etcd/conf/peer.pem \
+ bitnami/etcd:latest
+ done
+
+ - name: Setup Rust toolchain
+ uses: ./.github/actions/setup
+ with:
+ need-protoc: true
+ need-nextest: true
+ - name: Test
+ shell: bash
+ working-directory: core
+ run: cargo nextest run etcd --features services-etcd
+ env:
+ OPENDAL_ETCD_TEST: on
+ OPENDAL_ETCD_ENDPOINTS:
https://127.0.0.1:23790,https://127.0.0.1:23791,https://127.0.0.1:23792
+ OPENDAL_ETCD_ROOT: /tmp/opendal
+ OPENDAL_ETCD_USERNAME: root
+ OPENDAL_ETCD_PASSWORD: opendal
+ OPENDAL_ETCD_CA_PATH: /tmp/etcd/ca.pem
+ OPENDAL_ETCD_CERT_PATH: /tmp/etcd/client.pem
+ OPENDAL_ETCD_KEY_PATH: /tmp/etcd/client-key.pem
diff --git a/core/src/services/etcd/backend.rs
b/core/src/services/etcd/backend.rs
index 0181aeaa5..e2bc89af3 100644
--- a/core/src/services/etcd/backend.rs
+++ b/core/src/services/etcd/backend.rs
@@ -24,7 +24,6 @@ use etcd_client::Certificate;
use etcd_client::Client;
use etcd_client::ConnectOptions;
use etcd_client::Error as EtcdError;
-use etcd_client::GetOptions;
use etcd_client::Identity;
use etcd_client::TlsOptions;
use tokio::sync::OnceCell;
@@ -272,7 +271,6 @@ impl kv::Adapter for Adapter {
read: true,
write: true,
create_dir: true,
- list: true,
..Default::default()
},
@@ -303,22 +301,6 @@ impl kv::Adapter for Adapter {
let _ = client.delete(p, None).await?;
Ok(())
}
-
- async fn scan(&self, path: &str) -> Result<Vec<String>> {
- let p = build_rooted_abs_path(&self.root, path);
- let mut client = self.conn().await?;
- let get_options =
Some(GetOptions::new().with_prefix().with_keys_only());
- let resp = client.get(p, get_options).await?;
- let mut res = Vec::default();
- for kv in resp.kvs() {
- res.push(kv.key_str().map(String::from).map_err(|err| {
- Error::new(ErrorKind::Unexpected, "store key is not valid
utf-8 string")
- .set_source(err)
- })?);
- }
-
- Ok(res)
- }
}
impl From<EtcdError> for Error {
diff --git a/core/src/services/etcd/fixtures/ca-key.pem
b/core/src/services/etcd/fixtures/ca-key.pem
new file mode 100755
index 000000000..a3464d6a6
--- /dev/null
+++ b/core/src/services/etcd/fixtures/ca-key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwUs5Z1iMV+di9PEPRcyQMdt64d2iXHpz/Z+oXP4duQUIKM50
+B8DbOkEab8+Bz7GVCOHB40+XECn56o7wLPErE4YT1a70H3vVB9WuzYq3wYC2xCG5
+MxtH9hoxvi1m7tcvThWzhfpYRqriTcz8rZhD4zppXSotWqh0m/SsOmuuoXdvjjxQ
+JCop2v/GI/MVMvrl3rq/rPIx9xCWQV0VJnHyUyBZqxEgrrfd4+uq8s9/v42MDfAN
+J1QOBZ8dhYdmwOT9+iu9XqJUTUkI2I2xi5yeVNcCNXXjgOK0gUkok52koktPpc3s
+VmHfjbtCrwj3gjbQzeJ8ilojRhmQUfyl8IHzjwIDAQABAoIBACS5CO/kd81fjYJh
+pfgp1B9UxnTQ6b7OGOlPguof8T+sgA9YodyzPDNGGSKy9sOhxmHImlB4V4ffobZ5
+b5HqQ7fS2kPXmFO+8AssAqjUOPMPZCnBCnF2NmbANUEfWRX5Vehs4Q2MMWdDofkL
+/9lvOz6ZY1kq4TD/HiBfDLWQrb3JIZfZ5UhNVQ9VOc5/Yo7MoOxGR4FlTfnsCJo9
+8Hs8YAaDEmbqAcSQV0V3OmsFYCXRRJNXvfsI5SBaHxCPbMO+UavEWwtMUO2O6g3X
+kQBNdU114ihI+k1sfSLB2s7gFuCrXBJM6c0K4ZhLieL0Q5i9DpiFaWwxSUSnmE0z
+0b9zUhECgYEA1jW34kE967Ws0nAfoBPqPpt0t+aYqL2VBcWpWseofM7HMsPXR8PS
+1Wzihd4DtJZJ2zp3KEVIrPWAZarUDHOXzLk1F9QIB5xAqnFz4g7ic85ll71dniUP
+DV3kL2oCemyFvggDiEe5KaTk++0dOLwrnz+Ov8D3d8Z5zl2RK9ZN2AkCgYEA5wDm
+BxRROp3DG25jkMrIiFIyRrJo7aNJjNvfluZOB0X8Z6YaJkE586QdZkDSD6UClnmy
+uXFqgn1Kqw01OjQSa/PXEceOuCTQCmru0k6EompqRMcwS1Ah0BHA7eFbuE8uBaBR
+4hL2dSkbUK2amg1laBdiRmLllx/LPoPZG2pOZNcCgYAM+eATwqP/nVfEv6oKxmoQ
+NNaTNVLWFfXrDZg+uY2JUfVbK+XkoQYqi5gFR/etftUmhJonRFssrwqCHiEOSApQ
+CoAe22dJHrwEFy9P6FrrbPtWf9Al/lS1GJT0ElXoGJ7Zbh7YqI+c6DI+2JRGlY+G
+7BC3qPbUuJ80UxKyNx8sEQKBgAvNC/NIom2wKYt5NDTtZxkvucnKy3l4YgWEJgP7
+d/j/JknVPni91EwSEj5rCVArSdqOFQdMN/i6ldnvLszuZBKHvnD2FOBrYQVnORQd
+VsFFVnB1DI6MZW3Pul9sp9belKdM7WHzPgv2MaMJe2BrVsbeivK1kq0JvsRO0ASK
+hb65AoGAOjzkt4Op82qe8AtA4ACIM5TdsTtzG2ltCWsNyjQhQjnQi2+iZz9hJU4h
+kd9pQHsOxbnMVTn1ImbLO+gwVIRv/1x+y4NM6srqXPAGGC3Kz8FP8SYA72LD9bHT
+P5/1DqPUS3Rs9iIUQu7/0c0gGcqtpcq184P1kGQj9bCtqyBgS7o=
+-----END RSA PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/ca.pem
b/core/src/services/etcd/fixtures/ca.pem
new file mode 100755
index 000000000..d2b3ba791
--- /dev/null
+++ b/core/src/services/etcd/fixtures/ca.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrjCCApagAwIBAgIUX/cD1QAVA9NZq0Hhuyms/cm2PWUwDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMF0xCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxEDAOBgNVBAMTB0V0Y2QtQ0EwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDBSzlnWIxX52L08Q9FzJAx23rh3aJcenP9n6hc/h25
+BQgoznQHwNs6QRpvz4HPsZUI4cHjT5cQKfnqjvAs8SsThhPVrvQfe9UH1a7NirfB
+gLbEIbkzG0f2GjG+LWbu1y9OFbOF+lhGquJNzPytmEPjOmldKi1aqHSb9Kw6a66h
+d2+OPFAkKina/8Yj8xUy+uXeur+s8jH3EJZBXRUmcfJTIFmrESCut93j66ryz3+/
+jYwN8A0nVA4Fnx2Fh2bA5P36K71eolRNSQjYjbGLnJ5U1wI1deOA4rSBSSiTnaSi
+S0+lzexWYd+Nu0KvCPeCNtDN4nyKWiNGGZBR/KXwgfOPAgMBAAGjZjBkMA4GA1Ud
+DwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBQKWiXsC5E3
+r2wxv2maUJBBEb2PZjAfBgNVHSMEGDAWgBQKWiXsC5E3r2wxv2maUJBBEb2PZjAN
+BgkqhkiG9w0BAQsFAAOCAQEAVq6sfski5KvRDKSfOqRLC4czmuY97DDptOQmSqrh
+oSDcCayclxJHebBJtEcAzJWvulujDDV/nGy3BAljfq1nUhCgJw05C5MpMDIfnmB0
+5GBiRFvPK8GiLnAkekcrxsIsvZKAx7x9zvVuCaw9iMX1CZrz63Ah2GEkTUHMhUKW
+/KNuppD9ljT6PsQnoUZbBDeoLW1fL5/XK+n6WUUyTh8QipAT1HI3RLhgDqjhySz5
+cggcEXbalAUTFdf9WN/aT9xRjOJiopfd/cRfQlaZQnayP5j8yAdZa7jpVpeGkM1v
+27Tsck/fOqs9M6tjxKU6CQULMHA5zhqGt6btyRHIKKqvSA==
+-----END CERTIFICATE-----
diff --git a/core/src/services/etcd/fixtures/client-key.pem
b/core/src/services/etcd/fixtures/client-key.pem
new file mode 100755
index 000000000..da243b1c6
--- /dev/null
+++ b/core/src/services/etcd/fixtures/client-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIN1WjTUXMgX9e3n9itoGPNscpcTinyzuWbittQPZvDR7oAoGCCqGSM49
+AwEHoUQDQgAE6MZqWS6Kf0CWVm9BztTG+BwwFRjnSNrKF2FdjpFdoBg2r67CpdUw
+8Uet7Cc8AMRucNDX9H1LIuWfWfelxPj01w==
+-----END EC PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/client.pem
b/core/src/services/etcd/fixtures/client.pem
new file mode 100755
index 000000000..6500f60b0
--- /dev/null
+++ b/core/src/services/etcd/fixtures/client.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDETCCAfmgAwIBAgIUFPimgsvl22lnZ05oMrA2w7YKV4cwDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMG0xCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxIDAeBgNVBAMTF2V0Y2Qtc3NsLWNsdXN0ZXItY2xpZW50
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6MZqWS6Kf0CWVm9BztTG+BwwFRjn
+SNrKF2FdjpFdoBg2r67CpdUw8Uet7Cc8AMRucNDX9H1LIuWfWfelxPj016OBgzCB
+gDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/
+BAIwADAdBgNVHQ4EFgQUhQW+u58dv8XcuiSNzMMfVL4DK+4wHwYDVR0jBBgwFoAU
+Clol7AuRN69sMb9pmlCQQRG9j2YwCwYDVR0RBAQwAoIAMA0GCSqGSIb3DQEBCwUA
+A4IBAQCK9bYRt7wX2gOkdIRhopsqctQNHDXkp/lNO/q4+60Y6aul6krzKf5LNXYC
+cLNDIF19NSXdE9mbL8rieMw4M12qMRdhgFaodPWNt5byjJVtojMVD9SGVvT3Qq7+
+sL1XriBvRTj5Gt1Nd22PHxGpZhvWNMkcXnOhgEsupSAQqn96hwI4UJ9PFEeHZW6e
+YdZGmgRPSvKqSWseVH7t2lu19b0ppRENvACH+X5XVMoSqCINuIMHBBozjEwAfhCY
+gF2FCSx1ssp+fbjY4mchI2DF8FHYDh6FU9EeoioP0rt/kyJJP/JVQ9+177Iom9LJ
+tKBf6RVzgybmLl0R+AP/joPdbz/b
+-----END CERTIFICATE-----
diff --git a/core/src/services/etcd/fixtures/etcd1/etcd-key.pem
b/core/src/services/etcd/fixtures/etcd1/etcd-key.pem
new file mode 100755
index 000000000..7db8338f3
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd1/etcd-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIPptY4Vu8m64Hi4ER01xvjhbhWTY92CG2X8F5ijTl0q7oAoGCCqGSM49
+AwEHoUQDQgAEpPQwhr++uYtl/rFq6lcKwfORfARwqOrVqJSAsLB2DO/hWKrA3cIw
+4EsPnMnR6Gk8yIthUWTpyEmiB+AbvKys5g==
+-----END EC PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/etcd1/etcd.pem
b/core/src/services/etcd/fixtures/etcd1/etcd.pem
new file mode 100755
index 000000000..1f0346b48
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd1/etcd.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiWgAwIBAgIULR0Dl9a49uPeerAAjNrIPVf8GAAwDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMGwxCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxHzAdBgNVBAMTFmV0Y2Qtc3NsLWNsdXN0ZXItbm9kZTEw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASk9DCGv765i2X+sWrqVwrB85F8BHCo
+6tWolICwsHYM7+FYqsDdwjDgSw+cydHoaTzIi2FRZOnISaIH4Bu8rKzmo4GwMIGt
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU6vabBjFZsf4BK21+WJTRImFQyS4wHwYD
+VR0jBBgwFoAUClol7AuRN69sMb9pmlCQQRG9j2YwLgYDVR0RBCcwJYIFZXRjZDGH
+BLAQDAGHBLAQDAKHBLAQDAOHBLAQDASHBH8AAAEwDQYJKoZIhvcNAQELBQADggEB
+ADkI79ANi4Iz2B9mou5OaOhbTEhali8uiUy1Mhws9ltsYSy3PT4v+SxumZDl7Fzt
+xR5bep5BrkBoJdmcGY5f37bvoLnLXfZZzZBGGO9yzUjRkpT2VzFxzJYV+mVXBMHx
+25n3rfkK37pxebpDl/2MEoO3Mhd9ohT2BR+6Hf3SjC3Jv6Hz0EU2iCfM22GckqoH
+24UYPGHVrq5glA/Kox2H/UdzxWA/+/pHIdJder2cJ9eGLlp5NvW2QkDEykrvgxK6
+TD7yYga2mtUPmd879pW9jDR9lk61cggbr+UCx9Xu6vz1LWQLn6wV7nEOWDbL/x2k
+3Erff2yXewjTM0q1nTIa44I=
+-----END CERTIFICATE-----
diff --git a/core/src/services/etcd/fixtures/etcd2/etcd-key.pem
b/core/src/services/etcd/fixtures/etcd2/etcd-key.pem
new file mode 100755
index 000000000..bf02cd183
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd2/etcd-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIBDg2ZsdOxwunbnwgBQJNz0/WKf8FqCQEHupvz6RVjLKoAoGCCqGSM49
+AwEHoUQDQgAEgEg7aDPmf7JsB67OlMom3ifAWD4Ng/sL4LlsBtQzQrM0ypgHwbPt
+plPmJXtMdUMckb/AUmszBaEZv8+SayR3bg==
+-----END EC PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/etcd2/etcd.pem
b/core/src/services/etcd/fixtures/etcd2/etcd.pem
new file mode 100755
index 000000000..fe45c56d4
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd2/etcd.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiWgAwIBAgIUZNqWdal3PrEAFz3cIR4soTanvW0wDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMGwxCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxHzAdBgNVBAMTFmV0Y2Qtc3NsLWNsdXN0ZXItbm9kZTIw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASASDtoM+Z/smwHrs6UyibeJ8BYPg2D
++wvguWwG1DNCszTKmAfBs+2mU+Yle0x1QxyRv8BSazMFoRm/z5JrJHduo4GwMIGt
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUQ2Xzb/5Jv18Yf68Zfaf52TZzP2UwHwYD
+VR0jBBgwFoAUClol7AuRN69sMb9pmlCQQRG9j2YwLgYDVR0RBCcwJYIFZXRjZDKH
+BLAQDAGHBLAQDAKHBLAQDAOHBLAQDASHBH8AAAEwDQYJKoZIhvcNAQELBQADggEB
+AJRJlU/q4xxsr3+TuOdihkkbwKzDUtiruXtVVbUsbUQFV1F+8EsWAqwGk83sQN/m
+UnmRxSI3V84Ld/ErszkTINaOb0hItzNuL8BoDhoUD5QjnZDjLavktO74UYosGqam
+GLJvm7DqV+bjT1RivdJlYHeRajGOssJ60fmBe77iN85OfmDsH+pJvkqpK2hO72h3
+Vuj9SqTBMxRWO3TaOpjexV+0+IupbW4SjGJfKlzOgspz665svib6WFCysG6990WR
+ilTYybzpbbGV/MI9ivKPOiqv1pPr7F8e7QEC6SalF/0mu3R2cl7n7qSp11LknMp6
++WJeRJUKnFkWaeol5bT/dnA=
+-----END CERTIFICATE-----
diff --git a/core/src/services/etcd/fixtures/etcd3/etcd-key.pem
b/core/src/services/etcd/fixtures/etcd3/etcd-key.pem
new file mode 100755
index 000000000..ffc823c42
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd3/etcd-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIJsVUbEva9NBJDcNggMF/+MPCXh5thEpNvj/2I5q9E4toAoGCCqGSM49
+AwEHoUQDQgAEL2in+bs7DX3x/ZddtUND2d1A1eNxbTqTc9MrPwnu5d7f40xxJgDd
+Ck0PbOD0TrMfiixdTVDuBHJrigl+sasZpg==
+-----END EC PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/etcd3/etcd.pem
b/core/src/services/etcd/fixtures/etcd3/etcd.pem
new file mode 100755
index 000000000..15829a792
--- /dev/null
+++ b/core/src/services/etcd/fixtures/etcd3/etcd.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPTCCAiWgAwIBAgIUXOr/3WG8NfNx2ZNGzgNhvvmfHxQwDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMGwxCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxHzAdBgNVBAMTFmV0Y2Qtc3NsLWNsdXN0ZXItbm9kZTMw
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQvaKf5uzsNffH9l121Q0PZ3UDV43Ft
+OpNz0ys/Ce7l3t/jTHEmAN0KTQ9s4PROsx+KLF1NUO4EcmuKCX6xqxmmo4GwMIGt
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUjDWuHf2AZ1xJYcZEfw4NwQN+w3owHwYD
+VR0jBBgwFoAUClol7AuRN69sMb9pmlCQQRG9j2YwLgYDVR0RBCcwJYIFZXRjZDOH
+BLAQDAGHBLAQDAKHBLAQDAOHBLAQDASHBH8AAAEwDQYJKoZIhvcNAQELBQADggEB
+AGkcfVhz018UBGVn6ea2WXM9rIXWK72aFT041idO6haxQ5KsNGrffC8GZ2p3PFhh
+t1VZEIXbahdO+qf0LCXuvBNw4hQb/L9iYGbxbYV0OHqkvl4mT4rj5KSnsWBuA6dR
+UgYqvc82kh9T0PJNBKClJOP6X6238kEP1p/zc4bkTtq10wD2qwMYnu7O0Fp6hZOG
+/rYRJh3jrkhCgoN0U/vHmt2LMVMh6WvkiE5Pz+JqjMMKuZ+8WDz/UJ34PwGVcwP4
+BnzS760CGS4QcM5giZuzFDEYWy18PAxqbvEmptFYfuoSJDMqiUQaabDdmx9AvY6a
+RGGxmnj48YkWfBF6kvcqeeo=
+-----END CERTIFICATE-----
diff --git a/core/src/services/etcd/fixtures/server-key.pem
b/core/src/services/etcd/fixtures/server-key.pem
new file mode 100755
index 000000000..36b427d56
--- /dev/null
+++ b/core/src/services/etcd/fixtures/server-key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMlIlwW4MO8Cy/eFtCbdQK5NLQrr3q0eNO/GCQoJvp6OoAoGCCqGSM49
+AwEHoUQDQgAE+nfyvCG7rWbJ6vAgxSPCpVnVbkDCC9+vJ70/+E3BeqzYe2zDqfnx
+jXDOSCRxHoU+VzuwANCT0QmUXnAMjIOksg==
+-----END EC PRIVATE KEY-----
diff --git a/core/src/services/etcd/fixtures/server.pem
b/core/src/services/etcd/fixtures/server.pem
new file mode 100755
index 000000000..637aadb05
--- /dev/null
+++ b/core/src/services/etcd/fixtures/server.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDNzCCAh+gAwIBAgIUTi5Sgt66TA3a/xl6hZ+Oa4GouW8wDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAkpTMQswCQYDVQQHEwJTWjESMBAG
+A1UEChMJSGlsbHN0b25lMQ4wDAYDVQQLEwVDbG91ZDEQMA4GA1UEAxMHRXRjZC1D
+QTAeFw0yMzA3MjYxNTM0MDBaFw0yODA3MjQxNTM0MDBaMG0xCzAJBgNVBAYTAkNO
+MQswCQYDVQQIEwJKUzELMAkGA1UEBxMCU1oxEjAQBgNVBAoTCUhpbGxzdG9uZTEO
+MAwGA1UECxMFQ2xvdWQxIDAeBgNVBAMTF2V0Y2Qtc3NsLWNsdXN0ZXItc2VydmVy
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+nfyvCG7rWbJ6vAgxSPCpVnVbkDC
+C9+vJ70/+E3BeqzYe2zDqfnxjXDOSCRxHoU+VzuwANCT0QmUXnAMjIOksqOBqTCB
+pjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
+MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNq4VOe+O3mvZUqCyYp1ruuFP8wrMB8G
+A1UdIwQYMBaAFApaJewLkTevbDG/aZpQkEERvY9mMCcGA1UdEQQgMB6HBLAQDAGH
+BLAQDAKHBLAQDAOHBLAQDASHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAFZQG6wv
+Ii1qM5dmtlOGq2n1pUUCy/cxexgB3/K5jzCBJWHw6QErUG2HOp8ZFoAxmGCn+Dng
+b2Yj05wQSkxNYjrW6UrjD1GmbPQZm58vVbM6Zseu/aN10nWhW+UV1lzUKVa+Kc7J
+d/wWGYoKPazLQT8mX1DNOObLjd5Z8EewxbXedt789s9n6Ylxl4lutRKCX7q5kvW7
+RV5Y/kPc4JdADQK6okqxHP8A631HfX2upUAMY/DfVyvukgZ3s4TwlOYrUjarqHp5
+ABtdvPlgLFk2L9yBKFGEfkXagkodIWrgbBBy2wN10MnjVHWQXZoROPZXIt8ZzyYk
+nfURzuaBhGmsEzI=
+-----END CERTIFICATE-----
diff --git a/core/tests/behavior/main.rs b/core/tests/behavior/main.rs
index cc6d7f977..af51b8c15 100644
--- a/core/tests/behavior/main.rs
+++ b/core/tests/behavior/main.rs
@@ -107,6 +107,8 @@ fn main() -> anyhow::Result<()> {
tests.extend(behavior_test::<services::Cos>());
#[cfg(feature = "services-dashmap")]
tests.extend(behavior_test::<services::Dashmap>());
+ #[cfg(feature = "services-etcd")]
+ tests.extend(behavior_test::<services::Etcd>());
#[cfg(feature = "services-fs")]
tests.extend(behavior_test::<services::Fs>());
#[cfg(feature = "services-ftp")]