aioetcd3 version grpc aio

This commit is contained in:
2025-01-19 20:43:02 +01:00
commit a3e8dc2be6
45 changed files with 6254 additions and 0 deletions

0
test/__init__.py Normal file
View File

23
test/cfssl/ca.pem Normal file
View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID3jCCAsagAwIBAgIUfLIjRt7LfB0n9SVKLCfJR54lUdYwDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
bml0IDIxEjAQBgNVBAMTCU15IG93biBDQTAeFw0xNzA4MDEwODM3MDBaFw0yMjA3
MzEwODM3MDBaMHUxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1TYW4gRnJhbmNpc2Nv
MQswCQYDVQQHEwJDQTEYMBYGA1UEChMPTXkgQ29tcGFueSBOYW1lMRMwEQYDVQQL
EwpPcmcgVW5pdCAyMRIwEAYDVQQDEwlNeSBvd24gQ0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDb8Ec7DVjLw74wmnG6Ke0DBXBKRxy2MVdQNw6a6vQ1
UPXcjPjctPVoy1IrDK6f7CH3LGeiAv/g2zbgDQdRT/f3b986DxvBQMRj/rmRCsp4
pcd+Nt0LtKBmKJCA7kk+urx/gmAS/9wa7RcC9kRg3husihIpa02AEMtd759Czjgy
JHlCtlIoBSqxCqrEkKzc0Zw8SfDI7zKtOGlfA9bia6lx/y3TMvdCuPrDAvf1FSSj
ECdXL70jYSSgA40VvhBVF5Nom/gsJ+/DmrYNwsGiA4klFp4ip4eKIyBcyk/Ni1uG
wzQoLSaB0UqUCXKvWCimCzEnl/I0IJZ/TcrFmNoauGuxAgMBAAGjZjBkMA4GA1Ud
DwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1UdDgQWBBRsYfmUuh40
o+DpLTJauE06g098JzAfBgNVHSMEGDAWgBRsYfmUuh40o+DpLTJauE06g098JzAN
BgkqhkiG9w0BAQsFAAOCAQEAR/+35fG9b0Wt7jwoUF3L2A2OrZa36avb8ktkC/OO
qm7skzUBDb7iz+iznEDICgrvecjXwq+te5ob92H3weDs6YJz0+T8EXBnUtzN2+bu
eapdky5dZwweMqofr0FF2hLUVPWErgsZRj1gH1eLbFSirwtCbskmAzqK5TRKCtQL
cOZ/WlsgmCdETzHSLztdKKTau1l/qHJBdH7hIppG4iEISMueHlW+H9+yu9haKu2L
4J9feFOqC8G/aR+81og79WGwb2HJWgpw92ji8JxLvF5M1B++9AvwndkovkVgjFnk
JBDagrwsg/gr+FVi3uw5NnktLgtzzcMD0VWGCmAEn/R3Mg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKIpaNwPGovuVxJM1GMGiJDHZJWaDzJyXeXPoIjGsJqfoAoGCCqGSM49
AwEHoUQDQgAEYwsP1I307m9u1wrsqjacF5xdSk67iifUGT/MqbSSBtGlRK05VDn2
87ghkIrsX1B7j/LJcUCDLnzmJVPjfa8lWg==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMD5KcGoWPEneh+YMw36oJqC8wwJHyTlP1saFYuqUSKGoAoGCCqGSM49
AwEHoUQDQgAELMjGp4dyHA3vW3nU8XHh+JIT5B/bdIaSvVpJIoTgFPNoLpDspJmt
GAxStBczUE8rwmRLfNbk0aG8zn8EsZoqDA==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/TCCAeWgAwIBAgIUD8TzummshnrEovvmr4wqLzawxlowDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
bml0IDIxEjAQBgNVBAMTCU15IG93biBDQTAeFw0xNzA4MDEwOTU5MDBaFw0yMjA3
MzEwOTU5MDBaMEExCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1TYW4gRnJhbmNpc2Nv
MQswCQYDVQQHEwJDQTENMAsGA1UEAxMEcm9vdDBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABCzIxqeHchwN71t51PFx4fiSE+Qf23SGkr1aSSKE4BTzaC6Q7KSZrRgM
UrQXM1BPK8JkS3zW5NGhvM5/BLGaKgyjgYMwgYAwDgYDVR0PAQH/BAQDAgWgMBMG
A1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJZWw4xW
lf9oa/ioycvblk/fzktLMB8GA1UdIwQYMBaAFGxh+ZS6HjSj4OktMlq4TTqDT3wn
MAsGA1UdEQQEMAKCADANBgkqhkiG9w0BAQsFAAOCAQEAo4N4uDXl6nIG8ndrOeoe
S2JpPTU+gkaez2fs21DpuGO3SLSEnIYLcaY3p6sdjU2m0m2yGweLKLfVmQLzHO0R
4sZtKQFY1sklhCAhmiU5YZbb98gyXMfPVaFXCy5IWnajDsmhmh0G0UbVV/zaWJw+
B+yzGVvWMBI4htG9Zz59yIt4Fku2TgqDudiFEzm9OB9LykYS+oKKLqb2DlLmWSdu
NOr0j+sSwzTiGNSstb4jaXhbO2f80mykg4Rs5oFbiqYMH7qOfk5uR/uSomu+l03v
cv9t71iCZ1/ss+ZgfS24crnsAUqUZBRmPGu1lrXRaVNzkK9BGZ5XAHT2eBEQAlqp
OA==
-----END CERTIFICATE-----

19
test/cfssl/client.pem Normal file
View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIUfo2E4cRuVDSGcUlQbc8VWD0EHZcwDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
bml0IDIxEjAQBgNVBAMTCU15IG93biBDQTAeFw0xNzA4MDEwOTU4MDBaFw0yMjA3
MzEwOTU4MDBaMEMxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1TYW4gRnJhbmNpc2Nv
MQswCQYDVQQHEwJDQTEPMA0GA1UEAxMGY2xpZW50MFkwEwYHKoZIzj0CAQYIKoZI
zj0DAQcDQgAEYwsP1I307m9u1wrsqjacF5xdSk67iifUGT/MqbSSBtGlRK05VDn2
87ghkIrsX1B7j/LJcUCDLnzmJVPjfa8lWqOBgzCBgDAOBgNVHQ8BAf8EBAMCBaAw
EwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUG6Sc
mNr0LfHR3Th2ER8mo8UFOSIwHwYDVR0jBBgwFoAUbGH5lLoeNKPg6S0yWrhNOoNP
fCcwCwYDVR0RBAQwAoIAMA0GCSqGSIb3DQEBCwUAA4IBAQAk5+gTElEnkaKRIuy2
Uf/8GRSFAmlCbuEVGGuQ4Iif1KWVt0sklUC4EpknJGCDBDfRlH/n/O0cIAhxHJVd
8HXRfYy+ynhr08gdE7lbueavEpvUb3QNFR8ZrODcqwvJgHyWsffk7f87hpsx9lr2
mfLvokau0UhpVlq+x7IQ5dKw/ZzKv/zjI/A2guwK1UWdk5vr0W7LE5XY4pa+9/Qy
y6FxHcKc4z8FJ6ClRGy/RGJrbg0VgCmrTMa7NCpIyZ/onn7RaxHatSn7cnQPVNKq
fBHHBVdl1LlsJSPRZar0zGkUS1UeCtcSW1aqSmkO39p18tJ9hDWM0xkY6FlI3A+4
shAQ
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKiO/qjfQEIJS4OYW3rHUQodAEk2/PzNj+V6Oy/+JSnIoAoGCCqGSM49
AwEHoUQDQgAE2azbBMQaqhuTm5d+7rzIdqlXnBrv0LxwDLIQnUTosRYlrS19+gEg
AccoyJEyzGFzd3+Ot6OOX3nXUxDSVFzlLA==
-----END EC PRIVATE KEY-----

19
test/cfssl/server.pem Normal file
View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFjCCAf6gAwIBAgIUHeUTcT0g5zmaUzPKeqEi6AZMZ5wwDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
bml0IDIxEjAQBgNVBAMTCU15IG93biBDQTAeFw0xNzA4MDIwODM4MDBaFw0yMjA4
MDEwODM4MDBaMEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1TYW4gRnJhbmNpc2Nv
MQswCQYDVQQHEwJDQTEQMA4GA1UEAxMHbWVtYmVyMzBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABNms2wTEGqobk5uXfu68yHapV5wa79C8cAyyEJ1E6LEWJa0tffoB
IAHHKMiRMsxhc3d/jrejjl9511MQ0lRc5SyjgZkwgZYwDgYDVR0PAQH/BAQDAgWg
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKmG
Ok0gF06fFZlNPVR8QoF3i7/XMB8GA1UdIwQYMBaAFGxh+ZS6HjSj4OktMlq4TTqD
T3wnMCEGA1UdEQQaMBiHBH8AAAGHBGRJLgOHBGRJLgSHBGRJLgUwDQYJKoZIhvcN
AQELBQADggEBAMOUiyg+R6Qh6CFmNjxZJCk1hlRPrYucA/7phWRsqOI/PCFpak8d
LIOCQdPwTC20hqZxszN0GmJVdIZy1FEDa7yDZip2pYrmCR2ePAAwXtEhUAGBblIU
IAFiLtFps5GQqi3+f9SUto0HnriQiBt8fSf4KNeD84gkOt524qliAY2bIOdphJaY
Y+Qg6jkn8+lzoY+rD8tzOx37dgL+/V0dCyo7fBLnW6mOEdHk5sMpPPyWzmCYSdeb
SqNpp9+EDOVhz175CQuuq0WD+x3D210UIQsEFvyjg/W6Vinl9W/hOG6yxv01IVcM
yhngfepPKZ20b4njhHJv+geOn5yshCcTQjI=
-----END CERTIFICATE-----

279
test/test_auth.py Normal file
View File

@@ -0,0 +1,279 @@
import unittest
import asyncio
import functools
from aioetcd3.client import client, ssl_client, set_grpc_cipher
from aioetcd3.help import range_all, PER_RW
from aioetcd3.exceptions import AuthError, Unauthenticated, PermissionDenied
from .utils import switch_auth_off, switch_auth_on
def asynctest(f):
@functools.wraps(f)
def _f(self):
asyncio.get_event_loop().run_until_complete(f(self))
return _f
TEST_USER_NAME = 'test'
TEST_USER_PASSWORD = "test"
TEST_ROLE_NAME = 'admin'
class AuthTest(unittest.TestCase):
@asynctest
async def setUp(self):
endpoints = "127.0.0.1:2379"
self.client = client(endpoint=endpoints)
set_grpc_cipher()
auth_etcd_url = "127.0.0.1:2378"
self.root_client = ssl_client(endpoint=auth_etcd_url, ca_file="test/cfssl/ca.pem",
cert_file="test/cfssl/client-root.pem",
key_file="test/cfssl/client-root-key.pem")
self.client_client = ssl_client(endpoint=auth_etcd_url, ca_file="test/cfssl/ca.pem",
cert_file="test/cfssl/client.pem",
key_file="test/cfssl/client-key.pem")
await self.cleanUp()
@asynctest
async def test_auth_1(self):
await self.client.user_add(username=TEST_USER_NAME, password='1234')
users = await self.client.user_list()
self.assertIn(TEST_USER_NAME, users)
roles = await self.client.user_get(username=TEST_USER_NAME)
self.assertEqual(len(roles), 0)
await self.client.user_change_password(username=TEST_USER_NAME, password=TEST_USER_PASSWORD)
await self.client.user_delete(username=TEST_USER_NAME)
@asynctest
async def test_auth_2(self):
await self.client.role_add(name=TEST_ROLE_NAME)
roles = await self.client.role_list()
self.assertIn(TEST_ROLE_NAME, roles)
role_info = await self.client.role_get(name=TEST_ROLE_NAME)
await self.client.role_delete(name=TEST_ROLE_NAME)
@asynctest
async def test_auth_3(self):
await self.client.user_add(username=TEST_USER_NAME, password=TEST_USER_PASSWORD)
with self.assertRaises(Exception):
await self.client.user_grant_role(username=TEST_USER_NAME, role=TEST_ROLE_NAME)
await self.client.role_add(name=TEST_ROLE_NAME)
await self.client.user_grant_role(username=TEST_USER_NAME, role=TEST_ROLE_NAME)
await self.client.role_grant_permission(name=TEST_ROLE_NAME,
key_range=range_all(),
permission=PER_RW)
await self.client.user_revoke_role(username=TEST_USER_NAME, role=TEST_ROLE_NAME)
await self.client.role_revoke_permission(name=TEST_ROLE_NAME,
key_range=range_all())
@asynctest
async def test_auth_4(self):
await self.root_client.user_add(username='root', password='root')
await self.root_client.role_add(name='root')
await self.root_client.user_grant_role(username='root', role='root')
await self.root_client.auth_enable()
await self.root_client.user_add(username='client', password='client')
await self.root_client.role_add(name='client')
await self.root_client.put('/foo', '/foo')
value, meta = await self.root_client.get('/foo')
self.assertEqual(value, b'/foo')
with self.assertRaises(Exception):
await self.client_client.get('/foo')
await self.root_client.role_grant_permission(name='client', key_range='/foo', permission=PER_RW)
await self.root_client.user_grant_role(username='client', role='client')
value, meta = await self.client_client.get('/foo')
self.assertEqual(value, b'/foo')
await self.client_client.put('/foo', 'ssss')
async def delete_all_user(self):
users = await self.client.user_list()
for u in users:
await self.client.user_delete(username=u)
users = await self.root_client.user_list()
for u in users:
await self.root_client.user_delete(username=u)
async def delete_all_role(self):
roles = await self.client.role_list()
for r in roles:
await self.client.role_delete(name=r)
roles = await self.root_client.role_list()
for r in roles:
await self.root_client.role_delete(name=r)
async def cleanUp(self):
await self.client.delete(range_all())
await self.root_client.auth_disable()
await self.delete_all_user()
await self.delete_all_role()
@asynctest
async def tearDown(self):
await self.cleanUp()
await self.client.close()
class PasswordAuthTest(unittest.TestCase):
@asynctest
async def setUp(self):
self.endpoints = "127.0.0.1:2379"
self.unauthenticated_client = client(endpoint=self.endpoints)
await self.cleanUp()
await switch_auth_on(self.unauthenticated_client)
self.client_client = client(
endpoint=self.endpoints, username="client", password="client"
)
self.root_client = client(endpoint=self.endpoints, username="root", password="root")
async def create_kv_for_test(self):
await self.root_client.put('/foo', '/foo')
value, meta = await self.root_client.get('/foo')
self.assertEqual(value, b'/foo')
@asynctest
async def test_auth_1(self):
await self.create_kv_for_test()
with self.assertRaises(PermissionDenied):
await self.client_client.get('/foo')
await self.root_client.role_grant_permission(name='client', key_range='/foo', permission=PER_RW)
value, meta = await self.client_client.get('/foo')
self.assertEqual(value, b'/foo')
await self.client_client.put('/foo', 'ssss')
@asynctest
async def test_wrong_password(self):
wrong_password_client = client(endpoint=self.endpoints, username="client", password="wrong_password")
with self.assertRaises(AuthError) as exc:
await wrong_password_client.get("/foo")
assert repr(exc.exception) == "`{}`: reason: `{}`".format(exc.exception.code, exc.exception.details)
@asynctest
async def test_wrong_token(self):
await self.create_kv_for_test()
await self.root_client.role_grant_permission(name='client', key_range='/foo', permission=PER_RW)
new_client = client(endpoint=self.endpoints, username="client", password="client")
value, meta = await self.client_client.get('/foo')
self.assertEqual(value, b'/foo')
# Put invalid token
new_client._metadata = (("token", "invalid_token"),)
with self.assertRaises(Unauthenticated) as exc:
await new_client.get("/foo")
async def cleanUp(self):
await self.unauthenticated_client.delete(range_all())
@asynctest
async def tearDown(self):
await switch_auth_off(self.root_client, self.unauthenticated_client)
await self.cleanUp()
class PasswordAuthWithSslTest(unittest.TestCase):
@asynctest
async def setUp(self):
self.endpoints = "127.0.0.1:2377"
self.unauthenticated_client = ssl_client(
endpoint=self.endpoints,
ca_file="test/cfssl/ca.pem",
)
await self.cleanUp()
await switch_auth_on(self.unauthenticated_client)
self.root_client = ssl_client(endpoint=self.endpoints, ca_file="test/cfssl/ca.pem",
username="root", password="root")
self.client_client = ssl_client(endpoint=self.endpoints, ca_file="test/cfssl/ca.pem",
username="client", password="client")
async def create_kv_for_test(self):
await self.root_client.put('/foo', '/foo')
value, meta = await self.root_client.get('/foo')
self.assertEqual(value, b'/foo')
@asynctest
async def test_auth_1(self):
await self.create_kv_for_test()
with self.assertRaises(PermissionDenied):
await self.client_client.get('/foo')
await self.root_client.role_grant_permission(name='client', key_range='/foo', permission=PER_RW)
value, meta = await self.client_client.get('/foo')
self.assertEqual(value, b'/foo')
await self.client_client.put('/foo', 'ssss')
@asynctest
async def test_wrong_password(self):
wrong_password_client = ssl_client(
endpoint=self.endpoints, ca_file="test/cfssl/ca.pem",
username="client", password="wrong_password"
)
with self.assertRaises(AuthError) as exc:
await wrong_password_client.get("/foo")
assert repr(exc.exception) == "`{}`: reason: `{}`".format(exc.exception.code, exc.exception.details)
@asynctest
async def test_wrong_token(self):
await self.create_kv_for_test()
await self.root_client.role_grant_permission(name='client', key_range='/foo', permission=PER_RW)
new_client = ssl_client(
endpoint=self.endpoints, ca_file="test/cfssl/ca.pem",
username="root", password="root"
)
value, meta = await new_client.get('/foo')
self.assertEqual(value, b'/foo')
# Put invalid token
new_client._metadata = (("token", "invalid_token"),)
with self.assertRaises(Unauthenticated) as exc:
await new_client.get("/foo")
async def cleanUp(self):
await self.unauthenticated_client.delete(range_all())
@asynctest
async def tearDown(self):
await switch_auth_off(self.root_client, self.unauthenticated_client)
await self.cleanUp()

41
test/test_cluster.py Normal file
View File

@@ -0,0 +1,41 @@
import unittest
import asyncio
import functools
from aioetcd3.client import client
from aioetcd3.help import range_all
def asynctest(f):
@functools.wraps(f)
def _f(self):
asyncio.get_event_loop().run_until_complete(f(self))
return _f
class ClusterTest(unittest.TestCase):
def setUp(self):
endpoints = "127.0.0.1:2379"
self.client = client(endpoint=endpoints)
@asynctest
async def test_member(self):
members = await self.client.member_list()
self.assertTrue(members)
m = members[0]
# urls = [u for u in m.clientURLs]
# urls = [u.rpartition("//")[2] for u in urls]
healthy, unhealthy = await self.client.member_healthy([m.clientURLs])
self.assertTrue(healthy)
self.assertFalse(unhealthy)
healthy, unhealthy = await self.client.member_healthy()
self.assertTrue(healthy)
self.assertFalse(unhealthy)
@asynctest
async def tearDown(self):
await self.client.close()

163
test/test_kv.py Normal file
View File

@@ -0,0 +1,163 @@
import unittest
import asyncio
import functools
from aioetcd3.client import client
from aioetcd3.kv import KV
from aioetcd3.help import range_all, range_prefix, range_greater, range_greater_equal
from aioetcd3 import transaction
def asynctest(f):
@functools.wraps(f)
def _f(self):
return asyncio.get_event_loop().run_until_complete(f(self))
return _f
class KVTest(unittest.TestCase):
@asynctest
async def setUp(self):
endpoints = "127.0.0.1:2379"
self.client = client(endpoint=endpoints)
endpoints = "127.0.0.1:2379"
self.client.update_server_list(endpoint=endpoints)
await self.cleanUp()
async def cleanUp(self):
await self.client.delete(key_range=range_all())
@asynctest
async def tearDown(self):
await self.cleanUp()
await self.client.close()
@asynctest
async def test_put_get(self):
for i in range(0, 10):
key = '/test' + str(i)
value, meta = await self.client.put(key, str(i))
self.assertIsNone(value)
self.assertIsNone(meta)
value, meta = await self.client.put('/test9', "10", prev_kv=True)
self.assertEqual(value, b'9')
self.assertIsNotNone(meta)
value, meta = await self.client.put('/test9', "9", prev_kv=True, ignore_value=True)
self.assertEqual(value, b'10')
self.assertIsNotNone(meta)
value, meta = await self.client.put('/test9', "9", prev_kv=True)
self.assertEqual(value, b'10')
self.assertIsNotNone(meta)
count = await self.client.count(key_range=range_all())
self.assertEqual(count, 10)
value, meta = await self.client.get("/test9")
self.assertEqual(value, b'9')
self.assertIsNotNone(meta)
keys_list = await self.client.range_keys(key_range=range_all())
self.assertEqual(len(keys_list), 10)
value_list = await self.client.range(key_range=range_all())
self.assertEqual(len(value_list), 10)
value = [v[1].decode('utf-8') for v in value_list]
value.sort()
real_value = [str(i) for i in range(0, 10)]
self.assertEqual(value, real_value)
value_list = await self.client.range(key_range=range_all(), limit=5)
self.assertEqual(len(value_list), 5)
value_list = await self.client.range(key_range=range_prefix('/'))
self.assertEqual(len(value_list), 10)
value_list = await self.client.range(key_range=range_prefix('/'), limit=11)
self.assertEqual(len(value_list), 10)
value_list = await self.client.range(key_range=range_greater_equal('/test8'))
self.assertEqual(len(value_list), 2)
self.assertEqual(value_list[0][1], b'8')
self.assertEqual(value_list[1][1], b'9')
value_list = await self.client.range(key_range=range_greater('/testa'))
self.assertEqual(len(value_list), 0)
await self.client.delete(key_range='/test9')
value, meta = await self.client.get("/test9")
self.assertIsNone(value)
self.assertIsNone(meta)
value_list = await self.client.pop(key_range='/test8')
self.assertEqual(len(value_list), 1)
self.assertEqual(value_list[0][0], b'/test8')
self.assertEqual(value_list[0][1], b'8')
value_list = await self.client.delete(key_range=range_prefix('/'), prev_kv=True)
self.assertEqual(len(value_list), 8)
@asynctest
async def test_transaction(self):
await self.client.put('/trans1', 'trans1')
await self.client.put('/trans2', 'trans2')
is_success, response = await self.client.txn(compare=[
transaction.Value('/trans1') == b'trans1',
transaction.Value('/trans2') == b'trans2'
], success=[
KV.get.txn('/trans1'),
KV.range.txn('/trans2')
], fail=[
KV.delete.txn('/trans1')
])
self.assertEqual(is_success, True)
self.assertEqual(len(response), 2)
self.assertEqual(response[0][0], b'trans1')
self.assertEqual(response[1][0][:2], (b'/trans2', b'trans2'))
is_success, response = await self.client.txn(compare=[
transaction.Value('/trans1') == b'trans1',
transaction.Value('/trans2') == b'trans2'
], success=[
KV.delete.txn('/trans1'),
KV.put.txn('/trans2', 'trans2', prev_kv=True),
KV.put.txn('/trans3', 'trans3', prev_kv=True)
], fail=[
KV.delete.txn('/trans1')
])
self.assertEqual(is_success, True)
self.assertEqual(len(response), 3)
del_response = response[0]
self.assertEqual(del_response, 1)
put_response = response[1]
self.assertEqual(put_response[0], b'trans2')
put_response = response[2]
# there is not pre_kv None
self.assertIsNone(put_response[0])
is_success, response = await self.client.txn(compare=[
transaction.Value('/trans3') != b'trans3',
transaction.Version('/trans3') < 1000,
transaction.Mod('/trans3') > 100,
transaction.Create('/trans3') != 200
], success=[
], fail=[
KV.delete.txn('/trans3', prev_kv=True)
])
self.assertEqual(is_success, False)
self.assertEqual(len(response), 1)
self.assertEqual(len(response[0]), 1)
self.assertEqual(response[0][0][:2], (b'/trans3', b'trans3'))
if __name__ == '__main__':
unittest.main()

133
test/test_lease.py Normal file
View File

@@ -0,0 +1,133 @@
import unittest
import asyncio
import functools
from aioetcd3.client import client
from aioetcd3.help import range_all, range_prefix, PER_RW
from .utils import switch_auth_on, switch_auth_off
def asynctest(f):
@functools.wraps(f)
def _f(self):
asyncio.get_event_loop().run_until_complete(f(self))
return _f
class LeaseTest(unittest.TestCase):
@asynctest
async def setUp(self):
self.endpoints = "127.0.0.1:2379"
self.client = client(endpoint=self.endpoints)
await self.cleanUp()
async def _lease_1(self):
lease = await self.client.grant_lease(ttl=5)
self.assertEqual(lease.ttl, 5)
await asyncio.sleep(1)
lease, keys = await self.client.get_lease_info(lease)
self.assertLessEqual(lease.ttl, 4)
self.assertEqual(len(keys), 0)
lease = await self.client.refresh_lease(lease)
self.assertEqual(lease.ttl, 5)
await self.client.revoke_lease(lease)
lease, keys = await self.client.get_lease_info(lease)
self.assertIsNone(lease)
self.assertEqual(len(keys), 0)
@asynctest
async def test_lease_1(self):
await self._lease_1()
async def _lease_2(self):
lease = await self.client.grant_lease(ttl=5)
self.assertEqual(lease.ttl, 5)
await asyncio.sleep(1)
lease, keys = await lease.info()
self.assertLessEqual(lease.ttl, 4)
self.assertEqual(len(keys), 0)
lease = await lease.refresh()
self.assertEqual(lease.ttl, 5)
await lease.revoke()
lease, keys = await lease.info()
self.assertIsNone(lease)
self.assertEqual(len(keys), 0)
lease = None
async with self.client.grant_lease_scope(ttl=5) as l:
lease = l
await asyncio.sleep(1)
lease, keys = await lease.info()
self.assertIsNone(lease)
self.assertEqual(len(keys), 0)
@asynctest
async def test_lease_2(self):
await self._lease_2()
async def _lease_3(self):
lease = await self.client.grant_lease(ttl=5)
self.assertEqual(lease.ttl, 5)
await self.client.put("/testlease", "testlease", lease=lease)
await asyncio.sleep(6)
lease, keys = await lease.info()
self.assertIsNone(lease, None)
self.assertEqual(len(keys), 0)
value, meta = await self.client.get('/testlease')
self.assertIsNone(value)
self.assertIsNone(meta)
@asynctest
async def test_lease_3(self):
await self._lease_3()
async def _run_test_with_auth(self, test):
default_client = self.client
await switch_auth_on(default_client)
root_client = client(endpoint=self.endpoints, username="root", password="root")
await root_client.role_grant_permission(name='client', key_range=range_prefix('/testlease'), permission=PER_RW)
self.client = client(endpoint=self.endpoints, username="client", password="client")
try:
await test()
finally:
await switch_auth_off(
root_client,
default_client
)
await root_client.close()
await self.client.close()
self.client = default_client
@asynctest
async def test_lease_1_with_auth(self):
await self._run_test_with_auth(self._lease_1)
@asynctest
async def test_lease_2_with_auth(self):
await self._run_test_with_auth(self._lease_2)
@asynctest
async def test_lease_3_with_auth(self):
await self._run_test_with_auth(self._lease_3)
@asynctest
async def tearDown(self):
await self.cleanUp()
await self.client.close()
async def cleanUp(self):
await self.client.delete(range_all())

410
test/test_watch.py Normal file
View File

@@ -0,0 +1,410 @@
import unittest
import functools
import asyncio
from grpc import RpcError
from aioetcd3.client import client
from aioetcd3.help import range_all, range_prefix, PER_RW
from aioetcd3.watch import EVENT_TYPE_CREATE,EVENT_TYPE_DELETE,EVENT_TYPE_MODIFY,\
CompactRevisonException, WatchException
from .utils import switch_auth_off, switch_auth_on
def asynctest(f):
@functools.wraps(f)
def _f(self):
return asyncio.get_event_loop().run_until_complete(f(self))
return _f
class WatchTest(unittest.TestCase):
@asynctest
async def setUp(self):
self.endpoints = "127.0.0.1:2379"
self.client = client(endpoint=self.endpoints)
await self.cleanUp()
async def common_watch1(self):
f1 = asyncio.get_event_loop().create_future()
async def watch_1():
i = 0
async with self.client.watch_scope('/foo') as response:
f1.set_result(None)
async for event in response:
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
elif i == 2:
self.assertEqual(event.type, EVENT_TYPE_MODIFY)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo1')
elif i == 3:
self.assertEqual(event.type, EVENT_TYPE_DELETE)
self.assertEqual(event.key, b'/foo')
# delete event has no value
# self.assertEqual(event.value, b'foo1')
break
f2 = asyncio.get_event_loop().create_future()
async def watch_2():
i = 0
async for event in self.client.watch('/foo', prev_kv=True, create_event=True):
if event is None:
f2.set_result(None)
continue
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
elif i == 2:
self.assertEqual(event.type, EVENT_TYPE_MODIFY)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo1')
self.assertEqual(event.pre_value, b'foo')
elif i == 3:
self.assertEqual(event.type, EVENT_TYPE_DELETE)
self.assertEqual(event.key, b'/foo')
# self.assertEqual(event.value, b'foo1')
break
f3 = asyncio.get_event_loop().create_future()
async def watch_3():
i = 0
async for event in self.client.watch('/foo', prev_kv=True, noput=True, create_event=True):
if event is None:
f3.set_result(None)
continue
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_DELETE)
self.assertEqual(event.key, b'/foo')
# self.assertEqual(event.value, b'foo1')
break
f4 = asyncio.get_event_loop().create_future()
async def watch_4():
i = 0
async for event in self.client.watch('/foo', prev_kv=True, nodelete=True, create_event=True):
if event is None:
f4.set_result(None)
continue
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
elif i == 2:
self.assertEqual(event.type, EVENT_TYPE_MODIFY)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo1')
self.assertEqual(event.pre_value, b'foo')
break
w1 = asyncio.ensure_future(watch_1())
w2 = asyncio.ensure_future(watch_2())
w3 = asyncio.ensure_future(watch_3())
w4 = asyncio.ensure_future(watch_4())
await asyncio.wait_for(asyncio.wait([f1, f2, f3, f4]), 2)
await self.client.put('/foo', 'foo')
await self.client.put('/foo', 'foo1')
await self.client.delete('/foo')
done, pending = await asyncio.wait([w1, w2, w3, w4], timeout=20)
for t in done:
t.result()
@asynctest
async def test_watch_1(self):
await self.common_watch1()
async def watch_reconnect(self):
f1 = asyncio.get_event_loop().create_future()
f2 = asyncio.get_event_loop().create_future()
async def watch_1():
i = 0
async with self.client.watch_scope('/foo') as response:
f1.set_result(None)
async for event in response:
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
f2.set_result(None)
elif i == 2:
self.assertEqual(event.type, EVENT_TYPE_MODIFY)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo1')
elif i == 3:
self.assertEqual(event.type, EVENT_TYPE_DELETE)
self.assertEqual(event.key, b'/foo')
# delete event has no value
# self.assertEqual(event.value, b'foo1')
break
t1 = asyncio.ensure_future(watch_1())
await f1
await self.client.put('/foo', 'foo')
await f2
self.client.update_server_list(self.endpoints)
await self.client.put('/foo', 'foo1')
await self.client.delete('/foo')
await t1
@asynctest
async def test_watch_reconnect(self):
await self.watch_reconnect()
async def watch_create_cancel(self):
async def watch_1():
async with self.client.watch_scope('/foo') as _:
pass
async def watch_2():
async with self.client.watch_scope('/foo') as _:
await asyncio.sleep(5)
for _ in range(0, 5):
watches = [asyncio.ensure_future(watch_1() if i % 2 else watch_2()) for i in range(0, 200)]
await asyncio.sleep(1)
for w in watches[::3]:
w.cancel()
self.client.update_server_list(self.endpoints)
await asyncio.sleep(0.01)
for w in watches[1::3]:
w.cancel()
await asyncio.sleep(0.3)
for w in watches[2::3]:
w.cancel()
await asyncio.wait_for(asyncio.wait(watches), 3)
results = await asyncio.gather(*watches, return_exceptions=True)
print("Finished:", len([r for r in results if r is None]), "Cancelled:", len([r for r in results if r is not None]))
self.assertIsNotNone(self.client._watch_task_running)
await asyncio.sleep(3)
self.assertIsNone(self.client._watch_task_running)
@asynctest
async def test_watch_create_cancel(self):
await self.watch_create_cancel()
async def batch_events(self):
f1 = asyncio.get_event_loop().create_future()
f2 = asyncio.get_event_loop().create_future()
def _check_event(e, criterias):
if criterias[0]:
self.assertEqual(e.type, criterias[0])
if criterias[1]:
self.assertEqual(e.key, criterias[1])
if criterias[2]:
self.assertEqual(e.value, criterias[2])
async def watch_1():
asserts = [(EVENT_TYPE_CREATE, b'/foo/1', b'1'),
(EVENT_TYPE_CREATE, b'/foo/2', b'2'),
(EVENT_TYPE_MODIFY, b'/foo/1', b'2'),
(EVENT_TYPE_MODIFY, b'/foo/2', b'3'),
(EVENT_TYPE_DELETE, b'/foo/1', None),
(EVENT_TYPE_DELETE, b'/foo/2', None)]
async with self.client.watch_scope(range_prefix('/foo/')) as response:
f1.set_result(None)
async for e in response:
_check_event(e, asserts.pop(0))
if not asserts:
break
async def watch_2():
asserts = [((EVENT_TYPE_CREATE, b'/foo/1', b'1'),
(EVENT_TYPE_CREATE, b'/foo/2', b'2'),),
((EVENT_TYPE_MODIFY, b'/foo/1', b'2'),),
((EVENT_TYPE_MODIFY, b'/foo/2', b'3'),),
((EVENT_TYPE_DELETE, b'/foo/1', None),
(EVENT_TYPE_DELETE, b'/foo/2', None))]
async with self.client.watch_scope(range_prefix('/foo/'), batch_events=True) \
as response:
f2.set_result(None)
async for es in response:
batch = asserts.pop(0)
self.assertEqual(len(es), len(batch))
for e, a in zip(es, batch):
_check_event(e, a)
if not asserts:
break
t1 = asyncio.ensure_future(watch_1())
t2 = asyncio.ensure_future(watch_2())
await asyncio.wait_for(asyncio.wait([f1, f2]), 2)
self.assertTrue((await self.client.txn([], [self.client.put.txn('/foo/1', '1'),
self.client.put.txn('/foo/2', '2')], []))[0])
await self.client.put('/foo/1', '2')
await self.client.put('/foo/2', '3')
self.assertTrue((await self.client.txn([], [self.client.delete.txn('/foo/1'),
self.client.delete.txn('/foo/2')], []))[0])
await asyncio.gather(t1, t2)
@asynctest
async def test_batch_events(self):
await self.batch_events()
async def compact_revision(self):
await self.client.put('/foo', '1')
first_revision = self.client.last_response_info.revision
await self.client.put('/foo', '2')
await self.client.put('/foo', '3')
await self.client.put('/foo', '4')
await self.client.put('/foo', '5')
compact_revision = self.client.last_response_info.revision
await self.client.compact(compact_revision, True)
async def watch_1():
async with self.client.watch_scope('/foo', start_revision=first_revision) as response:
with self.assertRaises(CompactRevisonException) as cm:
async for e in response:
raise ValueError("Not raised")
self.assertEqual(cm.exception.revision, compact_revision)
async def watch_2():
async with self.client.watch_scope('/foo', ignore_compact=True, start_revision=first_revision) as responses:
async for e in responses:
self.assertEqual(e.type, EVENT_TYPE_MODIFY)
self.assertEqual(e.key, b'/foo')
self.assertEqual(e.value, b'5')
self.assertEqual(e.revision, compact_revision)
break
await watch_1()
await watch_2()
@asynctest
async def test_compact_revision(self):
await self.compact_revision()
async def watch_exception(self):
f1 = asyncio.get_event_loop().create_future()
f2 = asyncio.get_event_loop().create_future()
async def watch_1():
i = 0
async with self.client.watch_scope('/foo') as response:
f1.set_result(None)
with self.assertRaises(WatchException):
async for event in response:
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
f2.set_result(None)
elif i == 2:
raise ValueError("Not raised")
f3 = asyncio.get_event_loop().create_future()
f4 = asyncio.get_event_loop().create_future()
async def watch_2():
i = 0
async with self.client.watch_scope('/foo', always_reconnect=True) as response:
f3.set_result(None)
async for event in response:
i = i + 1
if i == 1:
self.assertEqual(event.type, EVENT_TYPE_CREATE)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo')
f4.set_result(None)
elif i == 2:
self.assertEqual(event.type, EVENT_TYPE_MODIFY)
self.assertEqual(event.key, b'/foo')
self.assertEqual(event.value, b'foo1')
elif i == 3:
self.assertEqual(event.type, EVENT_TYPE_DELETE)
self.assertEqual(event.key, b'/foo')
# delete event has no value
# self.assertEqual(event.value, b'foo1')
break
t1 = asyncio.ensure_future(watch_1())
t2 = asyncio.ensure_future(watch_2())
await f1
await f3
await self.client.put('/foo', 'foo')
await f2
await f4
fake_endpoints = 'ipv4:///127.0.0.1:49999'
self.client.update_server_list(fake_endpoints)
await asyncio.sleep(2)
self.client.update_server_list(self.endpoints)
await self.client.put('/foo', 'foo1')
await self.client.delete('/foo')
await t1
await t2
@asynctest
async def test_watch_exception(self):
await self.watch_exception()
async def _run_test_with_auth(self, test):
default_client = self.client
await switch_auth_on(default_client)
root_client = client(endpoint=self.endpoints, username="root", password="root")
await root_client.role_grant_permission(name='client', key_range=range_prefix('/foo'), permission=PER_RW)
self.client = client(endpoint=self.endpoints, username="client", password="client")
try:
await test()
finally:
await switch_auth_off(
root_client,
default_client
)
await root_client.close()
await self.client.close()
self.client = default_client
@asynctest
async def test_watch1_with_auth(self):
await self._run_test_with_auth(self.common_watch1)
@asynctest
async def test_watch_reconnect_with_auth(self):
await self._run_test_with_auth(self.watch_reconnect)
@asynctest
async def test_watch_create_cancel_with_auth(self):
await self._run_test_with_auth(self.watch_create_cancel)
@asynctest
async def test_batch_events_with_auth(self):
await self._run_test_with_auth(self.batch_events)
@asynctest
async def test_compact_revision_with_auth(self):
await self._run_test_with_auth(self.compact_revision)
@asynctest
async def test_watch_exception_with_auth(self):
await self._run_test_with_auth(self.watch_exception)
@asynctest
async def tearDown(self):
await self.cleanUp()
await self.client.close()
async def cleanUp(self):
await self.client.delete(range_all())
if __name__ == '__main__':
unittest.main()

17
test/utils.py Normal file
View File

@@ -0,0 +1,17 @@
async def switch_auth_on(client):
await client.user_add(username="root", password="root")
await client.role_add(name="root")
await client.user_grant_role(username="root", role="root")
await client.user_add(username="client", password="client")
await client.role_add(name="client")
await client.user_grant_role(username="client", role="client")
await client.auth_enable()
async def switch_auth_off(root_client, unautheticated_client):
await root_client.auth_disable()
await unautheticated_client.user_delete("client")
await unautheticated_client.user_delete("root")
await unautheticated_client.role_delete("client")
await unautheticated_client.role_delete("root")