mssf_core/client/
mod.rs

1// ------------------------------------------------------------
2// Copyright (c) Microsoft Corporation.  All rights reserved.
3// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
4// ------------------------------------------------------------
5
6use crate::{
7    Interface,
8    types::{FabricClientSettings, FabricSecurityCredentials},
9};
10use connection::{ClientConnectionEventHandlerBridge, LambdaClientConnectionNotificationHandler};
11use health_client::HealthClient;
12use mssf_com::FabricClient::{
13    IFabricClientConnectionEventHandler, IFabricClientSettings2, IFabricHealthClient4,
14    IFabricPropertyManagementClient2, IFabricQueryClient10, IFabricServiceManagementClient6,
15    IFabricServiceNotificationEventHandler,
16};
17use notification::{
18    LambdaServiceNotificationHandler, ServiceNotificationEventHandler,
19    ServiceNotificationEventHandlerBridge,
20};
21
22use crate::types::ClientRole;
23
24use self::{query_client::QueryClient, svc_mgmt_client::ServiceManagementClient};
25
26mod connection;
27mod notification;
28
29// Export public client modules
30pub mod health_client;
31mod property_client;
32pub mod query_client;
33pub mod svc_mgmt_client;
34// reexport
35pub use connection::GatewayInformationResult;
36pub use notification::ServiceNotification;
37pub use property_client::PropertyManagementClient;
38
39#[cfg(test)]
40mod tests;
41
42#[non_exhaustive]
43#[derive(Debug)]
44pub enum FabricClientCreationError {
45    InvalidFabricClientSettings(crate::Error),
46    InvalidFabricSecurityCredentials(crate::Error),
47}
48
49impl core::fmt::Display for FabricClientCreationError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            FabricClientCreationError::InvalidFabricClientSettings(error) => {
53                write!(f, "InvalidFabricClientSettings({error})")
54            }
55            FabricClientCreationError::InvalidFabricSecurityCredentials(error) => {
56                write!(f, "InvalidFabricSecurityCredentialss({error})")
57            }
58        }
59    }
60}
61
62impl core::error::Error for FabricClientCreationError {}
63
64/// Creates FabricClient com object using SF com API.
65fn create_local_client_internal<T: Interface>(
66    connection_strings: Option<&Vec<crate::WString>>,
67    service_notification_handler: Option<&IFabricServiceNotificationEventHandler>,
68    client_connection_handler: Option<&IFabricClientConnectionEventHandler>,
69    client_role: Option<ClientRole>,
70    client_settings: Option<FabricClientSettings>,
71    client_credentials: Option<FabricSecurityCredentials>,
72) -> Result<T, FabricClientCreationError> {
73    let role = client_role.unwrap_or(ClientRole::Unknown);
74
75    // create raw conn str ptrs.
76    let connection_strings_ptrs = connection_strings.map(|addrs| {
77        addrs
78            .iter()
79            .map(|s| crate::PCWSTR(s.as_ptr()))
80            .collect::<Vec<_>>()
81    });
82
83    let client = match connection_strings_ptrs {
84        Some(addrs) => {
85            assert!(
86                role == ClientRole::Unknown,
87                "ClientRole is for local client only and cannot be used for connecting to remote cluster."
88            );
89            crate::API_TABLE.fabric_create_client3::<T>(
90                &addrs,
91                service_notification_handler,
92                client_connection_handler,
93            )
94        },
95        None => {
96            if role == ClientRole::Unknown {
97                // unknown role should use the SF function without role param.
98                    crate::API_TABLE.fabric_create_local_client3::<T>(
99                        service_notification_handler,
100                        client_connection_handler,
101                    )
102            } else {
103                    crate::API_TABLE.fabric_create_local_client4::<T>(
104                        service_notification_handler,
105                        client_connection_handler,
106                        role.into(),
107                    )
108            }
109        }
110    }
111    // if params are right, client should be created. There is no network call involved during obj creation.
112    .expect("failed to create fabric client");
113    if client_settings.is_some() || client_credentials.is_some() {
114        let setting_interface = client
115            .clone()
116            .cast::<IFabricClientSettings2>()
117            .expect("failed to cast fabric client to IFabricClientSettings2");
118        if let Some(desired_settings) = client_settings {
119            desired_settings
120                .apply(&setting_interface)
121                .map_err(FabricClientCreationError::InvalidFabricClientSettings)?;
122        }
123        if let Some(desired_credentials) = client_credentials {
124            desired_credentials
125                .apply(setting_interface)
126                .map_err(FabricClientCreationError::InvalidFabricSecurityCredentials)?;
127        }
128    };
129    Ok(client)
130}
131
132// Builder for FabricClient
133pub struct FabricClientBuilder {
134    sn_handler: Option<IFabricServiceNotificationEventHandler>,
135    cc_handler: Option<LambdaClientConnectionNotificationHandler>,
136    client_role: ClientRole,
137    connection_strings: Option<Vec<crate::WString>>,
138    client_settings: Option<FabricClientSettings>,
139    client_credentials: Option<FabricSecurityCredentials>,
140}
141
142impl Default for FabricClientBuilder {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl FabricClientBuilder {
149    /// Creates the builder.
150    pub fn new() -> Self {
151        Self {
152            sn_handler: None,
153            cc_handler: None,
154            client_role: ClientRole::Unknown,
155            connection_strings: None,
156            client_settings: None,
157            client_credentials: None,
158        }
159    }
160
161    /// Configures the service notification handler internally.
162    fn with_service_notification_handler(
163        mut self,
164        handler: impl ServiceNotificationEventHandler,
165    ) -> Self {
166        self.sn_handler = Some(ServiceNotificationEventHandlerBridge::new_com(handler));
167        self
168    }
169
170    /// Configures the service notification handler.
171    /// See details in `register_service_notification_filter` API.
172    /// If the service endpoint change matches the registered filter,
173    /// this notification is invoked.
174    ///
175    pub fn with_on_service_notification<T>(self, f: T) -> Self
176    where
177        T: Fn(&ServiceNotification) -> crate::Result<()> + 'static,
178    {
179        let handler = LambdaServiceNotificationHandler::new(f);
180        self.with_service_notification_handler(handler)
181    }
182
183    /// When FabricClient connects to the SF cluster, this callback is invoked.
184    pub fn with_on_client_connect<T>(mut self, f: T) -> Self
185    where
186        T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
187    {
188        if self.cc_handler.is_none() {
189            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
190        }
191        if let Some(cc) = self.cc_handler.as_mut() {
192            cc.set_f_conn(f)
193        }
194        self
195    }
196
197    /// When FabricClient disconnets to the SF cluster, this callback is called.
198    /// This callback is not called on Drop of FabricClient.
199    pub fn with_on_client_disconnect<T>(mut self, f: T) -> Self
200    where
201        T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
202    {
203        if self.cc_handler.is_none() {
204            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
205        }
206        if let Some(cc) = self.cc_handler.as_mut() {
207            cc.set_f_disconn(f)
208        }
209        self
210    }
211
212    /// Sets the role of the client connection. Default is Unknown if not set.
213    /// Unknown role cannot be used for remote client connection.
214    /// If connection strings are set, only Unknown is allowed.
215    pub fn with_client_role(mut self, role: ClientRole) -> Self {
216        self.client_role = role;
217        self
218    }
219
220    /// Sets the client connection strings.
221    /// Example value: localhost:19000
222    pub fn with_connection_strings(mut self, addrs: Vec<crate::WString>) -> Self {
223        self.connection_strings = Some(addrs);
224        self
225    }
226
227    /// Sets the client settings
228    pub fn with_client_settings(mut self, client_settings: FabricClientSettings) -> Self {
229        self.client_settings = Some(client_settings);
230        self
231    }
232
233    // Sets the client credentials
234    pub fn with_credentials(mut self, client_credentials: FabricSecurityCredentials) -> Self {
235        self.client_credentials = Some(client_credentials);
236        self
237    }
238
239    /// Build the fabricclient
240    /// Remarks: FabricClient connect to SF cluster when
241    /// the first API call is triggered. Build/create of the object does not
242    /// establish connection.
243    pub fn build(self) -> Result<FabricClient, FabricClientCreationError> {
244        let c = Self::build_interface(self)?;
245        Ok(FabricClient::from_com(c))
246    }
247
248    /// Build the specific com interface of the fabric client.
249    pub fn build_interface<T: Interface>(self) -> Result<T, FabricClientCreationError> {
250        let cc_handler = self
251            .cc_handler
252            .map(ClientConnectionEventHandlerBridge::new_com);
253        create_local_client_internal::<T>(
254            self.connection_strings.as_ref(),
255            self.sn_handler.as_ref(),
256            cc_handler.as_ref(),
257            Some(self.client_role),
258            self.client_settings,
259            self.client_credentials,
260        )
261    }
262}
263
264// FabricClient safe wrapper
265// The design of FabricClient follows from the csharp client:
266// https://github.com/microsoft/service-fabric/blob/master/src/prod/src/managed/Api/src/System/Fabric/FabricClient.cs
267#[derive(Debug, Clone)]
268pub struct FabricClient {
269    property_client: PropertyManagementClient,
270    service_client: ServiceManagementClient,
271    query_client: QueryClient,
272    health_client: HealthClient,
273}
274
275impl FabricClient {
276    /// Get a builder
277    pub fn builder() -> FabricClientBuilder {
278        FabricClientBuilder::new()
279    }
280
281    /// Creates from com directly. This gives the user freedom to create com from
282    /// custom code and pass it in.
283    /// For the final state of FabricClient, this function should be private.
284    pub fn from_com(com: IFabricPropertyManagementClient2) -> Self {
285        let com_property_client = com.clone();
286        let com_service_client = com
287            .clone()
288            .cast::<IFabricServiceManagementClient6>()
289            .unwrap();
290        let com_query_client = com.clone().cast::<IFabricQueryClient10>().unwrap();
291        let com_health_client = com.clone().cast::<IFabricHealthClient4>().unwrap();
292        Self {
293            property_client: PropertyManagementClient::from(com_property_client),
294            service_client: ServiceManagementClient::from(com_service_client),
295            query_client: QueryClient::from(com_query_client),
296            health_client: HealthClient::from(com_health_client),
297        }
298    }
299
300    /// Get the client for managing Fabric Properties in Naming Service
301    pub fn get_property_manager(&self) -> &PropertyManagementClient {
302        &self.property_client
303    }
304
305    /// Get the client for quering Service Fabric information.
306    pub fn get_query_manager(&self) -> &QueryClient {
307        &self.query_client
308    }
309
310    /// Get the client for managing service information and lifecycles.
311    pub fn get_service_manager(&self) -> &ServiceManagementClient {
312        &self.service_client
313    }
314
315    /// Get the client for get/set Service Fabric health properties.
316    pub fn get_health_manager(&self) -> &HealthClient {
317        &self.health_client
318    }
319}