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    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::{ClaimsRetrievalMetadata, 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    /// Invoked when claim based credential is used, and claims retrieval is needed.
213    /// The callback payload contains metadata for claims retrieval, and user needs
214    /// to call AAD or other identity provider to get the claims.
215    /// The returned claims are used by FabricClient to authenticate to the cluster.
216    /// If empty claim or error is returned, the default handler inside SF client
217    /// is invoked for AAD auth.
218    pub fn with_on_claims_retrieval<T>(mut self, f: T) -> Self
219    where
220        T: Fn(connection::ClaimsRetrievalMetadata) -> crate::Result<crate::WString> + 'static,
221    {
222        if self.cc_handler.is_none() {
223            self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
224        }
225        if let Some(cc) = self.cc_handler.as_mut() {
226            cc.set_f_claims(f)
227        }
228        self
229    }
230
231    /// Sets the role of the client connection. Default is Unknown if not set.
232    /// Unknown role cannot be used for remote client connection.
233    /// If connection strings are set, only Unknown is allowed.
234    pub fn with_client_role(mut self, role: ClientRole) -> Self {
235        self.client_role = role;
236        self
237    }
238
239    /// Sets the client connection strings.
240    /// Example value: localhost:19000
241    pub fn with_connection_strings(mut self, addrs: Vec<crate::WString>) -> Self {
242        self.connection_strings = Some(addrs);
243        self
244    }
245
246    /// Sets the client settings
247    pub fn with_client_settings(mut self, client_settings: FabricClientSettings) -> Self {
248        self.client_settings = Some(client_settings);
249        self
250    }
251
252    // Sets the client credentials
253    pub fn with_credentials(mut self, client_credentials: FabricSecurityCredentials) -> Self {
254        self.client_credentials = Some(client_credentials);
255        self
256    }
257
258    /// Build the fabricclient
259    /// Remarks: FabricClient connect to SF cluster when
260    /// the first API call is triggered. Build/create of the object does not
261    /// establish connection.
262    pub fn build(self) -> Result<FabricClient, FabricClientCreationError> {
263        let c = Self::build_interface(self)?;
264        Ok(FabricClient::from_com(c))
265    }
266
267    /// Build the specific com interface of the fabric client.
268    pub fn build_interface<T: Interface>(self) -> Result<T, FabricClientCreationError> {
269        let cc_handler = self
270            .cc_handler
271            .map(ClientConnectionEventHandlerBridge::new_com);
272        create_local_client_internal::<T>(
273            self.connection_strings.as_ref(),
274            self.sn_handler.as_ref(),
275            cc_handler.as_ref(),
276            Some(self.client_role),
277            self.client_settings,
278            self.client_credentials,
279        )
280    }
281}
282
283// FabricClient safe wrapper
284// The design of FabricClient follows from the csharp client:
285// https://github.com/microsoft/service-fabric/blob/master/src/prod/src/managed/Api/src/System/Fabric/FabricClient.cs
286#[derive(Debug, Clone)]
287pub struct FabricClient {
288    property_client: PropertyManagementClient,
289    service_client: ServiceManagementClient,
290    query_client: QueryClient,
291    health_client: HealthClient,
292}
293
294impl FabricClient {
295    /// Get a builder
296    pub fn builder() -> FabricClientBuilder {
297        FabricClientBuilder::new()
298    }
299
300    /// Creates from com directly. This gives the user freedom to create com from
301    /// custom code and pass it in.
302    /// For the final state of FabricClient, this function should be private.
303    pub fn from_com(com: IFabricPropertyManagementClient2) -> Self {
304        let com_property_client = com.clone();
305        let com_service_client = com
306            .clone()
307            .cast::<IFabricServiceManagementClient6>()
308            .unwrap();
309        let com_query_client = com.clone().cast::<IFabricQueryClient10>().unwrap();
310        let com_health_client = com.clone().cast::<IFabricHealthClient4>().unwrap();
311        Self {
312            property_client: PropertyManagementClient::from(com_property_client),
313            service_client: ServiceManagementClient::from(com_service_client),
314            query_client: QueryClient::from(com_query_client),
315            health_client: HealthClient::from(com_health_client),
316        }
317    }
318
319    /// Get the client for managing Fabric Properties in Naming Service
320    pub fn get_property_manager(&self) -> &PropertyManagementClient {
321        &self.property_client
322    }
323
324    /// Get the client for quering Service Fabric information.
325    pub fn get_query_manager(&self) -> &QueryClient {
326        &self.query_client
327    }
328
329    /// Get the client for managing service information and lifecycles.
330    pub fn get_service_manager(&self) -> &ServiceManagementClient {
331        &self.service_client
332    }
333
334    /// Get the client for get/set Service Fabric health properties.
335    pub fn get_health_manager(&self) -> &HealthClient {
336        &self.health_client
337    }
338}