text: // Copyright (c) Microsoft Corporation
  // All rights reserved.
  //
  // MIT License
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
  // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
  // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  // module dependencies
  const crypto = require('crypto');
  const {isNil} = require('lodash');
  
  const {convertToJobAttempt} = require('@pai/utils/frameworkConverter');
  const launcherConfig = require('@pai/config/launcher');
  const createError = require('@pai/utils/error');
  const k8sModel = require('@pai/models/kubernetes/kubernetes');
  const logger = require('@pai/config/logger');
  const {sequelize} = require('@pai/utils/postgresUtil');
  
  const convertName = (name) => {
    // convert framework name to fit framework controller spec
    return name.toLowerCase().replace(/[^a-z0-9]/g, '');
  };
  
  const encodeName = (name) => {
    if (name.startsWith('unknown') || !name.includes('~')) {
      // framework is not generated by PAI
      return convertName(name.replace(/^unknown/g, ''));
    } else {
      // md5 hash
      return crypto.createHash('md5').update(name).digest('hex');
    }
  };
  
  if (sequelize && launcherConfig.enabledJobHistory) {
    const healthCheck = async () => {
      try {
        await sequelize.authenticate();
        return true;
      } catch (e) {
        logger.error(e.message);
        return false;
      }
    };
  
    const list = async (frameworkName) => {
      let attemptData = [];
      let uid;
  
      // get latest framework from k8s API
      let response;
      try {
        response = await k8sModel.getClient().get(
          launcherConfig.frameworkPath(encodeName(frameworkName)),
          {
            headers: launcherConfig.requestHeaders,
          }
        );
      } catch (error) {
        logger.error(`error when getting framework from k8s api: ${error.message}`);
        if (error.response != null) {
          response = error.response;
        } else {
          throw error;
        }
      }
  
      if (response.status === 200) {
        // get UID from k8s framework API
        uid = response.data.metadata.uid;
        attemptData.push({
          ...(await convertToJobAttempt(response.data)),
          isLatest: true,
        });
      } else if (response.status === 404) {
        logger.warn(`could not get framework ${uid} from k8s: ${JSON.stringify(response)}`);
        return {status: 404, data: null};
      } else {
        throw createError(response.status, 'UnknownError', response.data.message);
      }
  
      if (isNil(uid)) {
        return {status: 404, data: null};
      }
  
      const sqlSentence = `SELECT (record->'objectSnapshot') as data FROM fc_objectsnapshots WHERE ` +
        `record->'objectSnapshot'->'metadata'->'uid' ? '${uid}' and ` +
        `record->'objectSnapshot'->'kind' ? 'Framework' ` +
        `ORDER BY cast(record->'objectSnapshot'->'status'->'attemptStatus'->>'id' as INTEGER) ASC;`;
      const pgResult = (await sequelize.query(sqlSentence))[0];
      const jobRetries = await Promise.all(
        pgResult.map((row) => {
          return convertToJobAttempt(row.data);
        }),
      );
      attemptData.push(
        ...jobRetries.map((jobRetry) => {
          return {...jobRetry, isLatest: false};
        }),
      );
  
      return {status: 200, data: attemptData};
    };
  
    const get = async (frameworkName, jobAttemptIndex) => {
      let uid;
      let attemptFramework;
      let response;
      try {
        response = await k8sModel.getClient().get(
          launcherConfig.frameworkPath(encodeName(frameworkName)),
          {
            headers: launcherConfig.requestHeaders,
          }
        );
      } catch (error) {
        logger.error(`error when getting framework from k8s api: ${error.message}`);
        if (error.response != null) {
          response = error.response;
        } else {
          throw error;
        }
      }
  
      if (response.status === 200) {
        // get uid from k8s framwork API
        uid = response.data.metadata.uid;
        attemptFramework = response.data;
      } else if (response.status === 404) {
        logger.warn(`could not get framework ${uid} from k8s: ${JSON.stringify(response)}`);
        return {status: 404, data: null};
      } else {
        throw createError(response.status, 'UnknownError', response.data.message);
      }
  
      if (jobAttemptIndex < attemptFramework.spec.retryPolicy.maxRetryCount) {
        if (isNil(uid)) {
          return {status: 404, data: null};
        }
        const sqlSentence = `SELECT (record->'objectSnapshot') as data FROM fc_objectsnapshots WHERE ` +
          `record->'objectSnapshot'->'metadata'->'uid' ? '${uid}' and ` +
          `record->'objectSnapshot'->'status'->'attemptStatus'->>'id' = '${jobAttemptIndex}' and ` +
          `record->'objectSnapshot'->'kind' ? 'Framework' ` +
          `ORDER BY cast(record->'objectSnapshot'->'status'->'attemptStatus'->>'id' as INTEGER) ASC;`;
        const pgResult = (await sequelize.query(sqlSentence))[0];
  
        if (pgResult.length === 0) {
          return {status: 404, data: null};
        } else {
          attemptFramework = pgResult[0].data;
          const attemptDetail = await convertToJobAttempt(attemptFramework);
          return {status: 200, data: {...attemptDetail, isLatest: false}};
        }
      } else if (
        jobAttemptIndex === attemptFramework.spec.retryPolicy.maxRetryCount
      ) {
        // get latest frameworks from k8s API
        const attemptDetail = await convertToJobAttempt(attemptFramework);
        return {status: 200, data: {...attemptDetail, isLatest: true}};
      } else {
        return {status: 404, data: null};
      }
    };
  
    module.exports = {
      healthCheck,
      list,
      get,
    };
  } else {
    module.exports = {
      healthCheck: () => false,
      list: () => {
   throw Error('Unexpected Call');
  },
      get: () => {
   throw Error('Unexpected Call');
  },
    };
  }
  