Skip to content

Commit

Permalink
feat:db automatic database backup (#4377)
Browse files Browse the repository at this point in the history
Signed-off-by: jingyang <3161362058@qq.com>
  • Loading branch information
zjy365 committed Dec 5, 2023
1 parent f52b174 commit d518f58
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 34 deletions.
46 changes: 42 additions & 4 deletions frontend/providers/dbprovider/public/locales/en/common.json
Expand Up @@ -35,8 +35,6 @@
"Submit Error": "Submit Error",
"Export": "Export",
"Deploy": "Deploy",
"Edit Environment Variables": "Edit Environment Variables",
"DataBase Deployment": "DataBase Deployment",
"Creating": "Creating",
"Failed": "Failed",
"Update DataBase": "Update DataBase",
Expand Down Expand Up @@ -93,7 +91,6 @@
"Basic": "Basic",
"Restore Backup Tip": "Restoring the backup will create a new database, and you will need to provide the name of the new data, which cannot be the same as the current database.",
"Total Price": "Total",
"Redis HA": "HA",
"Monitor List": "Monitor List",
"No Data Available": "No Data Available",
"Resources": "Resources",
Expand Down Expand Up @@ -220,5 +217,46 @@
"stepOne": "# Set 'binlog_format' configuration to 'row'",
"stepTwo": "# Set 'binlog_row_image' configuration to 'full'"
}
}
},
"Auto": "Auto",
"Auto Backup": "Auto Backup",
"Backup Database": "Backup Database",
"Save": "Save",
"SaveTime": "SaveTime",
"Pod": "Pod",
"Monday": "Monday",
"Day": "Day",
"CronExpression": "Cron Expression",
"Confirm Restart": "Confirm Restart",
"Database Name": "Database Name",
"Delete Failed": "Delete Failed",
"Delete successful": "Delete successful",
"Friday": "Friday",
"Manual": "Manual",
"Delete Backup": "Delete Backup",
"Anticipated Price": "Anticipated Price",
"Restarting": "Restarting",
"Week": "Week",
"Start Minute": "Start Minute",
"Start Hour": "Start Hour",
"Start Backup": "Start Backup",
"Backup Name cannot empty": "Backup Name cannot empty",
"Manual Backup": "Manual Backup",
"Set auto backup successful": "Set auto backup successful",
"Saturday": "Saturday",
"Starting": "Starting",
"Sunday": "Sunday",
"Restore Success": "Restore Success",
"Restore Backup": "Restore Backup",
"Restore Database": "Restore Database",
"Pausing": "Pausing",
"Perday": "Perday",
"The backup task has been created successfully !": "The backup task has benn created successfully !",
"Thursday": "Thursday",
"Tuesday": "Tuesday",
"Updating": "Updating",
"Wednesday": "Wednesday",
"Failed to turn off automatic backup": "Failed to turn off automatic backup",
"Automatic backup is turned off": "Automatic backup is turned off",
"Are you sure you want to turn off automatic backup": "Are you sure you want to turn off automatic backup?"
}
7 changes: 6 additions & 1 deletion frontend/providers/dbprovider/public/locales/zh/common.json
Expand Up @@ -255,5 +255,10 @@
"stepOne": "# 设置 'binlog_format' 配置为 'row'",
"stepTwo": "# 设置 'binlog_row_image' 配置为'full'"
}
}
},
"Start Hour": "小时",
"Backup Name cannot empty": "备份名称不能为空",
"Failed to turn off automatic backup": "关闭自动备份失败",
"Automatic backup is turned off": "已关闭自动备份",
"Are you sure you want to turn off automatic backup": "确定关闭自动备份吗"
}
25 changes: 24 additions & 1 deletion frontend/providers/dbprovider/src/api/backup.ts
@@ -1,15 +1,38 @@
import { GET, POST, DELETE } from '@/services/request';
import type { Props as CreateBackupPros } from '@/pages/api/backup/create';
import { adaptBackup } from '@/utils/adapt';
import { adaptBackup, adaptBackupByCluster, adaptDBDetail } from '@/utils/adapt';
import { AutoBackupFormType } from '@/types/backup';
import type { Props as UpdatePolicyProps } from '@/pages/api/backup/updatePolicy';

/**
* Deprecated API: This endpoint is no longer supported.
*
* The new method for obtaining backup policies is by querying the 'cluster spec backup' endpoint
* for the specific database in the cluster.
*
* To update the auto-backup policy, use the PATCH operation on the 'cluster spec backup' resource.
*
* @param data - Object containing information about the database, including dbName and dbType.
* @returns {Promise<AutoBackupFormType>} - A promise resolving to the auto-backup configuration form.
*/
export const getBackupPolicy = (data: { dbName: string; dbType: string }) =>
GET<AutoBackupFormType>(`/api/backup/policy`, data);

export const createBackup = (data: CreateBackupPros) => POST('/api/backup/create', data);

export const getBackupList = (dbName: string) =>
GET('/api/backup/getBackupList', { dbName }).then((res) => res.map(adaptBackup));

export const deleteBackup = (backupName: string) =>
DELETE(`/api/backup/delBackup?backupName=${backupName}`);

export const updateBackupPolicy = (data: UpdatePolicyProps) =>
POST<AutoBackupFormType>(`/api/backup/updatePolicy`, data);

/**
* Retrieves backup policy by cluster.
* @param data An object containing dbName and dbType properties.
* @returns A Promise that resolves to the adapted backup policy data upon successful execution.
*/
export const getBackupPolicyByCluster = (data: { dbName: string; dbType: string }) =>
GET(`/api/getDBByName?name=${data.dbName}`).then(adaptBackupByCluster);
2 changes: 0 additions & 2 deletions frontend/providers/dbprovider/src/pages/api/backup/policy.ts
Expand Up @@ -50,5 +50,3 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}

export const getBackupPolicy = ({}: Props) => {};
Expand Up @@ -15,6 +15,8 @@ export type Props = {
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
const { dbName, dbType, patch } = req.body as Props;

console.log(dbName, dbType, patch);

if (!dbName || !dbType || !patch) {
jsonRes(res, {
code: 500,
Expand All @@ -23,30 +25,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
return;
}

const group = 'dataprotection.kubeblocks.io';
const group = 'apps.kubeblocks.io';
const version = 'v1alpha1';
const plural = 'backuppolicies';
const plural = 'clusters';

try {
const { k8sCustomObjects, namespace } = await getK8s({
kubeconfig: await authSession(req)
});

// get backup backupolicies.dataprotection.kubeblocks.io
await k8sCustomObjects.patchNamespacedCustomObject(
const result = await k8sCustomObjects.patchNamespacedCustomObject(
group,
version,
namespace,
plural,
`${dbName}-${DBBackupPolicyNameMap[dbType]}-backup-policy`,
dbName,
patch,
undefined,
undefined,
undefined,
{ headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH } }
);

jsonRes(res);
jsonRes(res, { data: result?.body });
} catch (err: any) {
jsonRes(res, {
code: 500,
Expand Down
Expand Up @@ -57,6 +57,13 @@ const BackupModal = ({
content: t('Manual Backup Tip'),
confirmText: 'Start Backup'
});

const { openConfirm: CloseAutoBackup, ConfirmChild: AutoBackupConfirmChild } = useConfirm({
title: 'Prompt',
content: t('Are you sure you want to turn off automatic backup'),
confirmText: 'Confirm'
});

const [refresh, setRefresh] = useState(false);
const [currentNav, setCurrentNav] = useState<`${NavEnum}`>(NavEnum.manual);
const {
Expand Down Expand Up @@ -162,12 +169,16 @@ const BackupModal = ({
})();

const patch = [
{ op: 'replace', path: '/spec/retention/ttl', value: `${data.saveTime}${data.saveType}` },
{ op: 'replace', path: '/spec/schedule/datafile/enable', value: data.start },
{
op: 'replace',
path: '/spec/schedule/datafile/cronExpression',
value: convertCronTime(cron, -8)
path: '/spec/backup',
value: {
enabled: data.start,
cronExpression: convertCronTime(cron, -8),
method: 'backupTool',
pitrEnabled: false,
retentionPeriod: `${data.saveTime}${data.saveType}`
}
}
];

Expand All @@ -193,6 +204,38 @@ const BackupModal = ({
}
});

const { mutate: onclickCloseAutoBackup } = useMutation({
mutationFn: async () => {
const patch = [
{
op: 'replace',
path: '/spec/backup/enabled',
value: false
}
];

return updateBackupPolicy({
dbName,
dbType,
patch
});
},
onSuccess() {
toast({
status: 'success',
title: t('Automatic backup is turned off')
});
refetchPolicy();
onClose();
},
onError(err) {
toast({
status: 'error',
title: t('Failed to turn off automatic backup')
});
}
});

return (
<>
<Modal isOpen onClose={onClose} isCentered>
Expand All @@ -209,6 +252,10 @@ const BackupModal = ({
variant={'deepLight'}
isChecked={getAutoValues('start')}
onChange={(e) => {
if (defaultVal.start) {
CloseAutoBackup(onclickCloseAutoBackup)();
return;
}
setAutoValue('start', e.target.checked);
setRefresh((state) => !state);
}}
Expand Down Expand Up @@ -389,6 +436,7 @@ const BackupModal = ({
</ModalContent>
</Modal>
<ConfirmChild />
<AutoBackupConfirmChild />
</>
);
};
Expand Down
Expand Up @@ -29,7 +29,7 @@ import { useConfirm } from '@/hooks/useConfirm';
import dayjs from 'dayjs';
import { BackupStatusEnum, backupTypeMap } from '@/constants/backup';
import { useTranslation } from 'next-i18next';
import { deleteBackup, getBackupPolicy } from '@/api/backup';
import { deleteBackup, getBackupPolicy, getBackupPolicyByCluster } from '@/api/backup';
import { getErrText } from '@/utils/tools';
import { getBackupList } from '@/api/backup';
import MyIcon from '@/components/Icon';
Expand Down Expand Up @@ -190,20 +190,10 @@ const BackupTable = ({ db }: { db?: DBDetailType }, ref: ForwardedRef<ComponentR
}));

const { data, refetch: refetchPolicy } = useQuery(['initpolicy', db.dbName, db.dbType], () =>
db.dbName && db.dbType
? getBackupPolicy({
dbName: db.dbName,
dbType: db.dbType
})
: {
start: false,
hour: '18',
minute: '00',
week: [],
type: 'day',
saveTime: 7,
saveType: 'day'
}
getBackupPolicyByCluster({
dbName: db.dbName,
dbType: db.dbType
})
);

return (
Expand Down
7 changes: 7 additions & 0 deletions frontend/providers/dbprovider/src/types/cluster.d.ts
Expand Up @@ -49,6 +49,13 @@ export interface KubeBlockClusterSpec {
};
}[];
}[];
backup: {
enabled: boolean;
cronExpression: string;
method: string;
pitrEnabled: boolean;
retentionPeriod: string;
};
}
export interface KubeBlockClusterStatus {
clusterDefGeneration: number;
Expand Down
31 changes: 31 additions & 0 deletions frontend/providers/dbprovider/src/utils/adapt.ts
Expand Up @@ -59,6 +59,37 @@ export const adaptDBDetail = (db: KbPgClusterType): DBDetailType => {
};
};

export const adaptBackupByCluster = (db: KbPgClusterType): AutoBackupFormType => {
const backup = db.spec.backup
? adaptPolicy({
metadata: {
name: db.metadata.name,
uid: db.metadata.uid
},
spec: {
retention: {
ttl: db.spec.backup.retentionPeriod
},
schedule: {
datafile: {
cronExpression: db.spec.backup.cronExpression,
enable: db.spec.backup.enabled
}
}
}
})
: {
start: false,
hour: '18',
minute: '00',
week: [],
type: 'day',
saveTime: 7,
saveType: 'd'
};
return backup;
};

export const adaptDBForm = (db: DBDetailType): DBEditType => {
const keys: Record<keyof DBEditType, any> = {
dbType: 1,
Expand Down
4 changes: 2 additions & 2 deletions frontend/providers/template/src/pages/api/updateRepo.ts
Expand Up @@ -46,11 +46,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
const gitOperationPromise = !fs.existsSync(targetPath)
? execAsync(`git clone ${repoHttpUrl} ${targetPath} --depth=1`)
: execAsync(`cd ${targetPath} && git pull --depth=1`);
: execAsync(`cd ${targetPath} && git pull --depth=1 --rebase`);

await Promise.race([gitOperationPromise, timeoutPromise]);
} catch (error) {
console.log('git operation timed out');
console.log('git operation timed out: \n', error);
}

if (!fs.existsSync(targetPath)) {
Expand Down

0 comments on commit d518f58

Please sign in to comment.