From 868e8540ba8aef17373618a0b8254d731c89a9af Mon Sep 17 00:00:00 2001 From: Lihi Wishnitzer Date: Tue, 29 Aug 2017 16:50:18 +0300 Subject: [PATCH] Add a "create" method for the client Adds a "create" method to the client, which verifies that the key did not exists in the key-value store, before assigning the new value to the key --- etcd3gw/client.py | 36 +++++++++++++++++++++++++++++++++++ etcd3gw/tests/test_etcd3gw.py | 25 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/etcd3gw/client.py b/etcd3gw/client.py index d403957..4cda9e7 100644 --- a/etcd3gw/client.py +++ b/etcd3gw/client.py @@ -122,6 +122,42 @@ def lock(self, id=str(uuid.uuid4()), ttl=DEFAULT_TIMEOUT): """ return Lock(id, ttl=ttl, client=self) + def create(self, key, value): + """Atomically create the given key only if the key doesn't exist. + + This verifies that the create_revision of a key equales to 0, then + creates the key with the value. + This operation takes place in a transaction. + + :param key: key in etcd to create + :param value: value of the key + :type value: bytes or string + :returns: status of transaction, ``True`` if the create was + successful, ``False`` otherwise + :rtype: bool + """ + base64_key = _encode(key) + base64_value = _encode(value) + txn = { + 'compare': [{ + 'key': base64_key, + 'result': 'EQUAL', + 'target': 'CREATE', + 'create_revision': 0 + }], + 'success': [{ + 'request_put': { + 'key': base64_key, + 'value': base64_value, + } + }], + 'failure': [] + } + result = self.transaction(txn) + if 'succeeded' in result: + return result['succeeded'] + return False + def put(self, key, value, lease=None): """Put puts the given key into the key-value store. diff --git a/etcd3gw/tests/test_etcd3gw.py b/etcd3gw/tests/test_etcd3gw.py index fb5197a..31e7dd9 100644 --- a/etcd3gw/tests/test_etcd3gw.py +++ b/etcd3gw/tests/test_etcd3gw.py @@ -302,3 +302,28 @@ def test_client_locks(self): self.assertTrue(lock.release()) self.assertFalse(lock.release()) self.assertFalse(lock.is_acquired()) + + @unittest.skipUnless( + _is_etcd3_running(), "etcd3 is not available") + def test_create_success(self): + key = '/foo/unique' + str(uuid.uuid4()) + # Verify that key is empty + self.assertEqual([], self.client.get(key)) + + status = self.client.create(key, 'bar') + # Verify that key is 'bar' + self.assertEqual(['bar'], self.client.get(key)) + self.assertTrue(status) + + @unittest.skipUnless( + _is_etcd3_running(), "etcd3 is not available") + def test_create_fail(self): + key = '/foo/' + str(uuid.uuid4()) + # Assign value to the key + self.client.put(key, 'bar') + self.assertEqual(['bar'], self.client.get(key)) + + status = self.client.create(key, 'goo') + # Verify that key is still 'bar' + self.assertEqual(['bar'], self.client.get(key)) + self.assertFalse(status)