Merged PR 648: ライセンス自動割り当て処理実装(リトライ対応)

## 概要
[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)
This commit is contained in:
maruyama.t 2023-12-25 04:33:12 +00:00
parent 1c18bf03b6
commit 9d8c736d92
4 changed files with 341 additions and 22 deletions

View File

@ -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,
};

View File

@ -20,15 +20,19 @@ import {
export async function licenseAutoAllocationProcessing(
context: InvocationContext,
datasource: DataSource
datasource: DataSource,
dateToTrigger?: Date
): Promise<void> {
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<License>,
accountId: number
accountId: number,
currentDateEndTime: DateWithDayEndTime
): Promise<License | undefined> {
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し、その後ループ終了

View File

@ -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<HttpResponseInit> {
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,
});

View File

@ -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が100101のものは有効期限が当日、翌日なので自動割り当て対象外
// 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(