|
5 | 5 | use crate::{ |
6 | 6 | Client, ConfiguredClient, LONG_POLL_TIMEOUT, RequestExt, RetryClient, SharedReplaceableClient, |
7 | 7 | TEMPORAL_NAMESPACE_HEADER_KEY, TemporalServiceClient, |
8 | | - metrics::{namespace_kv, task_queue_kv}, |
| 8 | + metrics::namespace_kv, |
9 | 9 | worker_registry::{ClientWorkerSet, Slot}, |
10 | 10 | }; |
11 | 11 | use dyn_clone::DynClone; |
@@ -370,22 +370,33 @@ impl RawGrpcCaller for Client {} |
370 | 370 | #[derive(Clone, Debug)] |
371 | 371 | pub(super) struct AttachMetricLabels { |
372 | 372 | pub(super) labels: Vec<MetricKeyValue>, |
| 373 | + pub(super) normal_task_queue: Option<String>, |
| 374 | + pub(super) sticky_task_queue: Option<String>, |
373 | 375 | } |
374 | 376 | impl AttachMetricLabels { |
375 | 377 | pub(super) fn new(kvs: impl Into<Vec<MetricKeyValue>>) -> Self { |
376 | | - Self { labels: kvs.into() } |
| 378 | + Self { |
| 379 | + labels: kvs.into(), |
| 380 | + normal_task_queue: None, |
| 381 | + sticky_task_queue: None, |
| 382 | + } |
377 | 383 | } |
378 | 384 | pub(super) fn namespace(ns: impl Into<String>) -> Self { |
379 | 385 | AttachMetricLabels::new(vec![namespace_kv(ns.into())]) |
380 | 386 | } |
381 | 387 | pub(super) fn task_q(&mut self, tq: Option<TaskQueue>) -> &mut Self { |
382 | 388 | if let Some(tq) = tq { |
383 | | - self.task_q_str(tq.name); |
| 389 | + if !tq.normal_name.is_empty() { |
| 390 | + self.sticky_task_queue = Some(tq.name); |
| 391 | + self.normal_task_queue = Some(tq.normal_name); |
| 392 | + } else { |
| 393 | + self.normal_task_queue = Some(tq.name); |
| 394 | + } |
384 | 395 | } |
385 | 396 | self |
386 | 397 | } |
387 | 398 | pub(super) fn task_q_str(&mut self, tq: impl Into<String>) -> &mut Self { |
388 | | - self.labels.push(task_queue_kv(tq.into())); |
| 399 | + self.normal_task_queue = Some(tq.into()); |
389 | 400 | self |
390 | 401 | } |
391 | 402 | } |
@@ -635,7 +646,21 @@ proxier! { |
635 | 646 | let task_queue = req_mut.task_queue.as_ref() |
636 | 647 | .map(|tq| tq.name.clone()).unwrap_or_default(); |
637 | 648 | match workers.try_reserve_wft_slot(namespace, task_queue) { |
638 | | - Some(s) => slot = Some(s), |
| 649 | + Some(reservation) => { |
| 650 | + // Populate eager_worker_deployment_options from the slot reservation |
| 651 | + if let Some(opts) = reservation.deployment_options { |
| 652 | + req_mut.eager_worker_deployment_options = Some(temporal_sdk_core_protos::temporal::api::deployment::v1::WorkerDeploymentOptions { |
| 653 | + deployment_name: opts.version.deployment_name, |
| 654 | + build_id: opts.version.build_id, |
| 655 | + worker_versioning_mode: if opts.use_worker_versioning { |
| 656 | + temporal_sdk_core_protos::temporal::api::enums::v1::WorkerVersioningMode::Versioned.into() |
| 657 | + } else { |
| 658 | + temporal_sdk_core_protos::temporal::api::enums::v1::WorkerVersioningMode::Unversioned.into() |
| 659 | + }, |
| 660 | + }); |
| 661 | + } |
| 662 | + slot = Some(reservation.slot); |
| 663 | + } |
639 | 664 | None => req_mut.request_eager_execution = false |
640 | 665 | } |
641 | 666 | } |
@@ -1398,15 +1423,6 @@ proxier! { |
1398 | 1423 | r.extensions_mut().insert(labels); |
1399 | 1424 | } |
1400 | 1425 | ); |
1401 | | - ( |
1402 | | - describe_worker, |
1403 | | - DescribeWorkerRequest, |
1404 | | - DescribeWorkerResponse, |
1405 | | - |r| { |
1406 | | - let labels = namespaced_request!(r); |
1407 | | - r.extensions_mut().insert(labels); |
1408 | | - } |
1409 | | - ); |
1410 | 1426 | ( |
1411 | 1427 | record_worker_heartbeat, |
1412 | 1428 | RecordWorkerHeartbeatRequest, |
@@ -1444,6 +1460,15 @@ proxier! { |
1444 | 1460 | r.extensions_mut().insert(labels); |
1445 | 1461 | } |
1446 | 1462 | ); |
| 1463 | + ( |
| 1464 | + describe_worker, |
| 1465 | + DescribeWorkerRequest, |
| 1466 | + DescribeWorkerResponse, |
| 1467 | + |r| { |
| 1468 | + let labels = namespaced_request!(r); |
| 1469 | + r.extensions_mut().insert(labels); |
| 1470 | + } |
| 1471 | + ); |
1447 | 1472 | ( |
1448 | 1473 | set_worker_deployment_manager, |
1449 | 1474 | SetWorkerDeploymentManagerRequest, |
@@ -1701,7 +1726,7 @@ mod tests { |
1701 | 1726 | } |
1702 | 1727 |
|
1703 | 1728 | #[tokio::test] |
1704 | | - async fn can_mock_workflow_service() { |
| 1729 | + async fn can_mock_services() { |
1705 | 1730 | #[derive(Clone)] |
1706 | 1731 | struct MyFakeServices {} |
1707 | 1732 | impl RawGrpcCaller for MyFakeServices {} |
@@ -1760,4 +1785,133 @@ mod tests { |
1760 | 1785 | .unwrap(); |
1761 | 1786 | assert_eq!(r.into_inner().namespaces[0].failover_version, 12345); |
1762 | 1787 | } |
| 1788 | + |
| 1789 | + #[rstest::rstest] |
| 1790 | + #[case::with_versioning(true)] |
| 1791 | + #[case::without_versioning(false)] |
| 1792 | + #[tokio::test] |
| 1793 | + async fn eager_reservations_attach_deployment_options(#[case] use_worker_versioning: bool) { |
| 1794 | + use crate::worker_registry::{MockClientWorker, MockSlot}; |
| 1795 | + use temporal_sdk_core_api::worker::{WorkerDeploymentOptions, WorkerDeploymentVersion}; |
| 1796 | + use temporal_sdk_core_protos::temporal::api::enums::v1::WorkerVersioningMode; |
| 1797 | + |
| 1798 | + let expected_mode = if use_worker_versioning { |
| 1799 | + WorkerVersioningMode::Versioned |
| 1800 | + } else { |
| 1801 | + WorkerVersioningMode::Unversioned |
| 1802 | + }; |
| 1803 | + |
| 1804 | + #[derive(Clone)] |
| 1805 | + struct MyFakeServices { |
| 1806 | + client_worker_set: Arc<ClientWorkerSet>, |
| 1807 | + expected_mode: WorkerVersioningMode, |
| 1808 | + } |
| 1809 | + impl RawGrpcCaller for MyFakeServices {} |
| 1810 | + impl RawClientProducer for MyFakeServices { |
| 1811 | + fn get_workers_info(&self) -> Option<Arc<ClientWorkerSet>> { |
| 1812 | + Some(self.client_worker_set.clone()) |
| 1813 | + } |
| 1814 | + fn workflow_client(&mut self) -> Box<dyn WorkflowService> { |
| 1815 | + Box::new(MyFakeWfClient { |
| 1816 | + expected_mode: self.expected_mode, |
| 1817 | + }) |
| 1818 | + } |
| 1819 | + fn operator_client(&mut self) -> Box<dyn OperatorService> { |
| 1820 | + unimplemented!() |
| 1821 | + } |
| 1822 | + fn cloud_client(&mut self) -> Box<dyn CloudService> { |
| 1823 | + unimplemented!() |
| 1824 | + } |
| 1825 | + fn test_client(&mut self) -> Box<dyn TestService> { |
| 1826 | + unimplemented!() |
| 1827 | + } |
| 1828 | + fn health_client(&mut self) -> Box<dyn HealthService> { |
| 1829 | + unimplemented!() |
| 1830 | + } |
| 1831 | + } |
| 1832 | + |
| 1833 | + let deployment_opts = WorkerDeploymentOptions { |
| 1834 | + version: WorkerDeploymentVersion { |
| 1835 | + deployment_name: "test-deployment".to_string(), |
| 1836 | + build_id: "test-build-123".to_string(), |
| 1837 | + }, |
| 1838 | + use_worker_versioning, |
| 1839 | + default_versioning_behavior: None, |
| 1840 | + }; |
| 1841 | + |
| 1842 | + let mut mock_provider = MockClientWorker::new(); |
| 1843 | + mock_provider |
| 1844 | + .expect_namespace() |
| 1845 | + .return_const("test-namespace".to_string()); |
| 1846 | + mock_provider |
| 1847 | + .expect_task_queue() |
| 1848 | + .return_const("test-task-queue".to_string()); |
| 1849 | + let mut mock_slot = MockSlot::new(); |
| 1850 | + mock_slot.expect_schedule_wft().returning(|_| Ok(())); |
| 1851 | + mock_provider |
| 1852 | + .expect_try_reserve_wft_slot() |
| 1853 | + .return_once(|| Some(Box::new(mock_slot))); |
| 1854 | + mock_provider |
| 1855 | + .expect_deployment_options() |
| 1856 | + .return_const(Some(deployment_opts.clone())); |
| 1857 | + |
| 1858 | + let client_worker_set = Arc::new(ClientWorkerSet::new()); |
| 1859 | + client_worker_set |
| 1860 | + .register_worker(Arc::new(mock_provider), true) |
| 1861 | + .unwrap(); |
| 1862 | + |
| 1863 | + #[derive(Clone)] |
| 1864 | + struct MyFakeWfClient { |
| 1865 | + expected_mode: WorkerVersioningMode, |
| 1866 | + } |
| 1867 | + impl WorkflowService for MyFakeWfClient { |
| 1868 | + fn start_workflow_execution( |
| 1869 | + &mut self, |
| 1870 | + request: tonic::Request<StartWorkflowExecutionRequest>, |
| 1871 | + ) -> BoxFuture<'_, Result<tonic::Response<StartWorkflowExecutionResponse>, tonic::Status>> |
| 1872 | + { |
| 1873 | + let req = request.into_inner(); |
| 1874 | + let expected_mode = self.expected_mode; |
| 1875 | + |
| 1876 | + assert!( |
| 1877 | + req.eager_worker_deployment_options.is_some(), |
| 1878 | + "eager_worker_deployment_options should be populated" |
| 1879 | + ); |
| 1880 | + |
| 1881 | + let opts = req.eager_worker_deployment_options.as_ref().unwrap(); |
| 1882 | + assert_eq!(opts.deployment_name, "test-deployment"); |
| 1883 | + assert_eq!(opts.build_id, "test-build-123"); |
| 1884 | + assert_eq!(opts.worker_versioning_mode, expected_mode as i32); |
| 1885 | + |
| 1886 | + async { Ok(Response::new(StartWorkflowExecutionResponse::default())) }.boxed() |
| 1887 | + } |
| 1888 | + } |
| 1889 | + |
| 1890 | + let mut mfs = MyFakeServices { |
| 1891 | + client_worker_set, |
| 1892 | + expected_mode, |
| 1893 | + }; |
| 1894 | + |
| 1895 | + // Create a request with eager execution enabled |
| 1896 | + let req = StartWorkflowExecutionRequest { |
| 1897 | + namespace: "test-namespace".to_string(), |
| 1898 | + workflow_id: "test-wf-id".to_string(), |
| 1899 | + workflow_type: Some( |
| 1900 | + temporal_sdk_core_protos::temporal::api::common::v1::WorkflowType { |
| 1901 | + name: "test-workflow".to_string(), |
| 1902 | + }, |
| 1903 | + ), |
| 1904 | + task_queue: Some(TaskQueue { |
| 1905 | + name: "test-task-queue".to_string(), |
| 1906 | + kind: 0, |
| 1907 | + normal_name: String::new(), |
| 1908 | + }), |
| 1909 | + request_eager_execution: true, |
| 1910 | + ..Default::default() |
| 1911 | + }; |
| 1912 | + |
| 1913 | + mfs.start_workflow_execution(req.into_request()) |
| 1914 | + .await |
| 1915 | + .unwrap(); |
| 1916 | + } |
1763 | 1917 | } |
0 commit comments