"docs/components/vscode:/vscode.git/clone" did not exist on "ece08dc926679909dd7a0d980f073cdee47414c2"
Unverified Commit f30d76ce authored by Graham King's avatar Graham King Committed by GitHub
Browse files

fix: KeyValueStore etcd impl should use internal etcd client (#4212)


Signed-off-by: default avatarGraham King <grahamk@nvidia.com>
parent 23660bc5
...@@ -182,43 +182,18 @@ impl EtcdBucket { ...@@ -182,43 +182,18 @@ impl EtcdBucket {
let k = make_key(&self.bucket_name, key); let k = make_key(&self.bucket_name, key);
tracing::trace!("etcd create: {k}"); tracing::trace!("etcd create: {k}");
// Use atomic transaction to check and create in one operation match self
let put_options = PutOptions::new().with_lease(self.client.lease_id() as i64);
// Build transaction that creates key only if it doesn't exist
let txn = Txn::new()
.when(vec![Compare::version(k.as_str(), CompareOp::Equal, 0)]) // Atomic check
.and_then(vec![TxnOp::put(k.as_str(), value, Some(put_options))]) // Only if check passes
.or_else(vec![
TxnOp::get(k.as_str(), None), // Key exists, get its info
]);
// Execute the transaction
let result = self
.client .client
.etcd_client() .kv_create(k.as_str(), value.into(), None)
.kv_client()
.txn(txn)
.await .await
.map_err(|e| StoreError::EtcdError(e.to_string()))?; .map_err(|e| StoreError::EtcdError(e.to_string()))?
{
if result.succeeded() { None => {
// Key was created successfully // Key was created successfully
return Ok(StoreOutcome::Created(1)); // version of new key is always 1 Ok(StoreOutcome::Created(1)) // version of new key is always 1
} }
Some(revision) => Ok(StoreOutcome::Exists(revision)),
// Key already existed, get its version
if let Some(etcd_client::TxnOpResponse::Get(get_resp)) =
result.op_responses().into_iter().next()
&& let Some(kv) = get_resp.kvs().first()
{
let version = kv.version() as u64;
return Ok(StoreOutcome::Exists(version));
} }
// Shouldn't happen, but handle edge case
Err(StoreError::EtcdError(
"Unexpected transaction response".to_string(),
))
} }
async fn update( async fn update(
......
...@@ -112,7 +112,7 @@ impl Client { ...@@ -112,7 +112,7 @@ impl Client {
/// Get a clone of the underlying [`etcd_client::Client`] instance. /// Get a clone of the underlying [`etcd_client::Client`] instance.
/// This returns a clone since the client is behind an RwLock. /// This returns a clone since the client is behind an RwLock.
pub fn etcd_client(&self) -> etcd_client::Client { fn etcd_client(&self) -> etcd_client::Client {
self.connector.get_client() self.connector.get_client()
} }
...@@ -121,29 +121,49 @@ impl Client { ...@@ -121,29 +121,49 @@ impl Client {
self.primary_lease self.primary_lease
} }
pub async fn kv_create(&self, key: &str, value: Vec<u8>, lease_id: Option<u64>) -> Result<()> { /// Returns Ok(None) if value was created, Ok(Some(revision)) if the value already exists.
pub async fn kv_create(
&self,
key: &str,
value: Vec<u8>,
lease_id: Option<u64>,
) -> Result<Option<u64>> {
let id = lease_id.unwrap_or(self.lease_id()); let id = lease_id.unwrap_or(self.lease_id());
let put_options = PutOptions::new().with_lease(id as i64); let put_options = PutOptions::new().with_lease(id as i64);
// Build the transaction // Build transaction that creates key only if it doesn't exist
let txn = Txn::new() let txn = Txn::new()
.when(vec![Compare::version(key, CompareOp::Equal, 0)]) // Ensure the lock does not exist .when(vec![Compare::version(key, CompareOp::Equal, 0)]) // Ensure the lock does not exist
.and_then(vec![ .and_then(vec![
TxnOp::put(key, value, Some(put_options)), // Create the object TxnOp::put(key, value, Some(put_options)), // Create the object
])
.or_else(vec![
TxnOp::get(key, None), // Key exists, get its info
]); ]);
// Execute the transaction // Execute the transaction
let result = self.connector.get_client().kv_client().txn(txn).await?; let result = self.connector.get_client().kv_client().txn(txn).await?;
// Created
if result.succeeded() { if result.succeeded() {
Ok(()) return Ok(None);
} else { }
// Already exists
if let Some(etcd_client::TxnOpResponse::Get(get_resp)) =
result.op_responses().into_iter().next()
&& let Some(kv) = get_resp.kvs().first()
{
let version = kv.version() as u64;
return Ok(Some(version));
}
// Error
for resp in result.op_responses() { for resp in result.op_responses() {
tracing::warn!(response = ?resp, "kv_create etcd op response"); tracing::warn!(response = ?resp, "kv_create etcd op response");
} }
anyhow::bail!("Unable to create key. Check etcd server status") anyhow::bail!("Unable to create key. Check etcd server status")
} }
}
/// Atomically create a key if it does not exist, or validate the values are identical if the key exists. /// Atomically create a key if it does not exist, or validate the values are identical if the key exists.
pub async fn kv_create_or_validate( pub async fn kv_create_or_validate(
......
...@@ -90,15 +90,11 @@ async fn create_barrier_key<T: Serialize>( ...@@ -90,15 +90,11 @@ async fn create_barrier_key<T: Serialize>(
let serialized_data = let serialized_data =
serde_json::to_vec(&data).map_err(LeaderWorkerBarrierError::SerdeError)?; serde_json::to_vec(&data).map_err(LeaderWorkerBarrierError::SerdeError)?;
// TODO: This can fail for many reasons, the most common of which is that the key already exists. match client.kv_create(key, serialized_data, lease_id).await {
// Currently, the ETCD client returns a very generic error, so we can't distinguish between the them. Ok(None) => Ok(()),
// For now, just assume it's because the key already exists. Ok(Some(_)) => Err(LeaderWorkerBarrierError::IdNotUnique),
client Err(err) => Err(LeaderWorkerBarrierError::EtcdError(err)),
.kv_create(key, serialized_data, lease_id) }
.await
.map_err(|_| LeaderWorkerBarrierError::IdNotUnique)?;
Ok(())
} }
/// Waits for a single key to appear (used for completion/abort signals) /// Waits for a single key to appear (used for completion/abort signals)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment