Skip to main content

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