From 50d34d6ae32664aec0f7e125c51e6ee38fa1efb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E7=AB=A5=E5=B4=87?= <792998983@qq.com> Date: Thu, 2 Nov 2023 21:45:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(mis):=20=E5=A2=9E=E5=8A=A0=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E5=90=8C=E6=AD=A5scow=E4=B8=8E=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E7=94=A8=E6=88=B7=E5=B0=81=E9=94=81=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=8F=8A=E8=B4=A6=E6=88=B7=E5=B0=81=E9=94=81/?= =?UTF-8?q?=E8=A7=A3=E5=B0=81=E7=8A=B6=E6=80=81=E7=9A=84=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20(#913)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 做了什么: 增加同步 scow 与适配器中用户封锁、账户封锁/解封状态的功能 1、增加同步 scow 与适配器中用户封锁、账户封锁/解封状态的接口,如果有部分账户/用户同步失败,跳过继续执行,接口返回失败账户/用户的信息 2、增加获取同步 scow 与适配器中用户封锁状态、账户封锁/解封状态的配置信息的接口 3、增加设置同步 scow 与适配器中用户封锁状态、账户封锁/解封状态的配置启动/关闭的接口 将原有接口updateBlockStatus置为deprecated 页面如下: ![image](https://github.com/PKUHPC/SCOW/assets/25954437/990dddbb-1786-4f4f-8403-47ee82f65e6b) 操作验证如下:先修改数据库,使一个账户在scow状态变为封锁,但用户仍旧能通过他它来提交作业。同步封锁状态后,提交的作业就无法运行了 ![12](https://github.com/PKUHPC/SCOW/assets/25954437/335c8ee4-c277-4a9e-ae88-fc778ac660b1) 同理,只修改scow数据库中的账户的状态为解封,该作业未运行,同步后,即可运行: ![13](https://github.com/PKUHPC/SCOW/assets/25954437/ca399e0d-f19d-4440-80e6-bf51a97f1698) --- .changeset/little-mirrors-argue.md | 5 + .changeset/silver-masks-repeat.md | 6 + .changeset/warm-wasps-relax.md | 7 ++ apps/cli/assets/config/mis.yaml | 7 ++ apps/mis-server/src/app.ts | 4 +- apps/mis-server/src/bl/block.ts | 108 ++++++++++++++---- apps/mis-server/src/plugins/index.ts | 4 +- .../mis-server/src/plugins/syncBlockStatus.ts | 85 ++++++++++++++ apps/mis-server/src/services/admin.ts | 25 ++++ apps/mis-server/src/tasks/syncBlockStatus.ts | 38 ++++++ .../tests/admin/updateBlockStatus.test.ts | 43 ++++++- apps/mis-web/src/apis/api.mock.ts | 11 +- apps/mis-web/src/apis/api.ts | 10 +- apps/mis-web/src/i18n/en.ts | 38 +++--- apps/mis-web/src/i18n/zh_cn.ts | 37 +++--- .../admin/systemDebug/slurmBlockStatus.tsx | 91 +++++++++------ .../synchronize/getSyncBlockStateInfo.ts | 51 +++++++++ .../setSynchronizeState.ts} | 16 ++- .../api/admin/synchronize/syncBlockStatus.ts | 47 ++++++++ docs/docs/deploy/config/mis/intro.md | 7 ++ .../deploy/config/mis/schedulers/slurm.md | 10 -- .../docs/deploy/config/mis/schedulers/sync.md | 25 ++++ libs/config/src/mis.ts | 4 + protos/server/admin.proto | 34 +++++- 24 files changed, 585 insertions(+), 128 deletions(-) create mode 100644 .changeset/little-mirrors-argue.md create mode 100644 .changeset/silver-masks-repeat.md create mode 100644 .changeset/warm-wasps-relax.md create mode 100644 apps/mis-server/src/plugins/syncBlockStatus.ts create mode 100644 apps/mis-server/src/tasks/syncBlockStatus.ts create mode 100644 apps/mis-web/src/pages/api/admin/synchronize/getSyncBlockStateInfo.ts rename apps/mis-web/src/pages/api/admin/{updateBlockStatus.ts => synchronize/setSynchronizeState.ts} (78%) create mode 100644 apps/mis-web/src/pages/api/admin/synchronize/syncBlockStatus.ts create mode 100644 docs/docs/deploy/config/mis/schedulers/sync.md diff --git a/.changeset/little-mirrors-argue.md b/.changeset/little-mirrors-argue.md new file mode 100644 index 0000000000..186db83c7c --- /dev/null +++ b/.changeset/little-mirrors-argue.md @@ -0,0 +1,5 @@ +--- +"@scow/config": minor +--- + +增加scow定时同步调度器用户封锁、账户封锁/解封状态的配置,可配置同步周期、是否启动 diff --git a/.changeset/silver-masks-repeat.md b/.changeset/silver-masks-repeat.md new file mode 100644 index 0000000000..f8fa835365 --- /dev/null +++ b/.changeset/silver-masks-repeat.md @@ -0,0 +1,6 @@ +--- +"@scow/mis-server": minor +"@scow/mis-web": minor +--- + +增加scow定时同步调度器用户封锁、账户封锁/解封状态的功能 diff --git a/.changeset/warm-wasps-relax.md b/.changeset/warm-wasps-relax.md new file mode 100644 index 0000000000..d7db562a00 --- /dev/null +++ b/.changeset/warm-wasps-relax.md @@ -0,0 +1,7 @@ +--- +"@scow/grpc-api": minor +--- +增加scow定时同步调度器用户封锁、账户封锁/解封状态的接口,返回失败的账户、用户的信息 +增加获取scow定时同步调度器用户封锁、账户封锁/解封状态配置信息的接口 +增加设置scow定时同步调度器用户封锁、账户封锁/解封状态配置启动/关闭的接口 +后续版本版本将会删除updateBlockStatus接口 \ No newline at end of file diff --git a/apps/cli/assets/config/mis.yaml b/apps/cli/assets/config/mis.yaml index f7a6a084b4..10a06b21e2 100644 --- a/apps/cli/assets/config/mis.yaml +++ b/apps/cli/assets/config/mis.yaml @@ -15,6 +15,13 @@ fetchJobs: # 周期的cron表达式 cron: "10 */10 * * * *" +# 周期性同步scow与调度器(如slurm)账户用户封锁状态的配置 +periodicSyncUserAccountBlockStatus: + # 是否开启 + enabled: true + # 周期的cron表达式 + cron: "0 4 * * *" + # 预定义的充值类型 predefinedChargingTypes: - 测试 diff --git a/apps/mis-server/src/app.ts b/apps/mis-server/src/app.ts index bcbc25e236..b8c5f138c5 100644 --- a/apps/mis-server/src/app.ts +++ b/apps/mis-server/src/app.ts @@ -13,7 +13,6 @@ import { Server } from "@ddadaal/tsgrpc-server"; import { omitConfigSpec } from "@scow/lib-config"; import { readVersionFile } from "@scow/utils/build/version"; -import { updateBlockStatusInSlurm } from "src/bl/block"; import { config } from "src/config/env"; import { plugins } from "src/plugins"; import { accountServiceServer } from "src/services/account"; @@ -53,8 +52,7 @@ export async function createServer() { await server.register(configServiceServer); await server.register(misConfigServiceServer); - const em = server.ext.orm.em.fork(); - await updateBlockStatusInSlurm(em, server.ext.clusters, server.logger); + await server.ext.syncBlockStatus.sync(); return server; } diff --git a/apps/mis-server/src/bl/block.ts b/apps/mis-server/src/bl/block.ts index 38608cabe8..b6cacd3995 100644 --- a/apps/mis-server/src/bl/block.ts +++ b/apps/mis-server/src/bl/block.ts @@ -14,62 +14,124 @@ import { asyncClientCall } from "@ddadaal/tsgrpc-client"; import { Logger } from "@ddadaal/tsgrpc-server"; import { Loaded } from "@mikro-orm/core"; import { MySqlDriver, SqlEntityManager } from "@mikro-orm/mysql"; +import { BlockedFailedUserAccount } from "@scow/protos/build/server/admin"; import { Account } from "src/entities/Account"; -import { SystemState } from "src/entities/SystemState"; import { UserAccount, UserStatus } from "src/entities/UserAccount"; import { ClusterPlugin } from "src/plugins/clusters"; import { callHook } from "src/plugins/hookClient"; - /** * Update block status of accounts and users in the slurm. * If it is whitelisted, it doesn't block. * - * @returns Updated number of blocked accounts and users + * @returns Block successful and failed accounts and users **/ export async function updateBlockStatusInSlurm( em: SqlEntityManager, clusterPlugin: ClusterPlugin["clusters"], logger: Logger, ) { + const blockedAccounts: string[] = []; + const blockedFailedAccounts: string[] = []; const accounts = await em.find(Account, { blocked: true }); + for (const account of accounts) { if (account.whitelist) { continue; } - await clusterPlugin.callOnAll(logger, async (client) => - await asyncClientCall(client.account, "blockAccount", { - accountName: account.accountName, - }), - ); + + try { + await clusterPlugin.callOnAll(logger, async (client) => + await asyncClientCall(client.account, "blockAccount", { + accountName: account.accountName, + }), + ); + blockedAccounts.push(account.accountName); + } catch (error) { + blockedFailedAccounts.push(account.accountName); + } } + const blockedUserAccounts: [string, string][] = []; + const blockedFailedUserAccounts: BlockedFailedUserAccount[] = []; const userAccounts = await em.find(UserAccount, { status: UserStatus.BLOCKED, }, { populate: ["user", "account"]}); + for (const ua of userAccounts) { - await clusterPlugin.callOnAll(logger, async (client) => - await asyncClientCall(client.user, "blockUserInAccount", { - accountName: ua.account.getProperty("accountName"), - userId: ua.user.getProperty("userId"), - }), - ); + try { + await clusterPlugin.callOnAll(logger, async (client) => + await asyncClientCall(client.user, "blockUserInAccount", { + accountName: ua.account.$.accountName, + userId: ua.user.$.userId, + }), + ); + blockedUserAccounts.push([ua.user.getProperty("userId"), ua.account.getProperty("accountName")]); + } catch (error) { + blockedFailedUserAccounts.push({ + userId: ua.user.$.userId, + accountName: ua.account.$.accountName, + }); + } } - const updateBlockTime = await em.upsert(SystemState, { - key: SystemState.KEYS.UPDATE_SLURM_BLOCK_STATUS, - value: new Date().toISOString(), + + logger.info("Updated block status in slurm of the following accounts: %o", blockedAccounts); + logger.info("Updated block status failed in slurm of the following accounts: %o", blockedFailedAccounts); + + logger.info("Updated block status in slurm of the following user account: %o", blockedUserAccounts); + logger.info("Updated block status failed in slurm of the following user account: %o", blockedFailedUserAccounts); + + return { + blockedAccounts, + blockedFailedAccounts, + blockedUserAccounts, + blockedFailedUserAccounts, + }; + +} + + +/** + * Update unblock status of accounts in the slurm. + * In order to ensure the stability of the service, serial is selected here. + * + * @returns Unblocked Block successful and failed accounts + **/ +export async function updateUnblockStatusInSlurm( + em: SqlEntityManager, clusterPlugin: ClusterPlugin["clusters"], logger: Logger, +) { + const accounts = await em.find(Account, { + $or: [ + { blocked: false }, + { whitelist: { $ne: null } }, + ], }); - await em.persistAndFlush(updateBlockTime); - logger.info("Updated block status in slurm of the following accounts: %o", accounts.map((x) => x.accountName)); - logger.info("Updated block status in slurm of the following user account: %o", - userAccounts.map((x) => [x.user.getProperty("userId"), x.account.getProperty("accountName")])); + const unblockedAccounts: string[] = []; + const unblockedFailedAccounts: string[] = []; + + for (const account of accounts) { + try { + await clusterPlugin.callOnAll(logger, async (client) => + await asyncClientCall(client.account, "unblockAccount", { + accountName: account.accountName, + }), + ); + unblockedAccounts.push(account.accountName); + } catch (error) { + unblockedFailedAccounts.push(account.accountName); + } + } + + logger.info("Updated unblock status in slurm of the following accounts: %o", unblockedAccounts); + logger.info("Updated unblock status failed in slurm of the following accounts: %o", unblockedFailedAccounts); return { - blockedAccounts: accounts.map((x) => x.id), - blockedUserAccounts: userAccounts.map((x) => x.id), + unblockedAccounts, + unblockedFailedAccounts, }; } + /** * Blocks the account in the slurm. * If it is whitelisted, it doesn't block. diff --git a/apps/mis-server/src/plugins/index.ts b/apps/mis-server/src/plugins/index.ts index 57f770fdf8..a7808e4b24 100644 --- a/apps/mis-server/src/plugins/index.ts +++ b/apps/mis-server/src/plugins/index.ts @@ -25,9 +25,10 @@ import { ClusterPlugin, clustersPlugin } from "src/plugins/clusters"; import { FetchPlugin, fetchPlugin } from "src/plugins/fetch"; import { ormPlugin } from "src/plugins/orm"; import { PricePlugin, pricePlugin } from "src/plugins/price"; +import { SyncBlockStatusPlugin, syncBlockStatusPlugin } from "src/plugins/syncBlockStatus"; declare module "@ddadaal/tsgrpc-server" { - interface Extensions extends ClusterPlugin, PricePlugin, FetchPlugin { + interface Extensions extends ClusterPlugin, PricePlugin, FetchPlugin, SyncBlockStatusPlugin { orm: MikroORM; capabilities: Capabilities; } @@ -43,6 +44,7 @@ export const plugins = [ pricePlugin, fetchPlugin, authServicePlugin, + syncBlockStatusPlugin, ]; if (commonConfig.scowApi) { diff --git a/apps/mis-server/src/plugins/syncBlockStatus.ts b/apps/mis-server/src/plugins/syncBlockStatus.ts new file mode 100644 index 0000000000..a24293510c --- /dev/null +++ b/apps/mis-server/src/plugins/syncBlockStatus.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { plugin } from "@ddadaal/tsgrpc-server"; +import { SyncBlockStatusResponse } from "@scow/protos/build/server/admin"; +import cron from "node-cron"; +import { misConfig } from "src/config/mis"; +import { lastSyncTime, synchronizeBlockStatus } from "src/tasks/syncBlockStatus"; + +export interface SyncBlockStatusPlugin { + syncBlockStatus: { + started: () => boolean; + start: () => void; + stop: () => void; + schedule: string; + lastSyncTime: () => Date | null; + sync: () => Promise; + } +} + +export const syncBlockStatusPlugin = plugin(async (f) => { + const synchronizeCron = misConfig.periodicSyncUserAccountBlockStatus?.cron ?? "0 4 * * *"; + let synchronizeStarted = !!misConfig.periodicSyncUserAccountBlockStatus?.enabled; + let synchronizeIsRunning = false; + + const logger = f.logger.child({ plugin: "syncBlockStatus" }); + logger.info("misConfig.periodicSyncStatus?.cron: %s", misConfig.periodicSyncUserAccountBlockStatus?.cron); + + const trigger = () => { + if (synchronizeIsRunning) return; + + synchronizeIsRunning = true; + return synchronizeBlockStatus(f.ext.orm.em.fork(), logger, f.ext).finally(() => { synchronizeIsRunning = false; }); + }; + + const task = cron.schedule( + synchronizeCron, + trigger, + { + timezone: "Asia/Shanghai", + scheduled: misConfig.periodicSyncUserAccountBlockStatus?.enabled, + }, + ); + + logger.info("Sync block status started."); + + f.addCloseHook(() => { + task.stop(); + logger.info("Sync block status stopped."); + }); + + f.addExtension("syncBlockStatus", { + started: () => synchronizeStarted, + start: () => { + if (synchronizeStarted) { + logger.info("Sync is requested to start but already started"); + } else { + task.start(); + synchronizeStarted = true; + logger.info("Sync started"); + } + }, + stop: () => { + if (!synchronizeStarted) { + logger.info("Sync is requested to stop but already stopped"); + } else { + task.stop(); + synchronizeStarted = false; + logger.info("Sync stopped"); + } + }, + schedule: synchronizeCron, + lastSyncTime: () => lastSyncTime, + sync: trigger, + }); +}); diff --git a/apps/mis-server/src/services/admin.ts b/apps/mis-server/src/services/admin.ts index 6f0918536e..fff63abc8b 100644 --- a/apps/mis-server/src/services/admin.ts +++ b/apps/mis-server/src/services/admin.ts @@ -195,6 +195,31 @@ export const adminServiceServer = plugin((server) => { return [reply ? reply : { newJobsCount: 0 }]; }, + getSyncBlockStatusInfo: async () => { + return [{ + syncStarted: server.ext.syncBlockStatus.started(), + schedule: server.ext.syncBlockStatus.schedule, + lastSyncTime: server.ext.syncBlockStatus.lastSyncTime()?.toISOString() ?? undefined, + }]; + }, + + setSyncBlockStatusState: async ({ request }) => { + const { started } = request; + + if (started) { + server.ext.syncBlockStatus.start(); + } else { + server.ext.syncBlockStatus.stop(); + } + + return [{}]; + }, + + syncBlockStatus: async () => { + const reply = await server.ext.syncBlockStatus.sync(); + return [reply]; + }, + updateBlockStatus: async ({ em, logger }) => { await updateBlockStatusInSlurm(em, server.ext.clusters, logger); return [{}]; diff --git a/apps/mis-server/src/tasks/syncBlockStatus.ts b/apps/mis-server/src/tasks/syncBlockStatus.ts new file mode 100644 index 0000000000..2f4358b64a --- /dev/null +++ b/apps/mis-server/src/tasks/syncBlockStatus.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { Logger } from "@ddadaal/tsgrpc-server"; +import { MySqlDriver, SqlEntityManager } from "@mikro-orm/mysql"; +import { updateBlockStatusInSlurm, updateUnblockStatusInSlurm } from "src/bl/block"; +import { SystemState } from "src/entities/SystemState"; +import { ClusterPlugin } from "src/plugins/clusters"; + +export let lastSyncTime: Date | null = null; + +export async function synchronizeBlockStatus( + em: SqlEntityManager, + logger: Logger, + clusterPlugin: ClusterPlugin, +) { + const { blockedFailedAccounts, blockedFailedUserAccounts } = + await updateBlockStatusInSlurm(em, clusterPlugin.clusters, logger); + const { unblockedFailedAccounts } = await updateUnblockStatusInSlurm(em, clusterPlugin.clusters, logger); + + lastSyncTime = new Date(); + + const updateBlockTime = await em.upsert(SystemState, { + key: SystemState.KEYS.UPDATE_SLURM_BLOCK_STATUS, + value: new Date().toISOString(), + }); + await em.persistAndFlush(updateBlockTime); + return { blockedFailedAccounts, blockedFailedUserAccounts, unblockedFailedAccounts }; +} diff --git a/apps/mis-server/tests/admin/updateBlockStatus.test.ts b/apps/mis-server/tests/admin/updateBlockStatus.test.ts index 68f2eb0052..9c6b8331fa 100644 --- a/apps/mis-server/tests/admin/updateBlockStatus.test.ts +++ b/apps/mis-server/tests/admin/updateBlockStatus.test.ts @@ -14,8 +14,10 @@ import { asyncClientCall } from "@ddadaal/tsgrpc-client"; import { Server } from "@ddadaal/tsgrpc-server"; import { ChannelCredentials } from "@grpc/grpc-js"; import { AccountServiceClient } from "@scow/protos/build/server/account"; +import { AdminServiceClient } from "@scow/protos/build/server/admin"; import { createServer } from "src/app"; import { updateBlockStatusInSlurm } from "src/bl/block"; +import { misConfig } from "src/config/mis"; import { SystemState } from "src/entities/SystemState"; import { BlockedData, insertBlockedData } from "tests/data/data"; import { dropDatabase } from "tests/data/helpers"; @@ -49,8 +51,10 @@ it("update block status", async () => { const blockedData = await updateBlockStatusInSlurm( server.ext.orm.em.fork(), server.ext.clusters, server.logger); - expect(blockedData.blockedAccounts).toEqual([data.blockedAccountB.id]); - expect(blockedData.blockedUserAccounts).toEqual([data.uaAA.id]); + expect(blockedData.blockedAccounts).toEqual([data.blockedAccountB.accountName]); + expect(blockedData.blockedUserAccounts).toEqual([ + [data.uaAA.user.getProperty("userId"), data.uaAA.account.getProperty("accountName")], + ]); }); it("update block status with whitelist accounts", async () => { @@ -68,6 +72,41 @@ it("update block status with whitelist accounts", async () => { expect(blockedData.blockedAccounts).not.toContain([data.blockedAccountB.id]); }); +it("gets current sync block status info", async () => { + const client = new AdminServiceClient(server.serverAddress, ChannelCredentials.createInsecure()); + const info = await asyncClientCall(client, "getSyncBlockStatusInfo", {}); + + expect(info.syncStarted).toEqual(misConfig.periodicSyncUserAccountBlockStatus?.enabled); + expect(info.schedule).toEqual(misConfig.periodicSyncUserAccountBlockStatus?.cron ?? "0 4 * * *"); + +}); + +it("sync unblock and block account", async () => { + const client = new AdminServiceClient(server.serverAddress, ChannelCredentials.createInsecure()); + const info = await asyncClientCall(client, "syncBlockStatus", { }); + + expect(info.blockedFailedAccounts).not.toContain(data.blockedAccountB.accountName); + expect(info.blockedFailedUserAccounts).not.toContain([ + [data.uaAA.user.getProperty("userId"), data.uaAA.account.getProperty("accountName")], + ]); + expect(info.unblockedFailedAccounts).not.toContain(data.unblockedAccountA.accountName); +}); + + +it("starts and stops sync block status ", async () => { + const client = new AdminServiceClient(server.serverAddress, ChannelCredentials.createInsecure()); + await asyncClientCall(client, "setSyncBlockStatusState", { started: false }); + + let info = await asyncClientCall(client, "getSyncBlockStatusInfo", { }); + + expect(info.syncStarted).toBeFalse(); + + await asyncClientCall(client, "setSyncBlockStatusState", { started: true }); + + info = await asyncClientCall(client, "getSyncBlockStatusInfo", {}); + + expect(info.syncStarted).toBeTrue(); +}); diff --git a/apps/mis-web/src/apis/api.mock.ts b/apps/mis-web/src/apis/api.mock.ts index 97217fb348..dc3ca9c9b3 100644 --- a/apps/mis-web/src/apis/api.mock.ts +++ b/apps/mis-web/src/apis/api.mock.ts @@ -378,7 +378,16 @@ export const mockApi: MockApi = { unblockUserInAccount: async () => ({ executed: true }), blockAccount: async () => ({ executed: true }), unblockAccount: async () => ({ executed: true }), - updateBlockStatus: async () => null, + syncBlockStatus: async () => ({ + blockedFailedAccounts: [], + unblockedFailedAccounts:[], + blockedFailedUserAccounts: [], + }), + getSyncBlockStatusJobInfo: async () => ({ + syncStarted: false, + schedule: "0 4 * * *", + }), + setSyncBlockStatusState: async () => null, removeUserFromAccount: async () => null, setAdmin: async () => ({ executed: true }), unsetAdmin: async () => ({ executed: false }), diff --git a/apps/mis-web/src/apis/api.ts b/apps/mis-web/src/apis/api.ts index d5104a8c8a..247d8329cc 100644 --- a/apps/mis-web/src/apis/api.ts +++ b/apps/mis-web/src/apis/api.ts @@ -31,9 +31,11 @@ import type { ImportUsersSchema } from "src/pages/api/admin/importUsers"; import type { QueryStorageQuotaSchema } from "src/pages/api/admin/queryStorageQuota"; import type { SetPlatformRoleSchema } from "src/pages/api/admin/setPlatformRole"; import type { SetTenantRoleSchema } from "src/pages/api/admin/setTenantRole"; +import type { GetSyncBlockStatusJobInfoSchema } from "src/pages/api/admin/synchronize/getSyncBlockStateInfo"; +import type { SetSyncBlockStatusStateSchema } from "src/pages/api/admin/synchronize/setSynchronizeState"; +import type { SyncBlockStatusSchema } from "src/pages/api/admin/synchronize/syncBlockStatus"; import type { UnsetPlatformRoleSchema } from "src/pages/api/admin/unsetPlatformRole"; import type { UnsetTenantRoleSchema } from "src/pages/api/admin/unsetTenantRole"; -import type { UpdateBlockStatusSchema } from "src/pages/api/admin/updateBlockStatus"; import type { AuthCallbackSchema } from "src/pages/api/auth/callback"; import type { LogoutSchema } from "src/pages/api/auth/logout"; import type { ValidateTokenSchema } from "src/pages/api/auth/validateToken"; @@ -104,9 +106,11 @@ export const api = { queryStorageQuota: apiClient.fromTypeboxRoute("GET", "/api/admin/queryStorageQuota"), setPlatformRole: apiClient.fromTypeboxRoute("PUT", "/api/admin/setPlatformRole"), setTenantRole: apiClient.fromTypeboxRoute("PUT", "/api/admin/setTenantRole"), + getSyncBlockStatusJobInfo: apiClient.fromTypeboxRoute("GET", "/api/admin/synchronize/getSyncBlockStateInfo"), + setSyncBlockStatusState: apiClient.fromTypeboxRoute("POST", "/api/admin/synchronize/setSynchronizeState"), + syncBlockStatus: apiClient.fromTypeboxRoute("PUT", "/api/admin/synchronize/syncBlockStatus"), unsetPlatformRole: apiClient.fromTypeboxRoute("PUT", "/api/admin/unsetPlatformRole"), unsetTenantRole: apiClient.fromTypeboxRoute("PUT", "/api/admin/unsetTenantRole"), - updateBlockStatus: apiClient.fromTypeboxRoute("PUT", "/api/admin/updateBlockStatus"), authCallback: apiClient.fromTypeboxRoute("GET", "/api/auth/callback"), logout: apiClient.fromTypeboxRoute("DELETE", "/api/auth/logout"), validateToken: apiClient.fromTypeboxRoute("GET", "/api/auth/validateToken"), @@ -124,8 +128,8 @@ export const api = { userExists: apiClient.fromTypeboxRoute("POST", "/api/init/userExists"), addBillingItem: apiClient.fromTypeboxRoute("POST", "/api/job/addBillingItem"), changeJobTimeLimit: apiClient.fromTypeboxRoute("PATCH", "/api/job/changeJobTimeLimit"), - getBillingItems: apiClient.fromTypeboxRoute("GET", "/api/job/getBillingItems"), getAvailableBillingTable: apiClient.fromTypeboxRoute("GET", "/api/job/getAvailableBillingTable"), + getBillingItems: apiClient.fromTypeboxRoute("GET", "/api/job/getBillingItems"), getJobByBiJobIndex: apiClient.fromTypeboxRoute("GET", "/api/job/getJobByBiJobIndex"), getMissingDefaultPriceItems: apiClient.fromTypeboxRoute("GET", "/api/job/getMissingDefaultPriceItems"), getJobInfo: apiClient.fromTypeboxRoute("GET", "/api/job/jobInfo"), diff --git a/apps/mis-web/src/i18n/en.ts b/apps/mis-web/src/i18n/en.ts index 82548542ed..f3a7057493 100644 --- a/apps/mis-web/src/i18n/en.ts +++ b/apps/mis-web/src/i18n/en.ts @@ -859,28 +859,22 @@ export default { systemDebug: { slurmBlockStatus: { syncUserAccountBlockingStatus: "Synchronize User Account Blocking Status", - alertInfo: "After the scheduler restarts, the blocking status of users between the cluster and SCOW " - + "may become unsynchronized. You can click 'Refresh Scheduler User Blocking Status' to " - + "manually refresh and synchronize all user statuses.", - slurmScheduler: "Slurm Scheduler", - slurmSchedulerMessage1: "If you are using the Slurm scheduler, due to technical limitations, " - + "when you run slurm.sh nodes and the slurm management node are not on the same node, " - + " blocked users, accounts, and user accounts will be unblocked after the slurm cluster restarts.", - slurmSchedulerMessage2: "SCOW will automatically refresh the slurm blocking status once " - + "when starting, but the slurm cluster may restart during SCOW operation, and SCOW cannot react " - + "to this temporarily.", - slurmSchedulerMessage3: "So, if you run slurm.sh nodes and the slurm management node are not " - + "on the same node, you need to manually execute the 'Refresh Scheduler User Blocking Status' " - + "function on this page after the slurm cluster restarts." - + " If slurm.sh nodes and the slurm management node are on the same node, you can ignore this function.", - otherScheduler: "Other Schedulers", - otherSchedulerMessage: "If you are using a scheduler other than Slurm, when the user blocking status " - + "is unsynchronized between the scheduler and SCOW, you can manually execute the " - + "'Refresh Scheduler User Blocking Status' function on this page.", - lastRunTime: "Last Run Time", - notBlocked: "Not Blocked", - refreshSuccess: "Refreshed Successfully", - refreshSchedulerUserBlockingStatus: "Refresh Scheduler User Blocking Status", + alertInfo: "SCOW will regularly synchronize the blocking status of accounts and users to the scheduler. " + + "You can click Sync Now to perform a manual synchronization.", + periodicSyncUserAccountBlockStatusInfo: "Periodically Synchronize Scheduler Account And User Blocked Status", + turnedOn: "Turned On", + paused: "Paused", + stopSync: "Stop Synchronization", + startSync: "Start Synchronization", + jobSyncCycle: "Block Status Synchronization Cycle", + lastSyncTime: "Last Run Time", + notSynced: "Not Synchronized", + syncSuccess: "Refreshed Successfully", + partialSyncSuccess: "Synchronization failed for some users/accounts:", + syncBlockedFailedAccount: "Accounts that failed to be synchronously blocked:", + syncUnblockedFailedAccount: "Accounts that failed to be synchronously unblocked:", + syncBlockedFailedUserAccount: "Synchronize the data of failed blocked users in the account:", + syncSchedulerBlockingStatusNow: "Refresh Scheduler User Blocking Status", }, fetchJobs: { jobInfoSync: "Job Information Synchronization", diff --git a/apps/mis-web/src/i18n/zh_cn.ts b/apps/mis-web/src/i18n/zh_cn.ts index 4df4214469..052ba16809 100644 --- a/apps/mis-web/src/i18n/zh_cn.ts +++ b/apps/mis-web/src/i18n/zh_cn.ts @@ -859,28 +859,21 @@ export default { systemDebug: { slurmBlockStatus: { syncUserAccountBlockingStatus: "用户账户封锁状态同步", - alertInfo: "在调度器重新启动后,集群与SCOW中用户的封锁状态可能出现不同步的情况,您可以点击刷新调度器用户封锁状态,手动刷新同步所有用户状态。", - - - slurmScheduler: "slurm调度器", - slurmSchedulerMessage1: "如果您使用的是slurm调度器,由于技术限制,当您运行slurm.sh节点和slurm管理节点并非同一节点时," + - "已封锁的用户、账户和用户账户将会在slurm集群重启后被解封。", - - slurmSchedulerMessage2: "SCOW在启动时将会自动刷新一次slurm封锁状态,但是slurm集群可能在SCOW运行时重启,SCOW暂时不能对这种情况做出反应。", - - - slurmSchedulerMessage3: "所以,如果您运行slurm.sh节点和slurm管理节点并非同一节点时,您需要在slurm集群重启后手动执行一下本页面的刷新调度器用户封锁状态的功能。" + - "如果slurm.sh节点和slurm管理节点为同一节点,您可以忽略本功能。", - - - otherScheduler: "其他调度器", - otherSchedulerMessage: "如果您使用的是slurm之外的调度器,在调度器和SCOW间用户封锁状态不同步时,可以手动执行一下本页面的刷新调度器用户封锁状态的功能。", - - - lastRunTime: "上次运行时间", - notBlocked: "未封锁过", - refreshSuccess: "刷新成功", - refreshSchedulerUserBlockingStatus: "刷新调度器用户封锁状态", + alertInfo: "SCOW会定期向调度器同步SCOW数据库中账户和用户的封锁状态,您可以点击立刻同步执行一次手动同步", + periodicSyncUserAccountBlockStatusInfo:"周期性同步调度器账户和用户的封锁状态", + turnedOn: "已开启", + paused: "已暂停", + stopSync: "停止同步", + startSync: "开始同步", + jobSyncCycle: "同步周期", + lastSyncTime: "上次同步时间", + notSynced: "未同步过", + syncSuccess: "同步成功", + partialSyncSuccess: "部分用户/账户同步失败:", + syncBlockedFailedAccount: "同步封锁失败的账户:", + syncUnblockedFailedAccount: "同步解封失败的账户:", + syncBlockedFailedUserAccount: "在账户中同步封锁用户失败的数据:", + syncSchedulerBlockingStatusNow: "立刻同步调度器账户和用户封锁状态", }, fetchJobs: { jobInfoSync: "作业信息同步", diff --git a/apps/mis-web/src/pages/admin/systemDebug/slurmBlockStatus.tsx b/apps/mis-web/src/pages/admin/systemDebug/slurmBlockStatus.tsx index 06fe555760..24b86ac7fb 100644 --- a/apps/mis-web/src/pages/admin/systemDebug/slurmBlockStatus.tsx +++ b/apps/mis-web/src/pages/admin/systemDebug/slurmBlockStatus.tsx @@ -11,9 +11,10 @@ */ import { formatDateTime } from "@scow/lib-web/build/utils/datetime"; -import { Alert, App, Collapse, Descriptions, Space, Spin } from "antd"; +import { Alert, App, Badge, Descriptions, Space, Spin } from "antd"; import { NextPage } from "next"; import { useState } from "react"; +import { useAsync } from "react-async"; import { api } from "src/apis"; import { requireAuth } from "src/auth/requireAuth"; import { DisabledA } from "src/components/DisabledA"; @@ -22,8 +23,7 @@ import { prefix, useI18nTranslateToString } from "src/i18n"; import { PlatformRole } from "src/models/User"; import { Head } from "src/utils/head"; -const { Panel } = Collapse; - +const promiseFn = async () => api.getSyncBlockStatusJobInfo({}); const p = prefix("page.admin.systemDebug.slurmBlockStatus."); export const SlurmBlockStatusPage: NextPage = requireAuth((u) => u.platformRoles.includes(PlatformRole.PLATFORM_ADMIN))( @@ -31,13 +31,14 @@ export const SlurmBlockStatusPage: NextPage = requireAuth((u) => u.platformRoles const t = useI18nTranslateToString(); - const isLoading = false; - const reload = () => {}; - const data = { lastRun: new Date().toISOString() }; + const { isLoading, data, reload } = useAsync({ + promiseFn, + }); const { message } = App.useApp(); - const [running, setRunning] = useState(false); + const [fetching, setFetching] = useState(false); + const [changingState, setChangingState] = useState(false); return (
@@ -55,44 +56,66 @@ export const SlurmBlockStatusPage: NextPage = requireAuth((u) => u.platformRoles )} /> - - -

- {t(p("slurmSchedulerMessage1"))}
- {t(p("slurmSchedulerMessage2"))}
- {t(p("slurmSchedulerMessage3"))} -

-
- -

- {t(p("otherSchedulerMessage"))} -

-
- -
- { data ? ( - + + + {data.syncStarted + ? + : + } + { + setChangingState(true); + api.setSyncBlockStatusState({ query: { started: !data.syncStarted } }) + .then(() => reload()) + .finally(() => setChangingState(false)); + }} + disabled={changingState} + > + {data.syncStarted ? t(p("stopSync")) : t(p("startSync"))} + + + + + {data.schedule} + + - {data.lastRun ? formatDateTime(data.lastRun) : t(p("notBlocked"))} + {data.lastSyncTime ? formatDateTime(data.lastSyncTime) : t(p("notSynced"))} { - setRunning(true); - await api.updateBlockStatus({}) - .then(() => { - message.success(t(p("refreshSuccess"))); + onClick={() => { + setFetching(true); + api.syncBlockStatus({}) + .then(({ blockedFailedAccounts, unblockedFailedAccounts, blockedFailedUserAccounts }) => { + if (!(blockedFailedAccounts.length || unblockedFailedAccounts.length + || blockedFailedUserAccounts.length)) { + message.success(t(p("syncSuccess"))); + } else { + let errorMessage = t(p("partialSyncSuccess")); + if (blockedFailedAccounts.length) { + errorMessage += t(p("syncBlockedFailedAccount")) + blockedFailedAccounts.join(","); + } + if (unblockedFailedAccounts.length) { + errorMessage += t(p("syncBlockedFailedAccount")) + unblockedFailedAccounts.join(","); + } + if (blockedFailedUserAccounts.length) { + errorMessage += t(p("syncBlockedFailedAccount")) + + JSON.stringify(blockedFailedUserAccounts); + } + message.error(errorMessage); + } + reload(); }) - .finally(() => setRunning(false)); - setRunning(false); + .finally(() => setFetching(false)); }} - disabled={running} + disabled={fetching} > - {t(p("refreshSchedulerUserBlockingStatus"))} + {t(p("syncSchedulerBlockingStatusNow"))} diff --git a/apps/mis-web/src/pages/api/admin/synchronize/getSyncBlockStateInfo.ts b/apps/mis-web/src/pages/api/admin/synchronize/getSyncBlockStateInfo.ts new file mode 100644 index 0000000000..a44730751b --- /dev/null +++ b/apps/mis-web/src/pages/api/admin/synchronize/getSyncBlockStateInfo.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime"; +import { asyncClientCall } from "@ddadaal/tsgrpc-client"; +import { AdminServiceClient } from "@scow/protos/build/server/admin"; +import { Static, Type } from "@sinclair/typebox"; +import { authenticate } from "src/auth/server"; +import { PlatformRole } from "src/models/User"; +import { getClient } from "src/utils/client"; + +// Cannot use GetSyncBlockStatusInfoResponse from protos +export const GetSyncBlockStatusInfoResponse = Type.Object({ + syncStarted: Type.Boolean(), + schedule: Type.String(), + lastSyncTime: Type.Optional(Type.String()), +}); + +export type GetSyncBlockStatusInfoResponse = Static; + +export const GetSyncBlockStatusJobInfoSchema = typeboxRouteSchema({ + method: "GET", + + responses: { + 200: GetSyncBlockStatusInfoResponse, + }, +}); +const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN)); + +export default typeboxRoute(GetSyncBlockStatusJobInfoSchema, + async (req, res) => { + + const info = await auth(req, res); + if (!info) { return; } + + const client = getClient(AdminServiceClient); + + const reply = await asyncClientCall(client, "getSyncBlockStatusInfo", {}); + + return { 200: reply }; + + }); diff --git a/apps/mis-web/src/pages/api/admin/updateBlockStatus.ts b/apps/mis-web/src/pages/api/admin/synchronize/setSynchronizeState.ts similarity index 78% rename from apps/mis-web/src/pages/api/admin/updateBlockStatus.ts rename to apps/mis-web/src/pages/api/admin/synchronize/setSynchronizeState.ts index 1947c9df0a..011bcb5a98 100644 --- a/apps/mis-web/src/pages/api/admin/updateBlockStatus.ts +++ b/apps/mis-web/src/pages/api/admin/synchronize/setSynchronizeState.ts @@ -18,16 +18,20 @@ import { authenticate } from "src/auth/server"; import { PlatformRole } from "src/models/User"; import { getClient } from "src/utils/client"; -export const UpdateBlockStatusSchema = typeboxRouteSchema({ - method: "PUT", +export const SetSyncBlockStatusStateSchema = typeboxRouteSchema({ + method: "POST", + + query: Type.Object({ + started: Type.Boolean(), + }), responses: { - 200: Type.Null(), + 204: Type.Null(), }, }); const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN)); -export default typeboxRoute(UpdateBlockStatusSchema, +export default typeboxRoute(SetSyncBlockStatusStateSchema, async (req, res) => { const info = await auth(req, res); @@ -35,6 +39,8 @@ export default typeboxRoute(UpdateBlockStatusSchema, const client = getClient(AdminServiceClient); - return await asyncClientCall(client, "updateBlockStatus", {}).then(() => ({ 200: null })); + await asyncClientCall(client, "setSyncBlockStatusState", { started: req.query.started }); + + return { 204: null }; }); diff --git a/apps/mis-web/src/pages/api/admin/synchronize/syncBlockStatus.ts b/apps/mis-web/src/pages/api/admin/synchronize/syncBlockStatus.ts new file mode 100644 index 0000000000..05f3427ab1 --- /dev/null +++ b/apps/mis-web/src/pages/api/admin/synchronize/syncBlockStatus.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime"; +import { asyncClientCall } from "@ddadaal/tsgrpc-client"; +import { AdminServiceClient } from "@scow/protos/build/server/admin"; +import { Type } from "@sinclair/typebox"; +import { authenticate } from "src/auth/server"; +import { PlatformRole } from "src/models/User"; +import { getClient } from "src/utils/client"; + +export const SyncBlockStatusSchema = typeboxRouteSchema({ + method: "PUT", + + responses: { + 200: Type.Object({ + blockedFailedAccounts: Type.Array(Type.String()), + blockedFailedUserAccounts: Type.Array(Type.Object({ + accountName: Type.String(), + userId: Type.String(), + })), + unblockedFailedAccounts: Type.Array(Type.String()), + }), + }, +}); +const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN)); + +export default typeboxRoute(SyncBlockStatusSchema, + async (req, res) => { + + const info = await auth(req, res); + if (!info) { return; } + + const client = getClient(AdminServiceClient); + + return await asyncClientCall(client, "syncBlockStatus", {}).then((x) => ({ 200: x })); + + }); diff --git a/docs/docs/deploy/config/mis/intro.md b/docs/docs/deploy/config/mis/intro.md index 9b32e893bb..5e8a621817 100644 --- a/docs/docs/deploy/config/mis/intro.md +++ b/docs/docs/deploy/config/mis/intro.md @@ -76,6 +76,13 @@ fetchJobs: # 周期的cron表达式 cron: "10 */10 * * * *" +# 周期性同步scow与调度器(如slurm)账户用户封锁状态的配置 +periodicSyncUserAccountBlockStatus: + # 是否开启 + enabled: true + # 周期的cron表达式 + cron: "0 4 * * *" + # 预定义的充值类型 predefinedChargingTypes: - 测试 diff --git a/docs/docs/deploy/config/mis/schedulers/slurm.md b/docs/docs/deploy/config/mis/schedulers/slurm.md index fed6180068..cfc42197d6 100644 --- a/docs/docs/deploy/config/mis/schedulers/slurm.md +++ b/docs/docs/deploy/config/mis/schedulers/slurm.md @@ -52,13 +52,3 @@ adapterUrl: localhost:8972 - SCOW中存在的用户或者账户时slurm中不存在 我们建议,部署好SCOW系统后,所有SCOW中支持的功能都从SCOW中操作。如果需要一些SCOW中不支持的操作,请完成操作后立即手动修改数据库,使SCOW和slurm的用户信息同步。 - -## 刷新调度器用户封锁状态 - -当运行slurm.sh节点和slurm管理节点并非同一节点时,已封锁的用户、账户和用户账户将会在slurm集群重启后被解封。 - -SCOW在启动时将会自动刷新一次slurm封锁状态,但是slurm集群可能在SCOW运行时重启,SCOW暂时不能对这种情况做出反应。 - -所以,如果运行slurm.sh节点和slurm管理节点并非同一节点时,您需要在slurm集群重启后,手动执行一下**平台调试**->**封锁状态同步**的**刷新调度器用户封锁状态**的功能。如果slurm.sh节点和slurm管理节点为同一节点,您可以忽略本功能。 - -如果您使用的是slurm之外的调度器,在调度器和SCOW间用户封锁状态不同步时,可以手动执行一下本页面的刷新调度器用户封锁状态的功能。 \ No newline at end of file diff --git a/docs/docs/deploy/config/mis/schedulers/sync.md b/docs/docs/deploy/config/mis/schedulers/sync.md new file mode 100644 index 0000000000..0e2b34e083 --- /dev/null +++ b/docs/docs/deploy/config/mis/schedulers/sync.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 2 +title: 同步封锁状态 +description: 同步调度器账户、用户封锁状态 +--- + +# 刷新调度器账户、用户封锁状态 + +由于已封锁的账户将会在slurm集群重启后被解封,且slurm集群可能在SCOW运行时重启,但并不会给scow发送信息,所以SCOW在启动时将会自动刷新一次slurm账户的封锁/解封,用户的封锁状态,同时默认在每天凌晨4点执行一次同步。 + +如果您对时效性有要求,可以在slurm(其它调度器也一样,此处以slurm为例)集群重启后,手动执行一下**平台调试**->**封锁状态同步**的**立刻同步调度器账户和用户封锁状态**的功能。 + +如果您不需要此功能,也可以自定义配置: + +在`config/mis.yaml`文件中,根据备注修改所需要的配置 + +```yaml title="config/mis.yaml" +# 周期性同步scow与调度器(如slurm)账户用户封锁状态的配置 +periodicSyncUserAccountBlockStatus: + # 是否开启 + enabled: true + # 周期的cron表达式 + cron: "0 4 * * *" + +``` diff --git a/libs/config/src/mis.ts b/libs/config/src/mis.ts index b9014a69df..92e24c05d3 100644 --- a/libs/config/src/mis.ts +++ b/libs/config/src/mis.ts @@ -78,6 +78,10 @@ export const MisConfigSchema = Type.Object({ }, { default: {} }), }, { default: {}, description: "获取作业功能的相关配置" }), + periodicSyncUserAccountBlockStatus: Type.Optional(Type.Object({ + enabled: Type.Boolean({ description:"是否默认打开", default: true }), + cron: Type.String({ description: "获取信息的周期的cron表达式", default: "0 4 * * *" }), + }, { default: {}, description: "用户账户封锁状态同步" })), jobChargeType: Type.String({ description: "对作业计费时,计费费用的的付款类型", default: "作业费用" }), changeJobPriceType: Type.String({ description: "修改作业费用时所使用的付款/充值类型", default: "作业费用更改" }), diff --git a/protos/server/admin.proto b/protos/server/admin.proto index b7750c8982..396dea0066 100644 --- a/protos/server/admin.proto +++ b/protos/server/admin.proto @@ -127,6 +127,32 @@ message FetchJobsResponse { uint32 new_jobs_count = 1; } +message GetSyncBlockStatusInfoRequest { +} +message GetSyncBlockStatusInfoResponse { + bool sync_started = 1; + string schedule = 2; + optional google.protobuf.Timestamp last_sync_time = 3; +} + +message SetSyncBlockStatusStateRequest { + bool started = 1; +} +message SetSyncBlockStatusStateResponse { +} + +message BlockedFailedUserAccount { + string account_name = 1; + string user_id= 2; +} +message SyncBlockStatusRequest{ +} +message SyncBlockStatusResponse{ + repeated string blocked_failed_accounts = 1; + repeated BlockedFailedUserAccount blocked_failed_user_accounts = 2; + repeated string unblocked_failed_accounts = 3; +} + message UpdateBlockStatusRequest{ } message UpdateBlockStatusResponse{ @@ -153,7 +179,11 @@ service AdminService { rpc GetFetchInfo(GetFetchInfoRequest) returns (GetFetchInfoResponse); rpc SetFetchState(SetFetchStateRequest) returns (SetFetchStateResponse); rpc FetchJobs(FetchJobsRequest) returns (FetchJobsResponse); - rpc UpdateBlockStatus(UpdateBlockStatusRequest) - returns (UpdateBlockStatusResponse); + rpc GetSyncBlockStatusInfo(GetSyncBlockStatusInfoRequest) returns (GetSyncBlockStatusInfoResponse); + rpc SetSyncBlockStatusState(SetSyncBlockStatusStateRequest) returns (SetSyncBlockStatusStateResponse); + rpc SyncBlockStatus(SyncBlockStatusRequest) returns (SyncBlockStatusResponse); + rpc UpdateBlockStatus(UpdateBlockStatusRequest) returns (UpdateBlockStatusResponse) { + option deprecated = true; + }; rpc GetAdminInfo(GetAdminInfoRequest) returns (GetAdminInfoResponse); }