|
@@ -2,6 +2,7 @@ import { flags } from '@oclif/command'
|
|
import { createApp } from '../services/webApi/app'
|
|
import { createApp } from '../services/webApi/app'
|
|
import ApiCommandBase from '../command-base/ApiCommandBase'
|
|
import ApiCommandBase from '../command-base/ApiCommandBase'
|
|
import logger, { initElasticLogger } from '../services/logger'
|
|
import logger, { initElasticLogger } from '../services/logger'
|
|
|
|
+import { loadDataObjectIdCache } from '../services/caching/localDataObjects'
|
|
import { ApiPromise } from '@polkadot/api'
|
|
import { ApiPromise } from '@polkadot/api'
|
|
import { performSync, TempDirName } from '../services/sync/synchronizer'
|
|
import { performSync, TempDirName } from '../services/sync/synchronizer'
|
|
import sleep from 'sleep-promise'
|
|
import sleep from 'sleep-promise'
|
|
@@ -13,6 +14,8 @@ import { KeyringPair } from '@polkadot/keyring/types'
|
|
import ExitCodes from './../command-base/ExitCodes'
|
|
import ExitCodes from './../command-base/ExitCodes'
|
|
import { CLIError } from '@oclif/errors'
|
|
import { CLIError } from '@oclif/errors'
|
|
import { Worker } from '@joystream/types/working-group'
|
|
import { Worker } from '@joystream/types/working-group'
|
|
|
|
+import fs from 'fs'
|
|
|
|
+const fsPromises = fs.promises
|
|
|
|
|
|
/**
|
|
/**
|
|
* CLI command:
|
|
* CLI command:
|
|
@@ -50,10 +53,11 @@ export default class Server extends ApiCommandBase {
|
|
description: 'Interval between synchronizations (in minutes)',
|
|
description: 'Interval between synchronizations (in minutes)',
|
|
default: 1,
|
|
default: 1,
|
|
}),
|
|
}),
|
|
- queryNodeHost: flags.string({
|
|
|
|
|
|
+ queryNodeEndpoint: flags.string({
|
|
char: 'q',
|
|
char: 'q',
|
|
required: true,
|
|
required: true,
|
|
- description: 'Query node host and port (e.g.: some.com:8081)',
|
|
|
|
|
|
+ default: 'http://localhost:8081/graphql',
|
|
|
|
+ description: 'Query node endpoint (e.g.: http://some.com:8081/graphql)',
|
|
}),
|
|
}),
|
|
syncWorkersNumber: flags.integer({
|
|
syncWorkersNumber: flags.integer({
|
|
char: 'r',
|
|
char: 'r',
|
|
@@ -61,10 +65,10 @@ export default class Server extends ApiCommandBase {
|
|
description: 'Sync workers number (max async operations in progress).',
|
|
description: 'Sync workers number (max async operations in progress).',
|
|
default: 20,
|
|
default: 20,
|
|
}),
|
|
}),
|
|
- elasticSearchHost: flags.string({
|
|
|
|
|
|
+ elasticSearchEndpoint: flags.string({
|
|
char: 'e',
|
|
char: 'e',
|
|
required: false,
|
|
required: false,
|
|
- description: `Elasticsearch host and port (e.g.: some.com:8081).
|
|
|
|
|
|
+ description: `Elasticsearch endpoint (e.g.: http://some.com:8081).
|
|
Log level could be set using the ELASTIC_LOG_LEVEL enviroment variable.
|
|
Log level could be set using the ELASTIC_LOG_LEVEL enviroment variable.
|
|
Supported values: warn, error, debug, info. Default:debug`,
|
|
Supported values: warn, error, debug, info. Default:debug`,
|
|
}),
|
|
}),
|
|
@@ -79,16 +83,17 @@ Supported values: warn, error, debug, info. Default:debug`,
|
|
async run(): Promise<void> {
|
|
async run(): Promise<void> {
|
|
const { flags } = this.parse(Server)
|
|
const { flags } = this.parse(Server)
|
|
|
|
|
|
- await removeTempDirectory(flags.uploads, TempDirName)
|
|
|
|
|
|
+ await recreateTempDirectory(flags.uploads, TempDirName)
|
|
|
|
|
|
- let elasticUrl
|
|
|
|
- if (!_.isEmpty(flags.elasticSearchHost)) {
|
|
|
|
- elasticUrl = `http://${flags.elasticSearchHost}`
|
|
|
|
- initElasticLogger(elasticUrl)
|
|
|
|
|
|
+ if (fs.existsSync(flags.uploads)) {
|
|
|
|
+ await loadDataObjectIdCache(flags.uploads, TempDirName)
|
|
}
|
|
}
|
|
|
|
|
|
- const queryNodeUrl = `http://${flags.queryNodeHost}/graphql`
|
|
|
|
- logger.info(`Query node endpoint set: ${queryNodeUrl}`)
|
|
|
|
|
|
+ if (!_.isEmpty(flags.elasticSearchEndpoint)) {
|
|
|
|
+ initElasticLogger(flags.elasticSearchEndpoint ?? '')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ logger.info(`Query node endpoint set: ${flags.queryNodeEndpoint}`)
|
|
|
|
|
|
if (flags.dev) {
|
|
if (flags.dev) {
|
|
await this.ensureDevelopmentChain()
|
|
await this.ensureDevelopmentChain()
|
|
@@ -98,39 +103,53 @@ Supported values: warn, error, debug, info. Default:debug`,
|
|
logger.warn(`Uploading auth-schema disabled.`)
|
|
logger.warn(`Uploading auth-schema disabled.`)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ const api = await this.getApi()
|
|
|
|
+
|
|
if (flags.sync) {
|
|
if (flags.sync) {
|
|
logger.info(`Synchronization enabled.`)
|
|
logger.info(`Synchronization enabled.`)
|
|
-
|
|
|
|
- runSyncWithInterval(flags.worker, queryNodeUrl, flags.uploads, flags.syncWorkersNumber, flags.syncInterval)
|
|
|
|
|
|
+ setTimeout(
|
|
|
|
+ async () =>
|
|
|
|
+ runSyncWithInterval(
|
|
|
|
+ api,
|
|
|
|
+ flags.worker,
|
|
|
|
+ flags.queryNodeEndpoint,
|
|
|
|
+ flags.uploads,
|
|
|
|
+ TempDirName,
|
|
|
|
+ flags.syncWorkersNumber,
|
|
|
|
+ flags.syncInterval
|
|
|
|
+ ),
|
|
|
|
+ 0
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
- const account = this.getAccount(flags)
|
|
|
|
- const api = await this.getApi()
|
|
|
|
|
|
+ const storageProviderAccount = this.getAccount(flags)
|
|
|
|
|
|
- await verifyWorkerId(api, flags.worker, account)
|
|
|
|
|
|
+ await verifyWorkerId(api, flags.worker, storageProviderAccount)
|
|
|
|
|
|
try {
|
|
try {
|
|
const port = flags.port
|
|
const port = flags.port
|
|
- const workerId = flags.worker ?? 0
|
|
|
|
|
|
+ const workerId = flags.worker
|
|
const maxFileSize = await api.consts.storage.maxDataObjectSize.toNumber()
|
|
const maxFileSize = await api.consts.storage.maxDataObjectSize.toNumber()
|
|
|
|
+ const tempFileUploadingDir = path.join(flags.uploads, TempDirName)
|
|
logger.debug(`Max file size runtime parameter: ${maxFileSize}`)
|
|
logger.debug(`Max file size runtime parameter: ${maxFileSize}`)
|
|
|
|
|
|
const app = await createApp({
|
|
const app = await createApp({
|
|
api,
|
|
api,
|
|
- account,
|
|
|
|
|
|
+ storageProviderAccount,
|
|
workerId,
|
|
workerId,
|
|
maxFileSize,
|
|
maxFileSize,
|
|
uploadsDir: flags.uploads,
|
|
uploadsDir: flags.uploads,
|
|
- tempDirName: TempDirName,
|
|
|
|
|
|
+ tempFileUploadingDir,
|
|
process: this.config,
|
|
process: this.config,
|
|
- queryNodeUrl,
|
|
|
|
|
|
+ queryNodeEndpoint: flags.queryNodeEndpoint,
|
|
enableUploadingAuth: !flags.disableUploadAuth,
|
|
enableUploadingAuth: !flags.disableUploadAuth,
|
|
- elasticSearchEndpoint: elasticUrl,
|
|
|
|
|
|
+ elasticSearchEndpoint: flags.elasticSearchEndpoint,
|
|
})
|
|
})
|
|
logger.info(`Listening on http://localhost:${port}`)
|
|
logger.info(`Listening on http://localhost:${port}`)
|
|
app.listen(port)
|
|
app.listen(port)
|
|
} catch (err) {
|
|
} catch (err) {
|
|
logger.error(`Server error: ${err}`)
|
|
logger.error(`Server error: ${err}`)
|
|
|
|
+ this.exit(ExitCodes.ServerError)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -145,52 +164,54 @@ Supported values: warn, error, debug, info. Default:debug`,
|
|
* @param workerId - worker ID
|
|
* @param workerId - worker ID
|
|
* @param queryNodeUrl - Query Node for data fetching
|
|
* @param queryNodeUrl - Query Node for data fetching
|
|
* @param uploadsDir - data uploading directory
|
|
* @param uploadsDir - data uploading directory
|
|
|
|
+ * @param tempDirectory - temporary data uploading directory
|
|
* @param syncWorkersNumber - defines a number of the async processes for sync
|
|
* @param syncWorkersNumber - defines a number of the async processes for sync
|
|
* @param syncIntervalMinutes - defines an interval between sync runs
|
|
* @param syncIntervalMinutes - defines an interval between sync runs
|
|
*
|
|
*
|
|
* @returns void promise.
|
|
* @returns void promise.
|
|
*/
|
|
*/
|
|
-function runSyncWithInterval(
|
|
|
|
|
|
+async function runSyncWithInterval(
|
|
|
|
+ api: ApiPromise,
|
|
workerId: number,
|
|
workerId: number,
|
|
queryNodeUrl: string,
|
|
queryNodeUrl: string,
|
|
uploadsDirectory: string,
|
|
uploadsDirectory: string,
|
|
|
|
+ tempDirectory: string,
|
|
syncWorkersNumber: number,
|
|
syncWorkersNumber: number,
|
|
syncIntervalMinutes: number
|
|
syncIntervalMinutes: number
|
|
) {
|
|
) {
|
|
- setTimeout(async () => {
|
|
|
|
- const sleepIntevalInSeconds = syncIntervalMinutes * 60 * 1000
|
|
|
|
-
|
|
|
|
|
|
+ const sleepIntevalInSeconds = syncIntervalMinutes * 60 * 1000
|
|
|
|
+ while (true) {
|
|
logger.info(`Sync paused for ${syncIntervalMinutes} minute(s).`)
|
|
logger.info(`Sync paused for ${syncIntervalMinutes} minute(s).`)
|
|
await sleep(sleepIntevalInSeconds)
|
|
await sleep(sleepIntevalInSeconds)
|
|
- logger.info(`Resume syncing....`)
|
|
|
|
-
|
|
|
|
try {
|
|
try {
|
|
- await performSync(workerId, syncWorkersNumber, queryNodeUrl, uploadsDirectory)
|
|
|
|
|
|
+ logger.info(`Resume syncing....`)
|
|
|
|
+ await performSync(api, workerId, syncWorkersNumber, queryNodeUrl, uploadsDirectory, tempDirectory)
|
|
} catch (err) {
|
|
} catch (err) {
|
|
logger.error(`Critical sync error: ${err}`)
|
|
logger.error(`Critical sync error: ${err}`)
|
|
}
|
|
}
|
|
-
|
|
|
|
- runSyncWithInterval(workerId, queryNodeUrl, uploadsDirectory, syncWorkersNumber, syncIntervalMinutes)
|
|
|
|
- }, 0)
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Removes the temporary directory from the uploading directory.
|
|
|
|
|
|
+ * Removes and recreates the temporary directory from the uploading directory.
|
|
* All files in the temp directory are deleted.
|
|
* All files in the temp directory are deleted.
|
|
*
|
|
*
|
|
- * @param uploadsDir - data uploading directory
|
|
|
|
|
|
+ * @param uploadsDirectory - data uploading directory
|
|
* @param tempDirName - temporary directory name within the uploading directory
|
|
* @param tempDirName - temporary directory name within the uploading directory
|
|
* @returns void promise.
|
|
* @returns void promise.
|
|
*/
|
|
*/
|
|
-async function removeTempDirectory(uploadsDir: string, tempDirName: string): Promise<void> {
|
|
|
|
|
|
+async function recreateTempDirectory(uploadsDirectory: string, tempDirName: string): Promise<void> {
|
|
try {
|
|
try {
|
|
- logger.info(`Removing temp directory ...`)
|
|
|
|
- const tempFileUploadingDir = path.join(uploadsDir, tempDirName)
|
|
|
|
|
|
+ const tempFileUploadingDir = path.join(uploadsDirectory, tempDirName)
|
|
|
|
|
|
|
|
+ logger.info(`Removing temp directory ...`)
|
|
const rimrafAsync = promisify(rimraf)
|
|
const rimrafAsync = promisify(rimraf)
|
|
await rimrafAsync(tempFileUploadingDir)
|
|
await rimrafAsync(tempFileUploadingDir)
|
|
|
|
+
|
|
|
|
+ logger.info(`Creating temp directory ...`)
|
|
|
|
+ await fsPromises.mkdir(tempFileUploadingDir)
|
|
} catch (err) {
|
|
} catch (err) {
|
|
- logger.error(`Removing temp directory error: ${err}`)
|
|
|
|
|
|
+ logger.error(`Temp directory IO error: ${err}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -205,7 +226,7 @@ async function removeTempDirectory(uploadsDir: string, tempDirName: string): Pro
|
|
*/
|
|
*/
|
|
async function verifyWorkerId(api: ApiPromise, workerId: number, account: KeyringPair): Promise<void> {
|
|
async function verifyWorkerId(api: ApiPromise, workerId: number, account: KeyringPair): Promise<void> {
|
|
// Cast Codec type to Worker type
|
|
// Cast Codec type to Worker type
|
|
- const workerObj = (await api.query.storageWorkingGroup.workerById(workerId)) as unknown
|
|
|
|
|
|
+ const workerObj = await api.query.storageWorkingGroup.workerById(workerId)
|
|
const worker = workerObj as Worker
|
|
const worker = workerObj as Worker
|
|
|
|
|
|
if (worker.role_account_id.toString() !== account.address) {
|
|
if (worker.role_account_id.toString() !== account.address) {
|