Skip to main content

ri/device/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Device Control Module
19//! 
20//! This module provides comprehensive smart device control functionality for Ri, including device
21//! discovery, control, and resource scheduling. It enables efficient management of devices and
22//! their resources across distributed environments.
23//! 
24//! ## Key Components
25//! 
26//! - **RiDeviceControlModule**: Main device control module implementing service module traits
27//! - **RiDevice**: Device representation with type, status, and capabilities
28//! - **RiDeviceType**: Enum defining supported device types
29//! - **RiDeviceStatus**: Enum defining device statuses
30//! - **RiDeviceCapabilities**: Device capabilities structure
31//! - **RiDeviceController**: Device controller for managing devices
32//! - **RiDeviceScheduler**: Device scheduler for resource allocation
33//! - **RiResourcePool**: Resource pool for managing device resources
34//! - **RiResourcePoolManager**: Manager for multiple resource pools
35//! - **RiResourcePoolStatistics**: Statistics for resource pool monitoring
36//! - **RiDeviceControlConfig**: Configuration for device control behavior
37//! - **RiDiscoveryResult**: Result structure for device discovery
38//! - **RiResourceRequest**: Request structure for resource allocation
39//! - **RiResourceAllocation**: Result structure for resource allocation
40//! - **RiResourcePoolStatus**: Status structure for resource pools
41//! 
42//! ## Design Principles
43//! 
44//! 1. **Device Abstraction**: Unified interface for different device types
45//! 2. **Auto Discovery**: Automatic device discovery in the network/environment
46//! 3. **Resource Scheduling**: Intelligent resource allocation and scheduling
47//! 4. **Configurable**: Highly configurable device control behavior
48//! 5. **Async Support**: Full async/await compatibility
49//! 6. **Resource Pooling**: Efficient management of device resources through pooling
50//! 7. **Service Module Integration**: Implements service module traits for seamless integration
51//! 8. **Thread-safe**: Uses Arc and RwLock for safe concurrent access
52//! 9. **Non-critical**: Device control failures should not break the entire application
53//! 10. **Monitoring**: Comprehensive statistics for device and resource monitoring
54//! 11. **Scalable**: Designed to handle large numbers of devices and concurrent tasks
55//! 
56//! ## Usage
57//! 
58//! ```rust
59//! use ri::prelude::*;
60//! use ri::device::{RiDeviceControlConfig, RiResourceRequest, RiDeviceType, RiDeviceCapabilities};
61//! 
62//! async fn example() -> RiResult<()> {
63//!     // Create device control configuration
64//!     let device_config = RiDeviceControlConfig {
65//!         discovery_enabled: true,
66//!         discovery_interval_secs: 30,
67//!         auto_scheduling_enabled: true,
68//!         max_concurrent_tasks: 100,
69//!         resource_allocation_timeout_secs: 60,
70//!     };
71//!     
72//!     // Create device control module
73//!     let device_module = RiDeviceControlModule::new()
74//!         .with_config(device_config);
75//!     
76//!     // Discover devices
77//!     let discovery_result = device_module.discover_devices().await?;
78//!     println!("Discovered {} devices, total devices: {}", 
79//!              discovery_result.discovered_devices.len(), 
80//!              discovery_result.total_devices);
81//!     
82//!     // Get device status
83//!     let devices = device_module.get_device_status().await?;
84//!     println!("Current devices: {:?}", devices);
85//!     
86//!     // Create resource request
87//!     let resource_request = RiResourceRequest {
88//!         request_id: "request-123".to_string(),
89//!         device_type: RiDeviceType::Compute,
90//!         required_capabilities: RiDeviceCapabilities {
91//!             cpu_cores: Some(4),
92//!             memory_gb: Some(8.0),
93//!             storage_gb: Some(100.0),
94//!             gpu_enabled: Some(true),
95//!             network_speed_mbps: Some(1000.0),
96//!             extra: Default::default(),
97//!         },
98//!         priority: 5,
99//!         timeout_secs: 30,
100//!     };
101//!     
102//!     // Allocate resource
103//!     if let Some(allocation) = device_module.allocate_resource(resource_request).await? {
104//!         println!("Allocated device: {} (ID: {})", 
105//!                  allocation.device_name, 
106//!                  allocation.device_id);
107//!         
108//!         // Release resource after use
109//!         device_module.release_resource(&allocation.allocation_id).await?;
110//!     }
111//!     
112//!     Ok(())
113//! }
114//! ```
115
116mod core;
117mod controller;
118pub mod scheduler;
119pub mod pool;
120pub mod discovery_scheduler;
121pub mod discovery;
122
123use std::sync::Arc;
124
125use serde::{Serialize, Deserialize};
126use tokio::sync::RwLock;
127use std::collections::HashMap as FxHashMap;
128
129use crate::observability::{RiMetricsRegistry, RiMetric, RiMetricConfig, RiMetricType};
130
131#[cfg(feature = "pyo3")]
132use pyo3::prelude::*;
133
134
135pub use core::{RiDevice, RiDeviceType, RiDeviceStatus, RiDeviceCapabilities, RiDeviceControlConfig, RiDeviceConfig, RiNetworkDeviceInfo, RiDeviceHealthMetrics};
136pub use controller::RiDeviceController;
137pub use pool::{RiResourcePool, RiResourcePoolManager, RiConnectionPoolStatistics, RiResourcePoolConfig};
138pub use scheduler::RiDeviceScheduler;
139pub use discovery_scheduler::{RiDeviceDiscoveryEngine, RiResourceScheduler};
140
141// Re-export discovery module types
142pub use discovery::{
143    RiDeviceDiscovery,
144    DiscoveryConfig,
145    DiscoveryStats,
146    DiscoveryStrategy,
147    HardwareCategory,
148    PlatformInfo,
149    PlatformType,
150    Architecture,
151    PlatformCompatibility,
152    ProviderRegistry,
153    RiHardwareProvider,
154    PluginRegistry,
155    RiHardwareDiscoveryPlugin,
156    PluginMetadata,
157    PluginStatus,
158    PluginError,
159    AsyncDiscovery,
160};
161
162use crate::core::{RiResult, RiServiceContext};
163
164
165/// Main device control module for Ri.
166/// 
167/// This module provides comprehensive smart device control functionality, including device discovery,
168/// control, and resource scheduling. It manages devices and their resources across distributed environments.
169#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
170pub struct RiDeviceControlModule {
171    /// Device controller for managing devices
172    controller: Arc<RwLock<RiDeviceController>>,
173    /// Device scheduler for resource allocation
174    scheduler: Arc<RwLock<RiDeviceScheduler>>,
175    /// Discovery engine for device discovery
176    discovery_engine: Arc<RwLock<RiResourceScheduler>>,
177    /// Map of resource pool names to resource pool instances
178    resource_pools: FxHashMap<String, Arc<RiResourcePool>>,
179    /// Shared resource pool manager backing the scheduler
180    ///
181    /// Kept at module level so discovered devices can be registered into
182    /// the same pools the scheduler selects from.
183    pool_manager: Arc<RwLock<RiResourcePoolManager>>,
184    /// Device control configuration
185    config: RiDeviceControlConfig,
186}
187
188/// Scheduling configuration for device control module
189///
190/// This configuration contains scheduling and resource management settings
191/// for device control operations.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
194pub struct RiDeviceSchedulingConfig {
195    /// Whether device discovery is enabled
196    pub discovery_enabled: bool,
197    /// Interval between device discovery scans in seconds
198    pub discovery_interval_secs: u64,
199    /// Whether automatic resource scheduling is enabled
200    pub auto_scheduling_enabled: bool,
201    /// Maximum number of concurrent tasks
202    pub max_concurrent_tasks: usize,
203    /// Timeout for resource allocation in seconds
204    pub resource_allocation_timeout_secs: u64,
205}
206
207impl Default for RiDeviceSchedulingConfig {
208    fn default() -> Self {
209        Self {
210            discovery_enabled: true,
211            discovery_interval_secs: 30,
212            auto_scheduling_enabled: true,
213            max_concurrent_tasks: 100,
214            resource_allocation_timeout_secs: 60,
215        }
216    }
217}
218
219#[cfg(feature = "pyo3")]
220#[pymethods]
221impl RiDeviceSchedulingConfig {
222    #[new]
223    fn py_new() -> Self {
224        Self::default()
225    }
226    
227    #[staticmethod]
228    fn default_config() -> Self {
229        Self::default()
230    }
231}
232
233/// Result structure for device discovery operations.
234/// 
235/// This struct contains information about the results of a device discovery scan, including
236/// discovered, updated, and removed devices.
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
239pub struct RiDiscoveryResult {
240    /// Newly discovered devices
241    pub discovered_devices: Vec<RiDevice>,
242    /// Devices with updated information
243    pub updated_devices: Vec<RiDevice>,
244    /// IDs of removed devices
245    pub removed_devices: Vec<String>, // device IDs
246    /// Total number of devices after discovery
247    pub total_devices: usize,
248}
249
250#[cfg(feature = "pyo3")]
251#[pymethods]
252impl RiDiscoveryResult {
253    #[new]
254    fn py_new() -> Self {
255        Self {
256            discovered_devices: Vec::new(),
257            updated_devices: Vec::new(),
258            removed_devices: Vec::new(),
259            total_devices: 0,
260        }
261    }
262    
263    #[staticmethod]
264    fn default_result() -> Self {
265        Self::default()
266    }
267    
268    fn discovered_devices_impl(&self) -> Vec<RiDevice> {
269        self.discovered_devices.clone()
270    }
271    
272    fn updated_devices_impl(&self) -> Vec<RiDevice> {
273        self.updated_devices.clone()
274    }
275    
276    fn removed_devices_impl(&self) -> Vec<String> {
277        self.removed_devices.clone()
278    }
279    
280    fn total_devices_impl(&self) -> usize {
281        self.total_devices
282    }
283    
284    fn __str__(&self) -> String {
285        format!("RiDiscoveryResult(discovered: {}, updated: {}, removed: {}, total: {})", 
286                self.discovered_devices.len(), self.updated_devices.len(), 
287                self.removed_devices.len(), self.total_devices)
288    }
289}
290
291impl Default for RiDiscoveryResult {
292    fn default() -> Self {
293        Self {
294            discovered_devices: Vec::new(),
295            updated_devices: Vec::new(),
296            removed_devices: Vec::new(),
297            total_devices: 0,
298        }
299    }
300}
301
302/// Request structure for resource allocation.
303/// 
304/// This struct defines the requirements for resource allocation, including device type, capabilities,
305/// priority, timeout, and advanced scheduling preferences such as SLA, resource weights,
306/// and affinity rules. New fields are optional to preserve backward compatibility.
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
309pub struct RiResourceRequest {
310    /// Unique request ID
311    pub request_id: String,
312    /// Required device type
313    pub device_type: RiDeviceType,
314    /// Required device capabilities
315    pub required_capabilities: RiDeviceCapabilities,
316    /// Request priority (1-10, higher is more important)
317    pub priority: u8, // 1-10, higher is more important
318    /// Request timeout in seconds
319    pub timeout_secs: u64,
320    /// Optional SLA class for this request (e.g. Critical / High / Medium / Low)
321    pub sla_class: Option<RiRequestSlaClass>,
322    /// Optional multi-dimensional resource weights to influence scheduling decisions
323    pub resource_weights: Option<RiResourceWeights>,
324    /// Optional affinity rules describing preferred/required device labels
325    pub affinity: Option<RiAffinityRules>,
326    /// Optional anti-affinity rules describing labels or devices to avoid
327    pub anti_affinity: Option<RiAffinityRules>,
328}
329
330#[cfg(feature = "pyo3")]
331#[pymethods]
332impl RiResourceRequest {
333    #[new]
334    #[pyo3(signature = (request_id, device_type, required_capabilities, priority=5, timeout_secs=60))]
335    fn py_new(request_id: String, device_type: RiDeviceType, required_capabilities: RiDeviceCapabilities, priority: u8, timeout_secs: u64) -> Self {
336        Self {
337            request_id,
338            device_type,
339            required_capabilities,
340            priority,
341            timeout_secs,
342            sla_class: None,
343            resource_weights: None,
344            affinity: None,
345            anti_affinity: None,
346        }
347    }
348    
349    #[pyo3(name = "request_id")]
350    fn request_id_impl(&self) -> String {
351        self.request_id.clone()
352    }
353    
354    #[pyo3(name = "device_type")]
355    fn device_type_impl(&self) -> RiDeviceType {
356        self.device_type
357    }
358    
359    #[pyo3(name = "required_capabilities")]
360    fn required_capabilities_impl(&self) -> RiDeviceCapabilities {
361        self.required_capabilities.clone()
362    }
363    
364    #[pyo3(name = "priority")]
365    fn priority_impl(&self) -> u8 {
366        self.priority
367    }
368    
369    #[pyo3(name = "timeout_secs")]
370    fn timeout_secs_impl(&self) -> u64 {
371        self.timeout_secs
372    }
373    
374    #[pyo3(name = "sla_class")]
375    fn sla_class_impl(&self) -> Option<RiRequestSlaClass> {
376        self.sla_class
377    }
378    
379    #[pyo3(name = "resource_weights")]
380    fn resource_weights_impl(&self) -> Option<RiResourceWeights> {
381        self.resource_weights.clone()
382    }
383    
384    #[pyo3(name = "affinity")]
385    fn affinity_impl(&self) -> Option<RiAffinityRules> {
386        self.affinity.clone()
387    }
388    
389    #[pyo3(name = "anti_affinity")]
390    fn anti_affinity_impl(&self) -> Option<RiAffinityRules> {
391        self.anti_affinity.clone()
392    }
393    
394    #[pyo3(name = "set_priority")]
395    fn set_priority_impl(&mut self, priority: u8) {
396        self.priority = priority;
397    }
398    
399    #[pyo3(name = "set_timeout_secs")]
400    fn set_timeout_secs_impl(&mut self, timeout_secs: u64) {
401        self.timeout_secs = timeout_secs;
402    }
403    
404    #[pyo3(name = "set_sla_class")]
405    fn set_sla_class_impl(&mut self, sla_class: Option<RiRequestSlaClass>) {
406        self.sla_class = sla_class;
407    }
408    
409    #[pyo3(name = "set_resource_weights")]
410    fn set_resource_weights_impl(&mut self, resource_weights: Option<RiResourceWeights>) {
411        self.resource_weights = resource_weights;
412    }
413    
414    #[pyo3(name = "set_affinity")]
415    fn set_affinity_impl(&mut self, affinity: Option<RiAffinityRules>) {
416        self.affinity = affinity;
417    }
418    
419    #[pyo3(name = "set_anti_affinity")]
420    fn set_anti_affinity_impl(&mut self, anti_affinity: Option<RiAffinityRules>) {
421        self.anti_affinity = anti_affinity;
422    }
423    
424    fn __str__(&self) -> String {
425        format!("RiResourceRequest(id: {}, type: {:?}, priority: {}, timeout: {}s)", 
426                self.request_id, self.device_type, self.priority, self.timeout_secs)
427    }
428}
429
430/// SLA class for a resource request.
431/// 
432/// This enum describes the service level expectations for a request. Schedulers can
433/// use this information to trade off between latency, availability, and resource usage.
434#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
435#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
436pub enum RiRequestSlaClass {
437    /// Mission critical requests that should be served with the highest priority
438    Critical,
439    /// High priority requests
440    High,
441    /// Normal priority requests
442    Medium,
443    /// Low priority / best-effort requests
444    Low,
445}
446
447#[cfg(feature = "pyo3")]
448#[pymethods]
449impl RiRequestSlaClass {
450    fn __str__(&self) -> String {
451        match self {
452            RiRequestSlaClass::Critical => "Critical".to_string(),
453            RiRequestSlaClass::High => "High".to_string(),
454            RiRequestSlaClass::Medium => "Medium".to_string(),
455            RiRequestSlaClass::Low => "Low".to_string(),
456        }
457    }
458}
459
460/// Multi-dimensional resource weights for scheduling.
461/// 
462/// This struct allows callers to express how important different resource dimensions are
463/// (compute, memory, storage, bandwidth) for a specific request. Schedulers can use these
464/// weights when computing fitness scores.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
467pub struct RiResourceWeights {
468    /// Weight for compute resources (e.g. CPU cores, GPU units)
469    pub compute_weight: f64,
470    /// Weight for memory capacity
471    pub memory_weight: f64,
472    /// Weight for storage capacity
473    pub storage_weight: f64,
474    /// Weight for network bandwidth
475    pub bandwidth_weight: f64,
476}
477
478#[cfg(feature = "pyo3")]
479#[pymethods]
480impl RiResourceWeights {
481    #[new]
482    #[pyo3(signature = (compute_weight=1.0, memory_weight=1.0, storage_weight=1.0, bandwidth_weight=1.0))]
483    fn py_new(compute_weight: f64, memory_weight: f64, storage_weight: f64, bandwidth_weight: f64) -> Self {
484        Self {
485            compute_weight,
486            memory_weight,
487            storage_weight,
488            bandwidth_weight,
489        }
490    }
491    
492    #[staticmethod]
493    fn default_weights() -> Self {
494        Self::default()
495    }
496    
497    #[pyo3(name = "compute_weight")]
498    fn compute_weight_impl(&self) -> f64 { self.compute_weight }
499    #[pyo3(name = "memory_weight")]
500    fn memory_weight_impl(&self) -> f64 { self.memory_weight }
501    #[pyo3(name = "storage_weight")]
502    fn storage_weight_impl(&self) -> f64 { self.storage_weight }
503    #[pyo3(name = "bandwidth_weight")]
504    fn bandwidth_weight_impl(&self) -> f64 { self.bandwidth_weight }
505    
506    #[pyo3(name = "set_compute_weight")]
507    fn set_compute_weight_impl(&mut self, weight: f64) { self.compute_weight = weight; }
508    #[pyo3(name = "set_memory_weight")]
509    fn set_memory_weight_impl(&mut self, weight: f64) { self.memory_weight = weight; }
510    #[pyo3(name = "set_storage_weight")]
511    fn set_storage_weight_impl(&mut self, weight: f64) { self.storage_weight = weight; }
512    #[pyo3(name = "set_bandwidth_weight")]
513    fn set_bandwidth_weight_impl(&mut self, weight: f64) { self.bandwidth_weight = weight; }
514    
515    fn __str__(&self) -> String {
516        format!("RiResourceWeights(compute: {}, memory: {}, storage: {}, bandwidth: {})", 
517                self.compute_weight, self.memory_weight, self.storage_weight, self.bandwidth_weight)
518    }
519}
520
521impl Default for RiResourceWeights {
522    fn default() -> Self {
523        Self {
524            compute_weight: 1.0,
525            memory_weight: 1.0,
526            storage_weight: 1.0,
527            bandwidth_weight: 1.0,
528        }
529    }
530}
531
532/// Affinity and anti-affinity rules for device selection.
533/// 
534/// Rules are expressed as label key/value pairs. Implementations can interpret labels
535/// using device metadata such as location, zone, rack, tenant, etc.
536#[derive(Debug, Clone, Serialize, Deserialize)]
537#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
538pub struct RiAffinityRules {
539    /// Labels that must be present with matching values
540    pub required_labels: FxHashMap<String, String>,
541    /// Labels that are preferred (but not strictly required)
542    pub preferred_labels: FxHashMap<String, String>,
543    /// Labels that must not be present with matching values
544    pub forbidden_labels: FxHashMap<String, String>,
545}
546
547#[cfg(feature = "pyo3")]
548#[pymethods]
549impl RiAffinityRules {
550    #[new]
551    fn py_new() -> Self {
552        Self {
553            required_labels: FxHashMap::default(),
554            preferred_labels: FxHashMap::default(),
555            forbidden_labels: FxHashMap::default(),
556        }
557    }
558    
559    #[staticmethod]
560    fn default_rules() -> Self {
561        Self::default()
562    }
563    
564    #[pyo3(name = "required_labels")]
565    fn required_labels_impl(&self) -> FxHashMap<String, String> {
566        self.required_labels.clone()
567    }
568    
569    #[pyo3(name = "preferred_labels")]
570    fn preferred_labels_impl(&self) -> FxHashMap<String, String> {
571        self.preferred_labels.clone()
572    }
573    
574    #[pyo3(name = "forbidden_labels")]
575    fn forbidden_labels_impl(&self) -> FxHashMap<String, String> {
576        self.forbidden_labels.clone()
577    }
578    
579    fn __str__(&self) -> String {
580        format!("RiAffinityRules(required: {}, preferred: {}, forbidden: {})", 
581                self.required_labels.len(), self.preferred_labels.len(), self.forbidden_labels.len())
582    }
583}
584
585impl Default for RiAffinityRules {
586    fn default() -> Self {
587        Self {
588            required_labels: FxHashMap::default(),
589            preferred_labels: FxHashMap::default(),
590            forbidden_labels: FxHashMap::default(),
591        }
592    }
593}
594
595/// Result structure for resource allocation.
596/// 
597/// This struct contains information about a successful resource allocation, including the allocated
598/// device, allocation time, and expiration time.
599#[derive(Debug, Clone, Serialize, Deserialize)]
600#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
601pub struct RiResourceAllocation {
602    /// Unique allocation ID
603    pub allocation_id: String,
604    /// ID of the allocated device
605    pub device_id: String,
606    /// Name of the allocated device
607    pub device_name: String,
608    /// Time when the resource was allocated
609    pub allocated_at: chrono::DateTime<chrono::Utc>,
610    /// Time when the allocation expires
611    pub expires_at: chrono::DateTime<chrono::Utc>,
612    /// Original resource request
613    pub request: RiResourceRequest,
614}
615
616#[cfg(feature = "pyo3")]
617#[pymethods]
618impl RiResourceAllocation {
619    #[new]
620    fn py_new(allocation_id: String, device_id: String, device_name: String, request: RiResourceRequest) -> Self {
621        let now = chrono::Utc::now();
622        let expires_at = now + chrono::TimeDelta::seconds(request.timeout_secs as i64);
623        
624        Self {
625            allocation_id,
626            device_id,
627            device_name,
628            allocated_at: now,
629            expires_at,
630            request,
631        }
632    }
633    
634    #[pyo3(name = "allocation_id")]
635    fn allocation_id_impl(&self) -> String {
636        self.allocation_id.clone()
637    }
638    
639    #[pyo3(name = "device_id")]
640    fn device_id_impl(&self) -> String {
641        self.device_id.clone()
642    }
643    
644    #[pyo3(name = "device_name")]
645    fn device_name_impl(&self) -> String {
646        self.device_name.clone()
647    }
648    
649    #[pyo3(name = "allocated_at")]
650    fn allocated_at_impl(&self) -> String {
651        self.allocated_at.to_rfc3339()
652    }
653    
654    #[pyo3(name = "expires_at")]
655    fn expires_at_impl(&self) -> String {
656        self.expires_at.to_rfc3339()
657    }
658    
659    #[pyo3(name = "request")]
660    fn request_impl(&self) -> RiResourceRequest {
661        self.request.clone()
662    }
663    
664    #[pyo3(name = "is_expired")]
665    fn is_expired_impl(&self) -> bool {
666        chrono::Utc::now() > self.expires_at
667    }
668    
669    #[pyo3(name = "remaining_time")]
670    fn remaining_time_impl(&self) -> i64 {
671        (self.expires_at - chrono::Utc::now()).num_seconds()
672    }
673    
674    fn __str__(&self) -> String {
675        format!("RiResourceAllocation(id: {}, device: {} ({}), expires: {})", 
676                self.allocation_id, self.device_name, self.device_id, self.expires_at)
677    }
678}
679
680impl Default for RiDeviceControlModule {
681    fn default() -> Self {
682        Self::new()
683    }
684}
685
686impl RiDeviceControlModule {
687    /// Creates a new device control module with default configuration.
688    /// 
689    /// # Returns
690    /// 
691    /// A new `RiDeviceControlModule` instance with default configuration
692    pub fn new() -> Self {
693        let controller = Arc::new(RwLock::new(RiDeviceController::new()));
694        let resource_pool_manager = Arc::new(RwLock::new(RiResourcePoolManager::new()));
695        let scheduler = Arc::new(RwLock::new(RiDeviceScheduler::new(resource_pool_manager.clone())));
696        let discovery_engine = Arc::new(RwLock::new(RiResourceScheduler::new()));
697        
698        Self {
699            controller,
700            scheduler,
701            discovery_engine,
702            resource_pools: FxHashMap::default(),
703            pool_manager: resource_pool_manager,
704            config: crate::device::core::RiDeviceControlConfig::default(),
705        }
706    }
707    
708    /// Configures the device control module with custom settings.
709    /// 
710    /// # Parameters
711    /// 
712    /// - `config`: The custom configuration to apply
713    /// 
714    /// # Returns
715    /// 
716    /// The updated `RiDeviceControlModule` instance
717    pub fn with_config(mut self, config: crate::device::core::RiDeviceControlConfig) -> Self {
718        self.config = config;
719        self
720    }
721    
722    /// Discovers devices in the network/environment.
723    /// 
724    /// This method performs a device discovery scan if discovery is enabled, returning information
725    /// about discovered, updated, and removed devices.
726    /// 
727    /// # Returns
728    /// 
729    /// A `RiResult<RiDiscoveryResult>` containing the discovery results
730    pub async fn discover_devices(&self) -> RiResult<RiDiscoveryResult> {
731        if !self.config.enable_cpu_discovery && !self.config.enable_gpu_discovery && 
732           !self.config.enable_memory_discovery && !self.config.enable_storage_discovery && 
733           !self.config.enable_network_discovery {
734            return Ok(RiDiscoveryResult {
735                discovered_devices: vec![],
736                updated_devices: vec![],
737                removed_devices: vec![],
738                total_devices: 0,
739            });
740        }
741        
742        let result = {
743            let mut controller = self.controller.write().await;
744            controller.discover_devices().await
745        };
746        
747        // Sync discovered devices into the scheduler's resource pools so
748        // allocate_resource can actually select them. Pools are rebuilt per
749        // type on every discovery round; devices still in Unknown state are
750        // marked Available before registration (is_available() requires it).
751        // NOTE: the controller lock must be fully released before touching
752        // the pool manager — allocate_resource acquires these locks in the
753        // opposite order and a nested hold here could deadlock.
754        if result.is_ok() {
755            let all_devices = {
756                let controller = self.controller.read().await;
757                controller.get_all_devices()
758            };
759            
760            if !all_devices.is_empty() {
761                let mut by_type: FxHashMap<RiDeviceType, Vec<RiDevice>> = FxHashMap::default();
762                for mut device in all_devices {
763                    if device.status() == crate::device::core::RiDeviceStatus::Unknown {
764                        device.set_status(crate::device::core::RiDeviceStatus::Available);
765                    }
766                    by_type.entry(device.device_type()).or_default().push(device);
767                }
768                
769                let mut manager = self.pool_manager.write().await;
770                for (device_type, devices) in by_type {
771                    let pool_name = format!("{:?}_pool", device_type).to_lowercase();
772                    // Rebuild the pool from scratch each discovery round
773                    manager.remove_pool(&pool_name);
774                    let mut pool = RiResourcePool::new(RiResourcePoolConfig {
775                        name: pool_name,
776                        device_type,
777                        ..Default::default()
778                    });
779                    for device in devices {
780                        pool.add_device(Arc::new(device));
781                    }
782                    manager.register_pool(pool);
783                }
784            }
785        }
786        
787        result
788    }
789    
790    /// Allocates a device resource based on the given request.
791    /// 
792    /// This method finds a suitable device based on the requested device type and capabilities,
793    /// allocates it using the device scheduler, and returns an allocation result if successful.
794    /// 
795    /// # Parameters
796    /// 
797    /// - `request`: The resource allocation request
798    /// 
799    /// # Returns
800    /// 
801    /// A `RiResult<Option<RiResourceAllocation>>` containing the allocation result if successful,
802    /// or None if allocation failed or auto-scheduling is disabled
803    pub async fn allocate_resource(&self, request: RiResourceRequest) -> RiResult<Option<RiResourceAllocation>> {
804        // Check if any device type scheduling is enabled
805        let scheduling_enabled = match request.device_type {
806            RiDeviceType::CPU => self.config.enable_cpu_discovery,
807            RiDeviceType::GPU => self.config.enable_gpu_discovery,
808            RiDeviceType::Memory => self.config.enable_memory_discovery,
809            RiDeviceType::Storage => self.config.enable_storage_discovery,
810            RiDeviceType::Network => self.config.enable_network_discovery,
811            _ => true, // Default to enabled for unknown types
812        };
813        
814        if !scheduling_enabled {
815            return Ok(None);
816        }
817
818        let allocation_request = crate::device::scheduler::RiAllocationRequest {
819            device_type: request.device_type,
820            capabilities: request.required_capabilities,
821            priority: request.priority as u32,
822            timeout_secs: request.timeout_secs,
823            sla_class: request.sla_class,
824            resource_weights: request.resource_weights,
825            affinity: request.affinity,
826            anti_affinity: request.anti_affinity,
827        };
828
829        let scheduler = self.scheduler.write().await;
830        let device = scheduler.select_device(&allocation_request).await;
831
832        if let Some(device) = device {
833            let allocation = RiResourceAllocation {
834                allocation_id: uuid::Uuid::new_v4().to_string(),
835                device_id: device.id().to_string(),
836                device_name: device.name().to_string(),
837                allocated_at: chrono::Utc::now(),
838                expires_at: chrono::Utc::now() + chrono::Duration::seconds(allocation_request.timeout_secs as i64),
839                request: RiResourceRequest {
840                    request_id: request.request_id,
841                    device_type: allocation_request.device_type,
842                    required_capabilities: allocation_request.capabilities,
843                    priority: request.priority,
844                    timeout_secs: allocation_request.timeout_secs,
845                    sla_class: allocation_request.sla_class,
846                    resource_weights: allocation_request.resource_weights,
847                    affinity: allocation_request.affinity,
848                    anti_affinity: allocation_request.anti_affinity,
849                },
850            };
851
852            // Mark device as busy via controller
853            let mut controller = self.controller.write().await;
854            controller.allocate_device(&allocation.device_id, &allocation.allocation_id).await?;
855
856            Ok(Some(allocation))
857        } else {
858            Ok(None)
859        }
860    }
861    
862    /// Releases a previously allocated device resource.
863    /// 
864    /// This method releases a device resource that was allocated with `allocate_resource`.
865    /// 
866    /// # Parameters
867    /// 
868    /// - `allocation_id`: The ID of the allocation to release
869    /// 
870    /// # Returns
871    /// 
872    /// A `RiResult<()>` indicating success or failure
873    pub async fn release_resource(&self, allocation_id: &str) -> RiResult<()> {
874        let mut controller = self.controller.write().await;
875        controller.release_device_by_allocation(allocation_id).await
876    }
877    
878    /// Gets the current status of all devices.
879    /// 
880    /// This method returns a list of all devices currently managed by the device controller.
881    /// 
882    /// # Returns
883    /// 
884    /// A `RiResult<Vec<RiDevice>>` containing all managed devices
885    pub async fn get_device_status(&self) -> RiResult<Vec<RiDevice>> {
886        let controller = self.controller.read().await;
887        Ok(controller.get_all_devices())
888    }
889    
890    /// Gets the status of all resource pools.
891    /// 
892    /// This method returns a map of resource pool names to their current status.
893    /// 
894    /// # Returns
895    /// 
896    /// A `FxHashMap<String, RiResourcePoolStatus>` containing the status of all resource pools
897    pub fn get_resource_pool_status(&self) -> FxHashMap<String, RiResourcePoolStatus> {
898        let mut status = FxHashMap::default();
899        for (pool_name, pool) in &self.resource_pools {
900            status.insert(pool_name.clone(), pool.get_status());
901        }
902        status
903    }
904    
905    /// Creates device management metrics and registers them with the metrics registry.
906    /// 
907    /// This method creates and registers the following metrics:
908    /// - dms_device_total: Total number of devices by type and status
909    /// - dms_device_allocation_attempts_total: Total number of allocation attempts
910    /// - dms_device_allocation_success_total: Total number of successful allocations
911    /// - dms_device_allocation_failure_total: Total number of failed allocations
912    /// - dms_device_discovery_attempts_total: Total number of device discovery attempts
913    /// - dms_device_discovery_success_total: Total number of successful device discoveries
914    /// - dms_device_resource_utilization: Resource utilization by device type
915    /// 
916    /// # Parameters
917    /// 
918    /// - `registry`: The metrics registry to register the metrics with
919    /// 
920    /// # Returns
921    /// 
922    /// A `RiResult<()>` indicating success or failure
923    #[allow(dead_code)]
924    fn create_device_metrics(&self, registry: Arc<RiMetricsRegistry>) -> RiResult<()> {
925        // Device total metric (Gauge)
926        let device_total_config = RiMetricConfig {
927            metric_type: RiMetricType::Gauge,
928            name: "dms_device_total".to_string(),
929            help: "Total number of devices by type and status".to_string(),
930            buckets: vec![],
931            quantiles: vec![],
932            max_age: std::time::Duration::from_secs(300),
933            age_buckets: 5,
934        };
935        let device_total_metric = Arc::new(RiMetric::new(device_total_config));
936        registry.register(device_total_metric)?;
937        
938        // Allocation attempts metric (Counter)
939        let allocation_attempts_config = RiMetricConfig {
940            metric_type: RiMetricType::Counter,
941            name: "dms_device_allocation_attempts_total".to_string(),
942            help: "Total number of device allocation attempts".to_string(),
943            buckets: vec![],
944            quantiles: vec![],
945            max_age: std::time::Duration::from_secs(0),
946            age_buckets: 0,
947        };
948        let allocation_attempts_metric = Arc::new(RiMetric::new(allocation_attempts_config));
949        registry.register(allocation_attempts_metric)?;
950        
951        // Allocation success metric (Counter)
952        let allocation_success_config = RiMetricConfig {
953            metric_type: RiMetricType::Counter,
954            name: "dms_device_allocation_success_total".to_string(),
955            help: "Total number of successful device allocations".to_string(),
956            buckets: vec![],
957            quantiles: vec![],
958            max_age: std::time::Duration::from_secs(0),
959            age_buckets: 0,
960        };
961        let allocation_success_metric = Arc::new(RiMetric::new(allocation_success_config));
962        registry.register(allocation_success_metric)?;
963        
964        // Allocation failure metric (Counter)
965        let allocation_failure_config = RiMetricConfig {
966            metric_type: RiMetricType::Counter,
967            name: "dms_device_allocation_failure_total".to_string(),
968            help: "Total number of failed device allocations".to_string(),
969            buckets: vec![],
970            quantiles: vec![],
971            max_age: std::time::Duration::from_secs(0),
972            age_buckets: 0,
973        };
974        let allocation_failure_metric = Arc::new(RiMetric::new(allocation_failure_config));
975        registry.register(allocation_failure_metric)?;
976        
977        // Discovery attempts metric (Counter)
978        let discovery_attempts_config = RiMetricConfig {
979            metric_type: RiMetricType::Counter,
980            name: "dms_device_discovery_attempts_total".to_string(),
981            help: "Total number of device discovery attempts".to_string(),
982            buckets: vec![],
983            quantiles: vec![],
984            max_age: std::time::Duration::from_secs(0),
985            age_buckets: 0,
986        };
987        let discovery_attempts_metric = Arc::new(RiMetric::new(discovery_attempts_config));
988        registry.register(discovery_attempts_metric)?;
989        
990        // Discovery success metric (Counter)
991        let discovery_success_config = RiMetricConfig {
992            metric_type: RiMetricType::Counter,
993            name: "dms_device_discovery_success_total".to_string(),
994            help: "Total number of successful device discoveries".to_string(),
995            buckets: vec![],
996            quantiles: vec![],
997            max_age: std::time::Duration::from_secs(0),
998            age_buckets: 0,
999        };
1000        let discovery_success_metric = Arc::new(RiMetric::new(discovery_success_config));
1001        registry.register(discovery_success_metric)?;
1002        
1003        // Resource utilization metric (Gauge)
1004        let resource_utilization_config = RiMetricConfig {
1005            metric_type: RiMetricType::Gauge,
1006            name: "dms_device_resource_utilization".to_string(),
1007            help: "Resource utilization by device type".to_string(),
1008            buckets: vec![],
1009            quantiles: vec![],
1010            max_age: std::time::Duration::from_secs(300),
1011            age_buckets: 5,
1012        };
1013        let resource_utilization_metric = Arc::new(RiMetric::new(resource_utilization_config));
1014        registry.register(resource_utilization_metric)?;
1015        
1016        Ok(())
1017    }
1018}
1019
1020#[cfg(feature = "pyo3")]
1021#[pyo3::prelude::pymethods]
1022impl RiDeviceControlModule {
1023    fn get_config(&self) -> String {
1024        format!("{:?}", self.config)
1025    }
1026}
1027
1028/// Status structure for resource pools.
1029/// 
1030/// This struct contains information about the current status of a resource pool, including capacity,
1031/// allocation, and utilization.
1032#[derive(Debug, Clone, Serialize, Deserialize)]
1033#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
1034pub struct RiResourcePoolStatus {
1035    /// Total capacity of the resource pool
1036    pub total_capacity: usize,
1037    /// Available capacity in the resource pool
1038    pub available_capacity: usize,
1039    /// Allocated capacity in the resource pool
1040    pub allocated_capacity: usize,
1041    /// Number of pending resource requests
1042    pub pending_requests: usize,
1043    /// Resource utilization rate (0.0 to 1.0)
1044    pub utilization_rate: f64,
1045}
1046
1047#[cfg(feature = "pyo3")]
1048#[pyo3::prelude::pymethods]
1049impl RiResourcePoolStatus {
1050    #[new]
1051    #[pyo3(signature = (total_capacity=0, available_capacity=0, allocated_capacity=0, pending_requests=0, utilization_rate=0.0))]
1052    fn py_new(total_capacity: usize, available_capacity: usize, allocated_capacity: usize, pending_requests: usize, utilization_rate: f64) -> Self {
1053        Self {
1054            total_capacity,
1055            available_capacity,
1056            allocated_capacity,
1057            pending_requests,
1058            utilization_rate,
1059        }
1060    }
1061    
1062    #[staticmethod]
1063    fn default_status() -> Self {
1064        Self::default()
1065    }
1066    
1067    #[pyo3(name = "total_capacity")]
1068    fn total_capacity_impl(&self) -> usize {
1069        self.total_capacity
1070    }
1071    
1072    #[pyo3(name = "available_capacity")]
1073    fn available_capacity_impl(&self) -> usize {
1074        self.available_capacity
1075    }
1076    
1077    #[pyo3(name = "allocated_capacity")]
1078    fn allocated_capacity_impl(&self) -> usize {
1079        self.allocated_capacity
1080    }
1081    
1082    #[pyo3(name = "pending_requests")]
1083    fn pending_requests_impl(&self) -> usize {
1084        self.pending_requests
1085    }
1086    
1087    #[pyo3(name = "utilization_rate")]
1088    fn utilization_rate_impl(&self) -> f64 {
1089        self.utilization_rate
1090    }
1091    
1092    fn __str__(&self) -> String {
1093        format!("RiResourcePoolStatus(total: {}, available: {}, allocated: {}, utilization: {:.2}%)",
1094                self.total_capacity, self.available_capacity, self.allocated_capacity, self.utilization_rate)
1095    }
1096}
1097
1098impl Default for RiResourcePoolStatus {
1099    fn default() -> Self {
1100        Self {
1101            total_capacity: 0,
1102            available_capacity: 0,
1103            allocated_capacity: 0,
1104            pending_requests: 0,
1105            utilization_rate: 0.0,
1106        }
1107    }
1108}
1109
1110#[async_trait::async_trait]
1111impl crate::core::RiModule for RiDeviceControlModule {
1112    /// Returns the name of the device control module.
1113    /// 
1114    /// # Returns
1115    /// 
1116    /// The module name as a string
1117    fn name(&self) -> &str {
1118        "Ri.DeviceControl"
1119    }
1120    
1121    /// Indicates whether the device control module is critical.
1122    /// 
1123    /// The device control module is non-critical, meaning that if it fails to initialize or operate,
1124    /// it should not break the entire application. This allows the core functionality to continue
1125    /// even if device control features are unavailable.
1126    /// 
1127    /// # Returns
1128    /// 
1129    /// `false` since device control is non-critical
1130    fn is_critical(&self) -> bool {
1131        false // Non-critical, should not break the app if device control fails
1132    }
1133    
1134    /// Initializes the device control module asynchronously.
1135    /// 
1136    /// This method performs the following steps:
1137    /// 1. Loads configuration from the service context
1138    /// 2. Initializes real device discovery based on system hardware
1139    /// 3. Sets up resource scheduling and management
1140    async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
1141        let binding = ctx.config();
1142        let cfg = binding.config();
1143        let device_config = parse_device_config(cfg.get("device"));
1144        
1145        let mut controller = self.controller.write().await;
1146        controller.discover_system_devices(&device_config).await?;
1147        drop(controller);
1148        
1149        let discovery_engine = RiResourceScheduler::new();
1150        
1151        let mut discovery_guard = self.discovery_engine.write().await;
1152        *discovery_guard = discovery_engine;
1153        
1154        if let Some(metrics_registry) = ctx.metrics_registry() {
1155            let mut controller = self.controller.write().await;
1156            controller.initialize_metrics(&metrics_registry)?;
1157            drop(controller);
1158        }
1159        
1160        let logger = ctx.logger();
1161        logger.info("Ri.DeviceControl", "Device control module initialized with real hardware discovery")?;
1162        Ok(())
1163    }
1164}
1165
1166impl crate::core::ServiceModule for RiDeviceControlModule {
1167    fn name(&self) -> &str {
1168        "Ri.DeviceControl"
1169    }
1170
1171    fn is_critical(&self) -> bool {
1172        false
1173    }
1174
1175    fn priority(&self) -> i32 {
1176        25
1177    }
1178
1179    fn dependencies(&self) -> Vec<&str> {
1180        vec![]
1181    }
1182
1183    fn init(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1184        Ok(())
1185    }
1186
1187    fn start(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1188        Ok(())
1189    }
1190
1191    fn shutdown(&mut self, _ctx: &mut crate::core::RiServiceContext) -> crate::core::RiResult<()> {
1192        Ok(())
1193    }
1194}
1195
1196fn parse_device_config(config_str: Option<&String>) -> crate::device::core::RiDeviceControlConfig {
1197    match config_str {
1198        Some(config) => {
1199            let trimmed = config.trim();
1200            if trimmed.starts_with('{') {
1201                if let Ok(result) = serde_json::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1202                    return result;
1203                }
1204            }
1205            if trimmed.starts_with("---") || trimmed.contains("discovery_enabled:") {
1206                if let Ok(result) = serde_yaml::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1207                    return result;
1208                }
1209            }
1210            if trimmed.contains('[') || trimmed.contains("discovery_enabled") {
1211                if let Ok(result) = toml::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed) {
1212                    return result;
1213                }
1214            }
1215            serde_json::from_str::<crate::device::core::RiDeviceControlConfig>(trimmed)
1216                .unwrap_or_default()
1217        }
1218        None => crate::device::core::RiDeviceControlConfig::default(),
1219    }
1220}