Skip to content

Commit

Permalink
Merge pull request #238 from openkfw/fix-backup-restore
Browse files Browse the repository at this point in the history
Fix backup/restore feature
  • Loading branch information
mathiashoeld authored Apr 25, 2019
2 parents b373849 + f69bb7d commit d862116
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 38 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed

- Fixed line of YAML file for master deployments via docker-compose, so that image of excel export service is pulled correctly [#223](https://github.com/openkfw/TruBudget/issues/223)

- Backup/restore works again. [#237](https://github.com/openkfw/TruBudget/issues/237)

## [1.0.0-beta.9] - 2019-04-23

Expand Down
19 changes: 8 additions & 11 deletions api/src/httpd/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FastifyInstance } from "fastify";

import { toHttpError } from "../http_errors";
import { Ctx } from "../lib/ctx";
import logger from "../lib/logger";
import { isReady } from "../lib/readiness";
Expand Down Expand Up @@ -186,15 +187,8 @@ const handleError = (req, res, err: any) => {
},
]);
} else {
const message = "INTERNAL SERVER ERROR";
logger.error({ error: err }, message);
send(res, [
500,
{
apiVersion: "1.0",
error: { code: 500, message },
},
]);
const { code, body } = toHttpError(err);
res.status(code).send(body);
}
}
}
Expand All @@ -215,6 +209,7 @@ export const registerRoutes = (
urlPrefix: string,
multichainHost: string,
backupApiPort: string,
invalidateCache: () => void,
) => {
const multichainClient = conn.multichainClient;

Expand Down Expand Up @@ -327,10 +322,12 @@ export const registerRoutes = (
server.post(
`${urlPrefix}/system.restoreBackup`,
getSchema(server, "restoreBackup"),
(req: AuthenticatedRequest, reply) => {
restoreBackup(multichainHost, backupApiPort, req)
async (req: AuthenticatedRequest, reply) => {
await restoreBackup(multichainHost, backupApiPort, req)
.then(response => send(reply, response))
.catch(err => handleError(req, reply, err));
// Invalidate the cache, regardless of the outcome:
await invalidateCache();
},
);

Expand Down
9 changes: 5 additions & 4 deletions api/src/httpd/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3263,10 +3263,11 @@ const schemas = {
],
consumes: ["application/gzip"],
body: {
description: "binary gzip file",
type: "string",
// format: "binary",
example: "backup.gz (send a backup-file as binary via an API-Testing-Tool like postman)",
// type=string + format=binary is not supported by fastify-swagger.
// Instead, we use the `AnyValue` schema, which allows anything.
AnyValue: {
description: "backup.gz (binary gzip file)",
},
},
response: {
200: {
Expand Down
5 changes: 4 additions & 1 deletion api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import * as ProjectUpdateAPI from "./project_update";
import * as ProjectViewDetailsAPI from "./project_view_details";
import * as ProjectViewHistoryAPI from "./project_view_history";
import * as Multichain from "./service";
import * as Cache from "./service/cache2";
import * as DocumentValidationService from "./service/document_validation";
import * as GlobalPermissionGrantService from "./service/global_permission_grant";
import * as GlobalPermissionRevokeService from "./service/global_permission_revoke";
Expand Down Expand Up @@ -199,7 +200,9 @@ function registerSelf(): Promise<boolean> {
* Deprecated API-setup
*/

registerRoutes(server, db, URL_PREFIX, multichainHost, backupApiPort);
registerRoutes(server, db, URL_PREFIX, multichainHost, backupApiPort, () =>
Cache.invalidateCache(db),
);

/*
* APIs related to Global Permissions
Expand Down
39 changes: 25 additions & 14 deletions api/src/service/cache2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,17 @@ export function initCache(): Cache2 {
};
}

export interface CacheInstance {
function clearCache(cache: Cache2): void {
cache.streamState.clear();
cache.eventsByStream.clear();
cache.cachedProjects.clear();
cache.cachedSubprojects.clear();
cache.cachedWorkflowItems.clear();
cache.cachedSubprojectLookup.clear();
cache.cachedWorkflowitemLookup.clear();
}

interface CacheInstance {
getGlobalEvents(): BusinessEvent[];
getUserEvents(userId?: string): BusinessEvent[];
getGroupEvents(groupId?: string): BusinessEvent[];
Expand All @@ -111,8 +121,6 @@ export interface CacheInstance {
): Promise<Result.Type<Workflowitem.Workflowitem>>;
}

export type TransactionFn<T> = (cache: CacheInstance) => Promise<T>;

export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance {
return {
getGlobalEvents: (): BusinessEvent[] => {
Expand Down Expand Up @@ -229,6 +237,8 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance {
};
}

export type TransactionFn<T> = (cache: CacheInstance) => Promise<T>;

export async function withCache<T>(
conn: ConnToken,
ctx: Ctx,
Expand Down Expand Up @@ -256,6 +266,18 @@ export async function withCache<T>(
}
}

export async function invalidateCache(conn: ConnToken): Promise<void> {
const cache = conn.cache2;
try {
// Make sure we're the only thread-of-execution:
await grabWriteLock(cache);
// Invalidate the cache by removing all of its data:
clearCache(cache);
} finally {
releaseWriteLock(cache);
}
}

async function grabWriteLock(cache: Cache2) {
while (cache.isWriteLocked) {
await new Promise(res => setTimeout(res, 1));
Expand All @@ -267,17 +289,6 @@ function releaseWriteLock(cache: Cache2) {
cache.isWriteLocked = false;
}

async function refresh(ctx: Ctx, conn: ConnToken, streamName?: string): Promise<void> {
const { cache2: cache } = conn;
try {
// Make sure we're the only thread-of-execution that updates the cache:
await grabWriteLock(cache);
await updateCache(ctx, conn, streamName);
} finally {
releaseWriteLock(cache);
}
}

async function findStartIndex(
multichainClient: MultichainClient,
streamName: string,
Expand Down
13 changes: 6 additions & 7 deletions api/src/system/restoreBackup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import axios from "axios";
import { VError } from "verror";

import { AuthenticatedRequest, HttpResponse } from "../httpd/lib";
import logger from "../lib/logger";

Expand All @@ -22,13 +24,10 @@ export const restoreBackup = async (
};
try {
await axios.post(`http://${multichainHost}:${backupApiPort}/chain/`, data, config);
} catch (err) {
if (err.response.status === 400) {
throw { kind: "CorruptFileError" };
} else {
logger.error({ error: err }, "An error occured while restoring the backup");
throw new Error(err.message);
}
logger.info("backup restored successfully");
} catch (error) {
const cause = error.response.status === 400 ? new Error(error.response.data) : error;
throw new VError(cause, "failed to restore backup");
}
return [
200,
Expand Down

0 comments on commit d862116

Please sign in to comment.