From 9d8c736d925755de6158ff866f824b9043b8e1d6 Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Mon, 25 Dec 2023 04:33:12 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20648:=20=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B9=E8=87=AA=E5=8B=95=E5=89=B2=E3=82=8A?= =?UTF-8?q?=E5=BD=93=E3=81=A6=E5=87=A6=E7=90=86=E5=AE=9F=E8=A3=85=EF=BC=88?= =?UTF-8?q?=E3=83=AA=E3=83=88=E3=83=A9=E3=82=A4=E5=AF=BE=E5=BF=9C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3296: ライセンス自動割り当て処理実装(リトライ対応)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3296) - licenseAutoAllocationProcessingに任意引数で日付を追加。  日付がある場合はその日付を実行日としてライセンス自動割り当てを行う。 ## レビューポイント - 未来日日付が指定された場合エラーにしなくてよいか? (運用で使う想定はない) ## 動作確認状況 - ユニットテストで確認、devlopで確認 ・引数なしで手動実行した場合に、実行日でライセンス自動割り当てが処理されること。 ・引数ありで手動実行した場合に、引数の日付でライセンス自動割り当てが処理されること。 ・(未来日で)引数ありで手動実行した場合に、引数の日付でライセンス自動割り当てが処理されること。 (あったらうれしいかもしれないのでリトライ処理の機能として可能な状態にしておいています。) ## 補足 - 実際にサポートの担当が行う作業は以下になります。 ①AzureFunctionのlicenseAutoAllocationManualRetryにアクセスする。 ![image (3).png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/648/attachments/image%20%283%29.png) ②左カラムの「コードとテスト」を押下し、「テストと実行」を押下する。 ![image (4).png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/648/attachments/image%20%284%29.png) ③入力欄の、「クエリ」にdateと日付をハイフン区切りで入力して、「実行」を押下する。 ![image (2).png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/648/attachments/image%20%282%29.png) --- dictation_function/src/constants/index.ts | 26 +++ .../src/functions/licenseAutoAllocation.ts | 25 +- .../licenseAutoAllocationManualRetry.ts | 91 ++++++++ .../src/test/licenseAutoAllocation.spec.ts | 221 ++++++++++++++++-- 4 files changed, 341 insertions(+), 22 deletions(-) create mode 100644 dictation_function/src/functions/licenseAutoAllocationManualRetry.ts diff --git a/dictation_function/src/constants/index.ts b/dictation_function/src/constants/index.ts index a89f15e..f1e0b64 100644 --- a/dictation_function/src/constants/index.ts +++ b/dictation_function/src/constants/index.ts @@ -267,3 +267,29 @@ export const TERM_TYPE = { EULA: "EULA", DPA: "DPA", } as const; + +/** + * HTTPメソッド + * @const {string[]} + */ +export const HTTP_METHODS = { + POST: "POST", + GET: "GET", + DELETE: "DELETE", + HEAD: "HEAD", + PATCH: "PATCH", + PUT: "PUT", + OPTIONS: "OPTIONS", + TRACE: "TRACE", + CONNECT: "CONNECT", +}; + +/** + * HTTPステータスコード + * @const {string[]} + */ +export const HTTP_STATUS_CODES = { + OK: 200, + BAD_REQUEST: 400, + INTERNAL_SERVER_ERROR: 500, +}; diff --git a/dictation_function/src/functions/licenseAutoAllocation.ts b/dictation_function/src/functions/licenseAutoAllocation.ts index 6bddc1a..b3b868b 100644 --- a/dictation_function/src/functions/licenseAutoAllocation.ts +++ b/dictation_function/src/functions/licenseAutoAllocation.ts @@ -20,15 +20,19 @@ import { export async function licenseAutoAllocationProcessing( context: InvocationContext, - datasource: DataSource + datasource: DataSource, + dateToTrigger?: Date ): Promise { try { context.log("[IN]licenseAutoAllocationProcessing"); // ライセンスの有効期間判定用 - const currentDateZeroTime = new DateWithZeroTime(); - const currentDateEndTime = new DateWithDayEndTime(); - + let currentDateZeroTime = new DateWithZeroTime(); + let currentDateEndTime = new DateWithDayEndTime(); + if (dateToTrigger) { + currentDateZeroTime = new DateWithZeroTime(dateToTrigger); + currentDateEndTime = new DateWithDayEndTime(dateToTrigger); + } // 自動更新対象の候補となるアカウントを取得 const accountRepository = datasource.getRepository(Account); const targetAccounts = await accountRepository.find({ @@ -206,10 +210,12 @@ export async function findTargetUser( export async function getAutoAllocatableLicense( context: InvocationContext, licenseRepository: Repository, - accountId: number + accountId: number, + currentDateEndTime: DateWithDayEndTime ): Promise { try { - const currentNextDateTime = new DateWithNextDayEndTime(); + context.log("[IN]getAutoAllocatableLicense"); + const currentNextDateTime = new DateWithNextDayEndTime(currentDateEndTime); // 割り当て可能なライセンスを取得 const license = await licenseRepository.findOne({ where: { @@ -218,7 +224,7 @@ export async function getAutoAllocatableLicense( LICENSE_ALLOCATED_STATUS.REUSABLE, LICENSE_ALLOCATED_STATUS.UNALLOCATED, ]), - expiry_date: MoreThan(currentNextDateTime), + expiry_date: MoreThan(currentNextDateTime) || null, }, order: { expiry_date: "ASC", @@ -233,6 +239,8 @@ export async function getAutoAllocatableLicense( context.error(e); context.log("getAutoAllocatableLicense failed."); throw e; + } finally { + context.log("[OUT]getAutoAllocatableLicense"); } } @@ -266,7 +274,8 @@ export async function allocateLicense( const autoAllocatableLicense = await getAutoAllocatableLicense( context, licenseRepository, - autoAllocationList.accountId + autoAllocationList.accountId, + currentDateEndTime ); // 割り当て可能なライセンスが存在しなければreturnし、その後ループ終了 diff --git a/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts b/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts new file mode 100644 index 0000000..18fa0f2 --- /dev/null +++ b/dictation_function/src/functions/licenseAutoAllocationManualRetry.ts @@ -0,0 +1,91 @@ +import { + HttpRequest, + HttpResponseInit, + InvocationContext, + app, + HttpMethod, +} from "@azure/functions"; +import { licenseAutoAllocationProcessing } from "./licenseAutoAllocation"; +import * as dotenv from "dotenv"; +import { DataSource } from "typeorm"; +import { User } from "../entity/user.entity"; +import { Account } from "../entity/account.entity"; +import { License, LicenseAllocationHistory } from "../entity/license.entity"; +import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants"; + +export async function licenseAutoAllocationManualRetry( + req: HttpRequest, + context: InvocationContext +): Promise { + context.log(req); + try { + if (req.method === HTTP_METHODS.POST) { + const queryParams = new URLSearchParams(req.url.split("&")[1]); // クエリパラメータを取得 + const requestedDate = queryParams.get("date"); + context.log("requestedDate: " + requestedDate); + let dateToTrigger: Date; + if (requestedDate) { + // パラメータのチェックを行う(YYYY-MM-DD形式) + if (!requestedDate.match(/^\d{4}-\d{2}-\d{2}$/)) { + context.log("Invalid date format."); + return { + status: HTTP_STATUS_CODES.BAD_REQUEST, + body: "Invalid date format.", + }; + } + dateToTrigger = new Date(requestedDate); + } else { + dateToTrigger = new Date(); + } + context.log("[IN]licenseAutoAllocationManualRetry"); + dotenv.config({ path: ".env" }); + dotenv.config({ path: ".env.local", override: true }); + let datasource: DataSource; + try { + datasource = new DataSource({ + type: "mysql", + host: process.env.DB_HOST, + port: Number(process.env.DB_PORT), + username: process.env.DB_USERNAME, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + entities: [User, Account, License, LicenseAllocationHistory], + }); + await datasource.initialize(); + } catch (e) { + context.log("database initialize failed."); + context.error(e); + throw e; + } + + await licenseAutoAllocationProcessing(context, datasource, dateToTrigger); + context.log("Automatic license allocation has been triggered."); + return { + status: HTTP_STATUS_CODES.OK, + body: "Automatic license allocation has been triggered.", + }; + } else { + context.log("Please use the POST method."); + return { + status: HTTP_STATUS_CODES.BAD_REQUEST, + body: "Please use the POST method.", + }; + } + } catch (e) { + context.log("licenseAutoAllocationManualRetry failed."); + context.error(e); + return { + status: HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, + body: "licenseAutoAllocationManualRetry failed.", + }; + } finally { + context.log("[OUT]licenseAutoAllocationManualRetry"); + } +} + +// httpトリガは定時処理licenseAutoAllocationの異常時の手動再実行用 +app.http("licenseAutoAllocationManualRetry", { + methods: [HTTP_METHODS.POST as HttpMethod], + authLevel: "function", + handler: licenseAutoAllocationManualRetry, +}); diff --git a/dictation_function/src/test/licenseAutoAllocation.spec.ts b/dictation_function/src/test/licenseAutoAllocation.spec.ts index a6f790c..0e1bb92 100644 --- a/dictation_function/src/test/licenseAutoAllocation.spec.ts +++ b/dictation_function/src/test/licenseAutoAllocation.spec.ts @@ -1,20 +1,11 @@ import { DataSource } from "typeorm"; import { licenseAutoAllocationProcessing } from "../functions/licenseAutoAllocation"; import { - ADMIN_ROLES, LICENSE_ALLOCATED_STATUS, LICENSE_TYPE, - SWITCH_FROM_TYPE, - TIERS, USER_ROLES, } from "../constants"; -import { - DateWithDayEndTime, - DateWithNextDayEndTime, - DateWithZeroTime, - ExpirationThresholdDate, - NewAllocatedLicenseExpirationDate, -} from "../common/types/types"; +import { DateWithDayEndTime } from "../common/types/types"; import { makeTestAccount, createLicense, @@ -25,7 +16,7 @@ import { import * as dotenv from "dotenv"; import { InvocationContext } from "@azure/functions"; -describe("licenseAlert", () => { +describe("licenseAutoAllocation", () => { dotenv.config({ path: ".env" }); dotenv.config({ path: ".env.local", override: true }); let source: DataSource | null = null; @@ -51,7 +42,6 @@ describe("licenseAlert", () => { const context = new InvocationContext(); const currentDateEndTime = new DateWithDayEndTime(); - console.log(currentDateEndTime); // アカウント const account1 = await makeTestAccount( @@ -249,12 +239,216 @@ describe("licenseAlert", () => { ); }); + it("有効期限が指定日のライセンスが自動更新されること(リトライ用)", async () => { + if (!source) fail(); + const context = new InvocationContext(); + // 11/22の日付を作成 + const dateSeptember22 = new Date(); + dateSeptember22.setMonth(11); + dateSeptember22.setDate(22); + dateSeptember22.setHours(23, 59, 59); + const currentDateEndTime = new DateWithDayEndTime(dateSeptember22); + + // アカウント + const account1 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}` } + ); + const account2 = await makeTestAccount( + source, + { tier: 5 }, + { role: `${USER_ROLES.NONE}` } + ); + + // 更新対象のユーザー(3role分) + const user1 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + }); + const user2 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.AUTHOR}`, + }); + const user3 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.TYPIST}`, + }); + + // 更新対象ではないユーザー(まだ有効期限が残っている) + const user4 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + }); + + // 更新対象ではないユーザー(auto_renewがfalse) + const user5 = await makeTestUser(source, { + account_id: account1.account.id, + role: `${USER_ROLES.NONE}`, + auto_renew: false, + }); + + // 割り当て済みで有効期限が12/31のライセンス + await createLicense( + source, + 1, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user1.id, + null, + null, + null + ); + await createLicense( + source, + 2, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user2.id, + null, + null, + null + ); + await createLicense( + source, + 3, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user3.id, + null, + null, + null + ); + await createLicense( + source, + 20, + currentDateEndTime, + account2.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + account2.admin.id, + null, + null, + null + ); + await createLicense( + source, + 5, + currentDateEndTime, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user5.id, + null, + null, + null + ); + + // 割り当て済みの更新対象ではないライセンス + const nextDate = new Date(); + nextDate.setDate(dateSeptember22.getDate() + 1); + nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + nextDate.setMilliseconds(0); + await createLicense( + source, + 4, + nextDate, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + user4.id, + null, + null, + null + ); + + // 有効期限が先の未割当ライセンスを作成 + // idが100,101のものは有効期限が当日、翌日なので自動割り当て対象外 + // idが102のものから割り当てられる + for (let i = 0; i < 10; i++) { + const date = new Date(); + date.setDate(dateSeptember22.getDate() + i); + date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + date.setMilliseconds(0); + await createLicense( + source, + i + 100, + date, + account1.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null + ); + } + + const dateMarch31 = new Date(); + dateMarch31.setMonth(12); + dateMarch31.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 + dateMarch31.setMilliseconds(0); + await createLicense( + source, + 200, + dateMarch31, + account2.account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.REUSABLE, + null, + null, + null, + null + ); + + await licenseAutoAllocationProcessing(context, source, dateSeptember22); + const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id); + const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id); + const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id); + const user4Allocated = await selectLicenseByAllocatedUser(source, user4.id); + const user5Allocated = await selectLicenseByAllocatedUser(source, user5.id); + const admin2Allocated = await selectLicenseByAllocatedUser( + source, + account2.admin.id + ); + const licenseAllocationHistory = await selectLicenseAllocationHistory( + source, + user1.id, + 104 + ); + // Author、Typist、Noneの優先順位で割り当てられていることを確認 + expect(user1Allocated.license?.id).toBe(104); + expect(user2Allocated.license?.id).toBe(102); + expect(user3Allocated.license?.id).toBe(103); + // 有効期限がまだあるので、ライセンスが更新されていないことを確認 + expect(user4Allocated.license?.id).toBe(4); + // auto_renewがfalseなので、ライセンスが更新されていないことを確認 + expect(user5Allocated.license?.id).toBe(5); + // 複数アカウント分の処理が正常に行われていることの確認 + expect(admin2Allocated.license?.id).toBe(200); + + // ライセンス割り当て履歴テーブルが更新されていることを確認 + expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe( + user1.id + ); + expect( + licenseAllocationHistory.licenseAllocationHistory?.is_allocated + ).toBe(true); + expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe( + account1.account.id + ); + }); it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => { if (!source) fail(); const context = new InvocationContext(); const currentDateEndTime = new DateWithDayEndTime(); - console.log(currentDateEndTime); // アカウント const account1 = await makeTestAccount( @@ -330,7 +524,6 @@ describe("licenseAlert", () => { const context = new InvocationContext(); const currentDateEndTime = new DateWithDayEndTime(); - console.log(currentDateEndTime); // アカウント const account1 = await makeTestAccount(