|
@@ -51,36 +51,181 @@ impl NetworkListener {
|
|
}))
|
|
}))
|
|
}
|
|
}
|
|
|
|
|
|
- pub async fn listen(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
|
+ pub async fn listen(self: &Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
|
|
self.change_listener.listen().await
|
|
self.change_listener.listen().await
|
|
}
|
|
}
|
|
|
|
|
|
- pub fn change_handler(self: Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
|
+ pub async fn change_handler(self: &Arc<Self>) -> Result<(), Box<dyn std::error::Error>> {
|
|
let this = self.clone();
|
|
let this = self.clone();
|
|
- tokio::spawn(async move {
|
|
|
|
- let mut rx = this.rx_channel.lock().await;
|
|
|
|
- while let Some(change) = rx.recv().await {
|
|
|
|
- if let Ok(m) = NetworkChange::decode(change.as_slice()) {
|
|
|
|
- print!("Received change: {:?}", m);
|
|
|
|
-
|
|
|
|
- let j = serde_json::to_string(&m).unwrap();
|
|
|
|
- let mut buffer = [0; 16384];
|
|
|
|
- let mut test: &mut [u8] = &mut buffer;
|
|
|
|
- let mut size: usize = 0;
|
|
|
|
- while let Ok(bytes) = test.write(j.as_bytes()) {
|
|
|
|
- if bytes == 0 {
|
|
|
|
- break; // No more space to write
|
|
|
|
|
|
+
|
|
|
|
+ let mut rx = this.rx_channel.lock().await;
|
|
|
|
+ while let Some(change) = rx.recv().await {
|
|
|
|
+ if let Ok(m) = NetworkChange::decode(change.as_slice()) {
|
|
|
|
+ let j = serde_json::to_string(&m).unwrap();
|
|
|
|
+ let mut buffer = [0; 16384];
|
|
|
|
+ let mut test: &mut [u8] = &mut buffer;
|
|
|
|
+ let mut size: usize = 0;
|
|
|
|
+ while let Ok(bytes) = test.write(j.as_bytes()) {
|
|
|
|
+ if bytes == 0 {
|
|
|
|
+ break; // No more space to write
|
|
|
|
+ }
|
|
|
|
+ size += bytes;
|
|
|
|
+ }
|
|
|
|
+ let callback = this.callback.lock().await;
|
|
|
|
+ let user_ptr = this.user_ptr.load(Ordering::Relaxed);
|
|
|
|
+
|
|
|
|
+ (callback)(user_ptr, test.as_ptr(), size);
|
|
|
|
+ } else {
|
|
|
|
+ eprintln!("Failed to decode change");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[cfg(test)]
|
|
|
|
+mod tests {
|
|
|
|
+ use super::*;
|
|
|
|
+ use crate::pubsub::change_listener::tests::setup_pubsub_emulator;
|
|
|
|
+ use crate::pubsub::protobuf::pbmessages::Network;
|
|
|
|
+
|
|
|
|
+ use gcloud_googleapis::pubsub::v1::PubsubMessage;
|
|
|
|
+ use gcloud_pubsub::client::{Client, ClientConfig};
|
|
|
|
+ use std::{
|
|
|
|
+ collections::HashMap,
|
|
|
|
+ sync::atomic::{AtomicBool, Ordering},
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ extern "C" fn dummy_callback(user_ptr: *mut c_void, data: *const u8, _size: usize) {
|
|
|
|
+ // Dummy callback for testing
|
|
|
|
+ assert!(!data.is_null(), "data pointer is null");
|
|
|
|
+ assert!(!user_ptr.is_null(), "user_ptr pointer is null");
|
|
|
|
+ let user_ptr = unsafe { &mut *(user_ptr as *mut TestNetworkListenr) };
|
|
|
|
+ user_ptr.callback_called();
|
|
|
|
+ println!("Dummy callback invoked");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ struct TestNetworkListenr {
|
|
|
|
+ dummy_callback_called: bool,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ impl TestNetworkListenr {
|
|
|
|
+ fn new() -> Self {
|
|
|
|
+ Self { dummy_callback_called: false }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn callback_called(&mut self) {
|
|
|
|
+ self.dummy_callback_called = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
|
|
+ async fn test_network_listener() {
|
|
|
|
+ println!("Setting up Pub/Sub emulator for network listener test");
|
|
|
|
+ let (_container, _host) = setup_pubsub_emulator().await.unwrap();
|
|
|
|
+
|
|
|
|
+ let mut tester = TestNetworkListenr::new();
|
|
|
|
+
|
|
|
|
+ let listener = NetworkListener::new(
|
|
|
|
+ "testctl",
|
|
|
|
+ Duration::from_secs(1),
|
|
|
|
+ dummy_callback,
|
|
|
|
+ &mut tester as *mut TestNetworkListenr as *mut c_void,
|
|
|
|
+ )
|
|
|
|
+ .await
|
|
|
|
+ .unwrap();
|
|
|
|
+
|
|
|
|
+ let rt = tokio::runtime::Handle::current();
|
|
|
|
+
|
|
|
|
+ let run = Arc::new(AtomicBool::new(true));
|
|
|
|
+ rt.spawn({
|
|
|
|
+ let run = run.clone();
|
|
|
|
+ let l = listener.clone();
|
|
|
|
+ async move {
|
|
|
|
+ while run.load(Ordering::Relaxed) {
|
|
|
|
+ match l.listen().await {
|
|
|
|
+ Ok(_) => {
|
|
|
|
+ println!("Listener exited successfully");
|
|
|
|
+ }
|
|
|
|
+ Err(e) => {
|
|
|
|
+ println!("Failed to start listener: {}", e);
|
|
|
|
+ assert!(false, "Listener failed to start");
|
|
}
|
|
}
|
|
- size += bytes;
|
|
|
|
}
|
|
}
|
|
- let callback = this.callback.lock().await;
|
|
|
|
- let user_ptr = this.user_ptr.load(Ordering::Relaxed);
|
|
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- (callback)(user_ptr, test.as_ptr(), size);
|
|
|
|
|
|
+ rt.spawn({
|
|
|
|
+ let run = run.clone();
|
|
|
|
+ let l = listener.clone();
|
|
|
|
+ async move {
|
|
|
|
+ while run.load(Ordering::Relaxed) {
|
|
|
|
+ match l.change_handler().await {
|
|
|
|
+ Ok(_) => {
|
|
|
|
+ println!("Change handler started successfully");
|
|
|
|
+ }
|
|
|
|
+ Err(e) => {
|
|
|
|
+ println!("Failed to start change handler: {}", e);
|
|
|
|
+ assert!(false, "Change handler failed to start");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- Ok(())
|
|
|
|
|
|
+ rt.spawn({
|
|
|
|
+ async move {
|
|
|
|
+ let client = Client::new(ClientConfig::default()).await.unwrap();
|
|
|
|
+ let topic = client.topic("controller-network-change-stream");
|
|
|
|
+ if !topic.exists(None).await.unwrap() {
|
|
|
|
+ topic.create(None, None).await.unwrap();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let mut publisher = topic.new_publisher(None);
|
|
|
|
+
|
|
|
|
+ let nc = NetworkChange {
|
|
|
|
+ old: Some(Network {
|
|
|
|
+ network_id: "test_network".to_string(),
|
|
|
|
+ name: Some("Test Network".to_string()),
|
|
|
|
+ ..Default::default()
|
|
|
|
+ }),
|
|
|
|
+ new: Some(Network {
|
|
|
|
+ network_id: "test_network".to_string(),
|
|
|
|
+ name: Some("Test Network Updated".to_string()),
|
|
|
|
+ ..Default::default()
|
|
|
|
+ }),
|
|
|
|
+ ..Default::default()
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ let data = NetworkChange::encode_to_vec(&nc);
|
|
|
|
+ let message = PubsubMessage {
|
|
|
|
+ data: data.into(),
|
|
|
|
+ attributes: HashMap::from([("controller_id".to_string(), "testctl".to_string())]),
|
|
|
|
+ ordering_key: format!("networks-{}", "testctl"),
|
|
|
|
+ ..Default::default()
|
|
|
|
+ };
|
|
|
|
+ let awaiter = publisher.publish(message).await;
|
|
|
|
+
|
|
|
|
+ match awaiter.get().await {
|
|
|
|
+ Ok(_) => println!("Message published successfully"),
|
|
|
|
+ Err(e) => {
|
|
|
|
+ assert!(false, "Failed to publish message: {}", e);
|
|
|
|
+ eprintln!("Failed to publish message: {}", e)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ publisher.shutdown().await;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ let mut counter = 0;
|
|
|
|
+ while !tester.dummy_callback_called && counter < 100 {
|
|
|
|
+ tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
+ counter += 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ run.store(false, Ordering::Relaxed);
|
|
|
|
+ assert!(tester.dummy_callback_called, "Callback was not called");
|
|
}
|
|
}
|
|
}
|
|
}
|