Skip to main content

dakera_client/
admin.rs

1//! Admin operations for the Dakera client.
2//!
3//! Provides methods for cluster management, cache, configuration, quotas,
4//! slow queries, backups, and TTL management.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::Result;
11use crate::DakeraClient;
12
13// ============================================================================
14// Cluster Types
15// ============================================================================
16
17/// Cluster status response
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ClusterStatus {
20    pub cluster_id: String,
21    pub state: String,
22    pub node_count: u32,
23    pub total_vectors: u64,
24    pub namespace_count: u64,
25    pub version: String,
26    pub timestamp: u64,
27}
28
29/// Node information
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct NodeInfo {
32    pub node_id: String,
33    pub address: String,
34    pub role: String,
35    pub status: String,
36    pub version: String,
37    pub uptime_seconds: u64,
38    pub vector_count: u64,
39    pub memory_bytes: u64,
40    #[serde(default)]
41    pub cpu_percent: f32,
42    #[serde(default)]
43    pub memory_percent: f32,
44    pub last_heartbeat: u64,
45}
46
47/// Node list response
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct NodeListResponse {
50    pub nodes: Vec<NodeInfo>,
51    pub total: u32,
52}
53
54// ============================================================================
55// Namespace Admin Types
56// ============================================================================
57
58/// Index statistics
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct IndexStats {
61    pub index_type: String,
62    pub is_built: bool,
63    pub size_bytes: u64,
64    pub indexed_vectors: u64,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub last_rebuild: Option<u64>,
67}
68
69/// Detailed namespace statistics
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct NamespaceAdminInfo {
72    pub name: String,
73    pub vector_count: u64,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub dimension: Option<usize>,
76    pub index_type: String,
77    pub storage_bytes: u64,
78    pub document_count: u64,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub created_at: Option<u64>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub updated_at: Option<u64>,
83    pub index_stats: IndexStats,
84}
85
86/// Namespace list response
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct NamespaceListResponse {
89    pub namespaces: Vec<NamespaceAdminInfo>,
90    pub total: u64,
91    pub total_vectors: u64,
92}
93
94/// Optimize namespace request
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct OptimizeRequest {
97    #[serde(default)]
98    pub force: bool,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub target_index_type: Option<String>,
101}
102
103/// Optimize namespace response
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct OptimizeResponse {
106    pub success: bool,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub job_id: Option<String>,
109    pub message: String,
110}
111
112// ============================================================================
113// Index Admin Types
114// ============================================================================
115
116/// Index statistics for all namespaces
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct IndexStatsResponse {
119    pub namespaces: HashMap<String, IndexStats>,
120    pub total_indexed_vectors: u64,
121    pub total_size_bytes: u64,
122}
123
124/// Rebuild index request
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct RebuildIndexRequest {
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub namespace: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub index_type: Option<String>,
131    #[serde(default)]
132    pub force: bool,
133}
134
135/// Rebuild index response
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct RebuildIndexResponse {
138    pub success: bool,
139    pub job_id: String,
140    pub message: String,
141}
142
143// ============================================================================
144// Cache Admin Types
145// ============================================================================
146
147/// Cache statistics
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct CacheStats {
150    pub enabled: bool,
151    pub cache_type: String,
152    pub entries: u64,
153    pub size_bytes: u64,
154    pub hits: u64,
155    pub misses: u64,
156    pub hit_rate: f64,
157    pub evictions: u64,
158}
159
160/// Clear cache request
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ClearCacheRequest {
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub namespace: Option<String>,
165}
166
167/// Clear cache response
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct ClearCacheResponse {
170    pub success: bool,
171    pub entries_cleared: u64,
172    pub message: String,
173}
174
175// ============================================================================
176// Configuration Types
177// ============================================================================
178
179/// Runtime configuration
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct RuntimeConfig {
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub max_vectors_per_namespace: Option<u64>,
184    pub default_index_type: String,
185    pub cache_enabled: bool,
186    pub cache_max_size_bytes: u64,
187    pub rate_limit_enabled: bool,
188    pub rate_limit_rps: u32,
189    pub query_timeout_ms: u64,
190}
191
192/// Update configuration response
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct UpdateConfigResponse {
195    pub success: bool,
196    pub config: RuntimeConfig,
197    pub message: String,
198    #[serde(default, skip_serializing_if = "Vec::is_empty")]
199    pub warnings: Vec<String>,
200}
201
202// ============================================================================
203// Quota Types
204// ============================================================================
205
206/// Quota configuration
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct QuotaConfig {
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub max_vectors: Option<u64>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub max_storage_bytes: Option<u64>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub max_queries_per_minute: Option<u64>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub max_writes_per_minute: Option<u64>,
217}
218
219/// Quota usage
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct QuotaUsage {
222    #[serde(default)]
223    pub current_vectors: u64,
224    #[serde(default)]
225    pub current_storage_bytes: u64,
226    #[serde(default)]
227    pub queries_this_minute: u64,
228    #[serde(default)]
229    pub writes_this_minute: u64,
230}
231
232/// Quota status for a namespace
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct QuotaStatus {
235    pub namespace: String,
236    pub config: QuotaConfig,
237    pub usage: QuotaUsage,
238}
239
240/// Quota list response
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct QuotaListResponse {
243    pub quotas: Vec<QuotaStatus>,
244    pub total: u64,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub default_config: Option<QuotaConfig>,
247}
248
249// ============================================================================
250// Slow Query Types
251// ============================================================================
252
253/// Slow query entry
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct SlowQueryEntry {
256    pub id: String,
257    pub timestamp: u64,
258    pub namespace: String,
259    pub query_type: String,
260    pub duration_ms: f64,
261    #[serde(default)]
262    pub parameters: Option<serde_json::Value>,
263    #[serde(default)]
264    pub results_count: u64,
265    #[serde(default)]
266    pub vectors_scanned: u64,
267}
268
269/// Slow query list response
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct SlowQueryListResponse {
272    pub queries: Vec<SlowQueryEntry>,
273    pub total: u64,
274    pub threshold_ms: f64,
275}
276
277// ============================================================================
278// Backup Types
279// ============================================================================
280
281/// Backup information
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct BackupInfo {
284    pub backup_id: String,
285    pub name: String,
286    pub backup_type: String,
287    pub status: String,
288    pub namespaces: Vec<String>,
289    pub vector_count: u64,
290    pub size_bytes: u64,
291    pub created_at: u64,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub completed_at: Option<u64>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub duration_seconds: Option<u64>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub storage_path: Option<String>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub error: Option<String>,
300    pub encrypted: bool,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub compression: Option<String>,
303}
304
305/// List backups response
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct BackupListResponse {
308    pub backups: Vec<BackupInfo>,
309    pub total: u64,
310}
311
312/// Create backup request
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct CreateBackupRequest {
315    pub name: String,
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub backup_type: Option<String>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub namespaces: Option<Vec<String>>,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub encrypt: Option<bool>,
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub compression: Option<String>,
324}
325
326/// Create backup response
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct CreateBackupResponse {
329    pub backup: BackupInfo,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub estimated_completion: Option<u64>,
332}
333
334/// Restore backup request
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct RestoreBackupRequest {
337    pub backup_id: String,
338    #[serde(skip_serializing_if = "Option::is_none")]
339    pub target_namespaces: Option<Vec<String>>,
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub overwrite: Option<bool>,
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub point_in_time: Option<u64>,
344}
345
346/// Restore backup response
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct RestoreBackupResponse {
349    pub restore_id: String,
350    pub status: String,
351    pub backup_id: String,
352    pub namespaces: Vec<String>,
353    pub started_at: u64,
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub estimated_completion: Option<u64>,
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub progress_percent: Option<u8>,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub vectors_restored: Option<u64>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub completed_at: Option<u64>,
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub duration_seconds: Option<u64>,
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub error: Option<String>,
366}
367
368// ============================================================================
369// TTL Types
370// ============================================================================
371
372/// TTL cleanup request
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct TtlCleanupRequest {
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub namespace: Option<String>,
377}
378
379/// TTL cleanup response
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct TtlCleanupResponse {
382    pub success: bool,
383    pub vectors_removed: u64,
384    pub namespaces_cleaned: Vec<String>,
385    pub message: String,
386}
387
388/// TTL statistics for a namespace
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct TtlStats {
391    pub namespace: String,
392    pub vectors_with_ttl: u64,
393    pub expiring_within_hour: u64,
394    pub expiring_within_day: u64,
395    pub expired_pending_cleanup: u64,
396}
397
398/// TTL statistics response
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct TtlStatsResponse {
401    pub namespaces: Vec<TtlStats>,
402    pub total_with_ttl: u64,
403    pub total_expired: u64,
404}
405
406// ============================================================================
407// Admin Client Methods
408// ============================================================================
409
410impl DakeraClient {
411    // ====================================================================
412    // Cluster Management
413    // ====================================================================
414
415    /// Get cluster status overview
416    pub async fn cluster_status(&self) -> Result<ClusterStatus> {
417        let url = format!("{}/admin/cluster/status", self.base_url);
418        let response = self.client.get(&url).send().await?;
419        self.handle_response(response).await
420    }
421
422    /// List cluster nodes
423    pub async fn cluster_nodes(&self) -> Result<NodeListResponse> {
424        let url = format!("{}/admin/cluster/nodes", self.base_url);
425        let response = self.client.get(&url).send().await?;
426        self.handle_response(response).await
427    }
428
429    // ====================================================================
430    // Namespace Administration
431    // ====================================================================
432
433    /// List all namespaces with detailed admin statistics
434    pub async fn list_namespaces_admin(&self) -> Result<NamespaceListResponse> {
435        let url = format!("{}/admin/namespaces", self.base_url);
436        let response = self.client.get(&url).send().await?;
437        self.handle_response(response).await
438    }
439
440    /// Delete an entire namespace and all its data
441    pub async fn delete_namespace_admin(&self, namespace: &str) -> Result<serde_json::Value> {
442        let url = format!("{}/admin/namespaces/{}", self.base_url, namespace);
443        let response = self.client.delete(&url).send().await?;
444        self.handle_response(response).await
445    }
446
447    /// Optimize a namespace
448    pub async fn optimize_namespace(
449        &self,
450        namespace: &str,
451        request: OptimizeRequest,
452    ) -> Result<OptimizeResponse> {
453        let url = format!("{}/admin/namespaces/{}/optimize", self.base_url, namespace);
454        let response = self.client.post(&url).json(&request).send().await?;
455        self.handle_response(response).await
456    }
457
458    // ====================================================================
459    // Index Management
460    // ====================================================================
461
462    /// Get index statistics for all namespaces
463    pub async fn index_stats(&self) -> Result<IndexStatsResponse> {
464        let url = format!("{}/admin/indexes/stats", self.base_url);
465        let response = self.client.get(&url).send().await?;
466        self.handle_response(response).await
467    }
468
469    /// Rebuild indexes
470    pub async fn rebuild_indexes(
471        &self,
472        request: RebuildIndexRequest,
473    ) -> Result<RebuildIndexResponse> {
474        let url = format!("{}/admin/indexes/rebuild", self.base_url);
475        let response = self.client.post(&url).json(&request).send().await?;
476        self.handle_response(response).await
477    }
478
479    // ====================================================================
480    // Cache Management
481    // ====================================================================
482
483    /// Get cache statistics
484    pub async fn cache_stats(&self) -> Result<CacheStats> {
485        let url = format!("{}/admin/cache/stats", self.base_url);
486        let response = self.client.get(&url).send().await?;
487        self.handle_response(response).await
488    }
489
490    /// Clear cache, optionally for a specific namespace
491    pub async fn cache_clear(&self, namespace: Option<&str>) -> Result<ClearCacheResponse> {
492        let url = format!("{}/admin/cache/clear", self.base_url);
493        let request = ClearCacheRequest {
494            namespace: namespace.map(|s| s.to_string()),
495        };
496        let response = self.client.post(&url).json(&request).send().await?;
497        self.handle_response(response).await
498    }
499
500    // ====================================================================
501    // Configuration
502    // ====================================================================
503
504    /// Get runtime configuration
505    pub async fn get_config(&self) -> Result<RuntimeConfig> {
506        let url = format!("{}/admin/config", self.base_url);
507        let response = self.client.get(&url).send().await?;
508        self.handle_response(response).await
509    }
510
511    /// Update runtime configuration
512    pub async fn update_config(
513        &self,
514        updates: HashMap<String, serde_json::Value>,
515    ) -> Result<UpdateConfigResponse> {
516        let url = format!("{}/admin/config", self.base_url);
517        let response = self.client.put(&url).json(&updates).send().await?;
518        self.handle_response(response).await
519    }
520
521    // ====================================================================
522    // Quotas
523    // ====================================================================
524
525    /// List all namespace quotas
526    pub async fn get_quotas(&self) -> Result<QuotaListResponse> {
527        let url = format!("{}/admin/quotas", self.base_url);
528        let response = self.client.get(&url).send().await?;
529        self.handle_response(response).await
530    }
531
532    /// Get quota for a specific namespace
533    pub async fn get_quota(&self, namespace: &str) -> Result<QuotaStatus> {
534        let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
535        let response = self.client.get(&url).send().await?;
536        self.handle_response(response).await
537    }
538
539    /// Set quota for a specific namespace
540    pub async fn set_quota(
541        &self,
542        namespace: &str,
543        config: QuotaConfig,
544    ) -> Result<serde_json::Value> {
545        let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
546        let request = serde_json::json!({ "config": config });
547        let response = self.client.put(&url).json(&request).send().await?;
548        self.handle_response(response).await
549    }
550
551    /// Delete quota for a specific namespace
552    pub async fn delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
553        let url = format!("{}/admin/quotas/{}", self.base_url, namespace);
554        let response = self.client.delete(&url).send().await?;
555        self.handle_response(response).await
556    }
557
558    /// Update quotas (alias for set_quota on default)
559    pub async fn update_quotas(&self, config: Option<QuotaConfig>) -> Result<serde_json::Value> {
560        let url = format!("{}/admin/quotas/default", self.base_url);
561        let request = serde_json::json!({ "config": config });
562        let response = self.client.put(&url).json(&request).send().await?;
563        self.handle_response(response).await
564    }
565
566    // ====================================================================
567    // Slow Queries
568    // ====================================================================
569
570    /// List recent slow queries
571    pub async fn slow_queries(
572        &self,
573        limit: Option<usize>,
574        namespace: Option<&str>,
575        query_type: Option<&str>,
576    ) -> Result<SlowQueryListResponse> {
577        let mut url = format!("{}/admin/slow-queries", self.base_url);
578        let mut params = Vec::new();
579        if let Some(l) = limit {
580            params.push(format!("limit={}", l));
581        }
582        if let Some(ns) = namespace {
583            params.push(format!("namespace={}", ns));
584        }
585        if let Some(qt) = query_type {
586            params.push(format!("query_type={}", qt));
587        }
588        if !params.is_empty() {
589            url.push('?');
590            url.push_str(&params.join("&"));
591        }
592        let response = self.client.get(&url).send().await?;
593        self.handle_response(response).await
594    }
595
596    /// Get slow query summary and patterns
597    pub async fn slow_query_summary(&self) -> Result<serde_json::Value> {
598        let url = format!("{}/admin/slow-queries/summary", self.base_url);
599        let response = self.client.get(&url).send().await?;
600        self.handle_response(response).await
601    }
602
603    /// Clear slow query log
604    pub async fn clear_slow_queries(&self) -> Result<serde_json::Value> {
605        let url = format!("{}/admin/slow-queries", self.base_url);
606        let response = self.client.delete(&url).send().await?;
607        self.handle_response(response).await
608    }
609
610    // ====================================================================
611    // Backups
612    // ====================================================================
613
614    /// Create a new backup
615    pub async fn create_backup(
616        &self,
617        request: CreateBackupRequest,
618    ) -> Result<CreateBackupResponse> {
619        let url = format!("{}/admin/backups", self.base_url);
620        let response = self.client.post(&url).json(&request).send().await?;
621        self.handle_response(response).await
622    }
623
624    /// List all backups
625    pub async fn list_backups(&self) -> Result<BackupListResponse> {
626        let url = format!("{}/admin/backups", self.base_url);
627        let response = self.client.get(&url).send().await?;
628        self.handle_response(response).await
629    }
630
631    /// Get backup details by ID
632    pub async fn get_backup(&self, backup_id: &str) -> Result<BackupInfo> {
633        let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
634        let response = self.client.get(&url).send().await?;
635        self.handle_response(response).await
636    }
637
638    /// Restore from a backup
639    pub async fn restore_backup(
640        &self,
641        request: RestoreBackupRequest,
642    ) -> Result<RestoreBackupResponse> {
643        let url = format!("{}/admin/backups/restore", self.base_url);
644        let response = self.client.post(&url).json(&request).send().await?;
645        self.handle_response(response).await
646    }
647
648    /// Delete a backup
649    pub async fn delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
650        let url = format!("{}/admin/backups/{}", self.base_url, backup_id);
651        let response = self.client.delete(&url).send().await?;
652        self.handle_response(response).await
653    }
654
655    // ====================================================================
656    // TTL Management
657    // ====================================================================
658
659    /// Run TTL cleanup on expired vectors
660    pub async fn ttl_cleanup(&self, namespace: Option<&str>) -> Result<TtlCleanupResponse> {
661        let url = format!("{}/admin/ttl/cleanup", self.base_url);
662        let request = TtlCleanupRequest {
663            namespace: namespace.map(|s| s.to_string()),
664        };
665        let response = self.client.post(&url).json(&request).send().await?;
666        self.handle_response(response).await
667    }
668
669    /// Get TTL statistics
670    pub async fn ttl_stats(&self) -> Result<TtlStatsResponse> {
671        let url = format!("{}/admin/ttl/stats", self.base_url);
672        let response = self.client.get(&url).send().await?;
673        self.handle_response(response).await
674    }
675}