@@ -12,7 +12,6 @@ const { KmsAWSClient } = require('@scality/arsenal').network;
1212const Common = require ( './common' ) ;
1313const vault = require ( '../auth/vault' ) ;
1414const Cache = require ( './Cache' ) ;
15- const cache = new Cache ( ) ;
1615const {
1716 KmsProtocol,
1817 makeBackend,
@@ -69,13 +68,14 @@ function getClientIdentifier(backend) {
6968 * Build a client for every entry of the `kms` config.
7069 * @param {object[] } entries - `config.kms`
7170 * @returns {object } `{ clientInstances, defaultInstance }`, with
72- * `clientInstances` keyed by `type:protocol:provider`
71+ * `clientInstances` keyed by `type:protocol:provider`, each with its own
72+ * health check cache
7373 */
7474function createClients ( entries ) {
7575 const instances = { } ;
7676 let defaultInstance ;
7777 entries . forEach ( entry => {
78- const instance = getClient ( entry . protocol , entry . settings ) ;
78+ const instance = { ... getClient ( entry . protocol , entry . settings ) , healthCache : new Cache ( ) } ;
7979 instances [ getClientIdentifier ( instance . client . backend ) ] = instance ;
8080 if ( entry . default ) {
8181 defaultInstance = instance ;
@@ -99,8 +99,11 @@ const { clientInstances, defaultInstance } = createClients(config.kms);
9999const { client, implName } = defaultInstance ;
100100const currentIdentifier = getClientIdentifier ( client . backend ) ;
101101
102+ // Taken before the SSE-migration client is added, which is not health-checked
103+ const configuredInstances = Object . values ( clientInstances ) ;
104+
102105// Keys of every configured KMS are accepted
103- const availableBackends = Object . values ( clientInstances ) . map ( instance => instance . client . backend ) ;
106+ const availableBackends = configuredInstances . map ( instance => instance . client . backend ) ;
104107
105108let previousBackend ;
106109let previousIdentifier ;
@@ -168,6 +171,84 @@ function getClientForKey(key, log) {
168171 } ;
169172}
170173
174+ /**
175+ * Health of one KMS, from its cache when the last check is recent.
176+ * @param {object } instance - client instance, from createClients
177+ * @param {object } log - logger object
178+ * @param {function } cb - callback, called with (null, result)
179+ * @returns {undefined }
180+ */
181+ function checkKmsHealth ( instance , log , cb ) {
182+ const { client : kmsClient , healthCache } = instance ;
183+ const { provider } = kmsClient . backend ;
184+ if ( ! kmsClient . healthcheck ) {
185+ return cb ( null , { code : 200 , message : 'OK' } ) ;
186+ }
187+
188+ const cachedResult = healthCache . getResult ( ) ;
189+ logger . debug ( 'current KMS cache state' , { provider, result : cachedResult } ) ;
190+
191+ const shouldRefreshCache = healthCache . shouldRefresh ( ) ;
192+
193+ if ( shouldRefreshCache ) {
194+ logger . debug ( 'health check for KMS backend' , { provider } ) ;
195+ return kmsClient . healthcheck ( log , err => {
196+ let res ;
197+ if ( err ) {
198+ res = {
199+ // The following response makes sure that if KMS is down,
200+ // cloudserver health check is still healthy.
201+ // Simply including an error code in the response won't cause the health check to fail.
202+ // Instead, the healthCheck logic detects errors by checking for the "error" field.
203+ code : err . code ,
204+ message : 'KMS health check failed' ,
205+ description : err . description ,
206+ } ;
207+ logger . warn ( 'KMS health check failed' , { provider, errorCode : err . code , error : err . description } ) ;
208+ } else {
209+ res = {
210+ code : 200 ,
211+ message : 'OK' ,
212+ } ;
213+ logger . info ( 'KMS health check succeeded' , { provider, res } ) ;
214+ }
215+
216+ healthCache . setResult ( res ) ;
217+ const updatedResult = healthCache . getResult ( ) ;
218+ logger . debug ( 'updated KMS cache:' , { provider, result : updatedResult } ) ;
219+
220+ return cb ( null , updatedResult ) ;
221+ } ) ;
222+ }
223+
224+ // Use cached healthcheck result if within a 1-hour window
225+ logger . debug ( 'using cached KMS health check' , { provider, cachedResult } ) ;
226+ return cb ( null , cachedResult ) ;
227+ }
228+
229+ /**
230+ * Health of each KMS, checked in parallel.
231+ * @param {object[] } instances - client instances, from createClients
232+ * @param {object } log - logger object
233+ * @param {function } cb - callback, called with (null, result) where result
234+ * has one entry per KMS, keyed by provider name
235+ * @returns {undefined }
236+ */
237+ function checkKmsHealthByProvider ( instances , log , cb ) {
238+ async . map (
239+ instances ,
240+ ( instance , done ) => checkKmsHealth ( instance , log , done ) ,
241+ // No error: a failed check is reported in the KMS entry
242+ ( err , results ) => {
243+ const respBody = { } ;
244+ instances . forEach ( ( instance , i ) => {
245+ respBody [ instance . client . backend . provider ] = results [ i ] ;
246+ } ) ;
247+ return cb ( null , respBody ) ;
248+ } ,
249+ ) ;
250+ }
251+
171252class KMS {
172253 /** Access to client for tests (to create a key without vault check and bucket) */
173254 static get client ( ) {
@@ -547,55 +628,17 @@ class KMS {
547628 ) ;
548629 }
549630
631+ /**
632+ * Health of every configured KMS, keyed by provider name.
633+ * @param {object } log - logger object
634+ * @param {function } cb - callback, called with (null, result)
635+ * @returns {undefined }
636+ */
550637 static checkHealth ( log , cb ) {
551- if ( ! client . healthcheck ) {
552- return cb ( null , {
553- [ implName ] : { code : 200 , message : 'OK' } ,
554- } ) ;
555- }
556-
557- const cachedResult = cache . getResult ( ) ;
558- logger . debug ( 'current KMS cache state' , { result : cachedResult } ) ;
559-
560- const shouldRefreshCache = cache . shouldRefresh ( ) ;
561-
562- if ( shouldRefreshCache ) {
563- logger . debug ( 'health check for KMS backend' ) ;
564- return client . healthcheck ( log , err => {
565- let res ;
566- if ( err ) {
567- res = {
568- // The following response makes sure that if KMS is down,
569- // cloudserver health check is still healthy.
570- // Simply including an error code in the response won't cause the health check to fail.
571- // Instead, the healthCheck logic detects errors by checking for the "error" field.
572- code : err . code ,
573- message : 'KMS health check failed' ,
574- description : err . description ,
575- } ;
576- logger . warn ( 'KMS health check failed' , { errorCode : err . code , error : err . description } ) ;
577- } else {
578- res = {
579- code : 200 ,
580- message : 'OK' ,
581- } ;
582- logger . info ( 'KMS health check succeeded' , { res } ) ;
583- }
584-
585- cache . setResult ( res ) ;
586- const updatedResult = cache . getResult ( ) ;
587- logger . debug ( 'updated KMS cache:' , { result : updatedResult } ) ;
588-
589- const respBody = { [ implName ] : updatedResult } ;
590- return cb ( null , respBody ) ;
591- } ) ;
592- }
593-
594- // Use cached healthcheck result if within a 1-hour window
595- logger . debug ( 'using cached KMS health check' , { cachedResult } ) ;
596- return cb ( null , { [ implName ] : cachedResult } ) ;
638+ return checkKmsHealthByProvider ( configuredInstances , log , cb ) ;
597639 }
598640}
599641
600642module . exports = KMS ;
601643module . exports . createClients = createClients ;
644+ module . exports . checkKmsHealthByProvider = checkKmsHealthByProvider ;
0 commit comments