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
135pub struct FabricClientBuilder {
136    sn_handler: Option<IFabricServiceNotificationEventHandler>,
137    cc_handler: Option<LambdaClientConnectionNotificationHandler>,
138    client_role: ClientRole,
139    connection_strings: Option<Vec<crate::WString>>,
140    client_settings: Option<FabricClientSettings>,
141    client_credentials: Option<FabricSecurityCredentials>,
142}
143
144impl Default for FabricClientBuilder {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150impl FabricClientBuilder {
151    /// Creates the builder.
152    pub fn new() -> Self {
153        Self {
154            sn_handler: None,
155            cc_handler: None,
156            client_role: ClientRole::Unknown,
157            connection_strings: None,
158            client_settings: None,
159            client_credentials: None,
160        }
161    }
162
163    /// Configures the service notification handler internally.
164    fn with_service_notification_handler(
165        mut self,
166        handler: impl ServiceNotificationEventHandler,
167    ) -> Self {
168        self.sn_handler = Some(ServiceNotificationEventHandlerBridge::new_com(handler));
169        self
170    }
171
172    /// Configures the service notification handler.
173    /// See details in `register_service_notification_filter` API.
174    /// If the service endpoint change matches the registered filter,
175    /// this notification is invoked.
176    ///
177    pub fn with_on_service_notification<T>(self, f: T) -> Self
178    where
179        T: Fn(ServiceNotification) -> crate::Result<()> + 'static,
180    {
181        let handler = LambdaServiceNotificationHandler::new(f);
182        self.with_service_notification_handler(handler)
183    }
184
185    /// When FabricClient connects to the SF cluster, this callback is invoked.
186    pub fn with_on_client_connect<T>(mut self, f: T) -> Self
187    where
188        T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
189    {
190        if self.cc_handler.is_none() {
191            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
192        }
193        if let Some(cc) = self.cc_handler.as_mut() {
194            cc.set_f_conn(f)
195        }
196        self
197    }
198
199    /// When FabricClient disconnets to the SF cluster, this callback is called.
200    /// This callback is not called on Drop of FabricClient.
201    pub fn with_on_client_disconnect<T>(mut self, f: T) -> Self
202    where
203        T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
204    {
205        if self.cc_handler.is_none() {
206            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
207        }
208        if let Some(cc) = self.cc_handler.as_mut() {
209            cc.set_f_disconn(f)
210        }
211        self
212    }
213
214    /// Invoked when claim based credential is used, and claims retrieval is needed.
215    /// The callback payload contains metadata for claims retrieval, and user needs
216    /// to call AAD or other identity provider to get the claims.
217    /// The returned claims are used by FabricClient to authenticate to the cluster.
218    /// If empty claim or error is returned, the default handler inside SF client
219    /// is invoked for AAD auth.
220    pub fn with_on_claims_retrieval<T>(mut self, f: T) -> Self
221    where
222        T: Fn(connection::ClaimsRetrievalMetadata) -> crate::Result<crate::WString> + 'static,
223    {
224        if self.cc_handler.is_none() {
225            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
226        }
227        if let Some(cc) = self.cc_handler.as_mut() {
228            cc.set_f_claims(f)
229        }
230        self
231    }
232
233    /// Sets the role of the client connection. Default is Unknown if not set.
234    /// Unknown role cannot be used for remote client connection.
235    /// If connection strings are set, only Unknown is allowed.
236    pub fn with_client_role(mut self, role: ClientRole) -> Self {
237        self.client_role = role;
238        self
239    }
240
241    /// Sets the client connection strings.
242    /// Example value: localhost:19000
243    pub fn with_connection_strings(mut self, addrs: Vec<crate::WString>) -> Self {
244        self.connection_strings = Some(addrs);
245        self
246    }
247
248    /// Sets the client settings
249    pub fn with_client_settings(mut self, client_settings: FabricClientSettings) -> Self {
250        self.client_settings = Some(client_settings);
251        self
252    }
253
254    // Sets the client credentials
255    pub fn with_credentials(mut self, client_credentials: FabricSecurityCredentials) -> Self {
256        self.client_credentials = Some(client_credentials);
257        self
258    }
259
260    /// Build the fabricclient
261    /// Remarks: FabricClient connect to SF cluster when
262    /// the first API call is triggered. Build/create of the object does not
263    /// establish connection.
264    pub fn build(self) -> Result<FabricClient, FabricClientCreationError> {
265        let c = Self::build_interface(self)?;
266        Ok(FabricClient::from_com(c))
267    }
268
269    /// Build the specific com interface of the fabric client.
270    pub fn build_interface<T: Interface>(self) -> Result<T, FabricClientCreationError> {
271        let cc_handler = self
272            .cc_handler
273            .map(ClientConnectionEventHandlerBridge::new_com);
274        create_local_client_internal::<T>(
275            self.connection_strings.as_ref(),
276            self.sn_handler.as_ref(),
277            cc_handler.as_ref(),
278            Some(self.client_role),
279            self.client_settings,
280            self.client_credentials,
281        )
282    }
283}
284
285// FabricClient safe wrapper
286// The design of FabricClient follows from the csharp client:
287// https://github.com/microsoft/service-fabric/blob/master/src/prod/src/managed/Api/src/System/Fabric/FabricClient.cs
288#[derive(Debug, Clone)]
289pub struct FabricClient {
290    property_client: PropertyManagementClient,
291    service_client: ServiceManagementClient,
292    query_client: QueryClient,
293    health_client: HealthClient,
294    application_client: ApplicationManagementClient,
295}
296
297impl FabricClient {
298    /// Get a builder
299    pub fn builder() -> FabricClientBuilder {
300        FabricClientBuilder::new()
301    }
302
303    /// Creates from com directly. This gives the user freedom to create com from
304    /// custom code and pass it in.
305    /// For the final state of FabricClient, this function should be private.
306    pub fn from_com(com: IFabricPropertyManagementClient2) -> Self {
307        let com_property_client = com.clone();
308        let com_service_client = com
309            .clone()
310            .cast::<IFabricServiceManagementClient8>()
311            .unwrap();
312        let com_query_client = com.clone().cast::<IFabricQueryClient13>().unwrap();
313        let com_health_client = com.clone().cast::<IFabricHealthClient4>().unwrap();
314        let com_application_client = com
315            .clone()
316            .cast::<IFabricApplicationManagementClient>()
317            .unwrap();
318        Self {
319            property_client: PropertyManagementClient::from(com_property_client),
320            service_client: ServiceManagementClient::from(com_service_client),
321            query_client: QueryClient::from(com_query_client),
322            health_client: HealthClient::from(com_health_client),
323            application_client: ApplicationManagementClient::from(com_application_client),
324        }
325    }
326
327    /// Get the client for managing Fabric Properties in Naming Service
328    pub fn get_property_manager(&self) -> &PropertyManagementClient {
329        &self.property_client
330    }
331
332    /// Get the client for quering Service Fabric information.
333    pub fn get_query_manager(&self) -> &QueryClient {
334        &self.query_client
335    }
336
337    /// Get the client for managing service information and lifecycles.
338    pub fn get_service_manager(&self) -> &ServiceManagementClient {
339        &self.service_client
340    }
341
342    /// Get the client for get/set Service Fabric health properties.
343    pub fn get_health_manager(&self) -> &HealthClient {
344        &self.health_client
345    }
346
347    /// Get the client for managing applications, including querying application
348    /// upgrade progress.
349    pub fn get_application_manager(&self) -> &ApplicationManagementClient {
350        &self.application_client
351    }
352}