-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the queue-granules task to fetch collection configs for each granule from S3 #284
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
54a9a1a
Refactored parse-pdr
0b91ef4
Update sftp mixin to remove default export
ac3504a
Remove unused findTmpTestDataDirectory function from test-utils
1363fed
Add a simple parse-pdr test
cca6efc
Merge branch 'master' into CUMULUS-450
8994bad
CUMULUS-450 Fix incorrect collection configs in queue-granules
70c75fb
Updated changelog
a56240e
Ratchet-down eslint score
fdbd682
Updates based on PR feedback
e4d9842
Added a reminder to document the granuleFromFileGroup function
ab21eb8
Ratchet-down eslint score
267681a
Create 1.2.1-alpha.1 release of ingest and queue-granules
2e68f00
Merge branch 'master' of https://github.com/cumulus-nasa/cumulus
731c205
Merge branch 'master' into CUMULUS-450
d49a7ad
ParsePdr task uses correct granuleExtractionId if multiple dataTypes …
86a9180
Merge branch 'master' into CUMULUS-450
74449e9
Add comments to collection-config-store
5aa0aaa
Revert index reorganization
ef39309
Add comments to collection-config-store tests
23c3cd3
Revert import order changes
27b9844
Updated changelog
299a4ea
Revert PDR import order changes
d758e44
Revert SFTP import order changes
bcb3798
Revert queue-granules array order
b0c67d5
Update eslint ratchet score
d599326
Fix e2e tests
1ef5f0b
Updates to support cumulus integration tests
7e90148
Updates to get integration tests working
ac8d9e1
Merge branch 'master' into CUMULUS-450
8dcaecd
Merge branch 'master' into CUMULUS-450
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
1306 | ||
1145 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
'use strict'; | ||
|
||
const { s3 } = require('./aws'); | ||
|
||
/** | ||
* Store and retrieve collection configs in S3 | ||
*/ | ||
class CollectionConfigStore { | ||
/** | ||
* Initialize a CollectionConfigFetcher instance | ||
* | ||
* @param {string} bucket - the bucket where collection configs are stored | ||
* @param {string} stackName - the Cumulus deployment stack name | ||
*/ | ||
constructor(bucket, stackName) { | ||
this.bucket = bucket; | ||
this.stackName = stackName; | ||
this.cache = {}; | ||
} | ||
|
||
/** | ||
* Fetch a collection config from S3 (or cache if available) | ||
* | ||
* @param {string} dataType - the name of the collection config to fetch | ||
* @returns {Object} the fetched collection config | ||
*/ | ||
async get(dataType) { | ||
// Check to see if the collection config has already been cached | ||
if (!this.cache[dataType]) { | ||
let response; | ||
try { | ||
// Attempt to fetch the collection config from S3 | ||
response = await s3().getObject({ | ||
Bucket: this.bucket, | ||
Key: this.configKey(dataType) | ||
}).promise(); | ||
} | ||
catch (err) { | ||
if (err.code === 'NoSuchKey') { | ||
throw new Error(`A collection config for data type "${dataType}" was not found.`); | ||
} | ||
|
||
if (err.code === 'NoSuchBucket') { | ||
throw new Error(`Collection config bucket does not exist: ${this.bucket}`); | ||
} | ||
|
||
throw err; | ||
} | ||
|
||
// Store the fetched collection config to the cache | ||
this.cache[dataType] = JSON.parse(response.Body.toString()); | ||
} | ||
|
||
return this.cache[dataType]; | ||
} | ||
|
||
/** | ||
* Store a collection config to S3 | ||
* | ||
* @param {string} dataType - the name of the collection config to store | ||
* @param {Object} config - the collection config to store | ||
* @returns {Promise<null>} resolves when the collection config has been written | ||
* to S3 | ||
*/ | ||
async put(dataType, config) { | ||
this.cache[dataType] = config; | ||
|
||
return s3().putObject({ | ||
Bucket: this.bucket, | ||
Key: `${this.stackName}/collections/${dataType}.json`, | ||
Body: JSON.stringify(config) | ||
}).promise().then(() => null); // Don't leak implementation details to the caller | ||
} | ||
|
||
/** | ||
* Return the S3 key pointing to the collection config | ||
* | ||
* @param {string} dataType - the datatype | ||
* @returns {string} the S3 key where the collection config is located | ||
* | ||
* @private | ||
*/ | ||
configKey(dataType) { | ||
return `${this.stackName}/collections/${dataType}.json`; | ||
} | ||
} | ||
module.exports = CollectionConfigStore; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"no-param-reassign": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
'use strict'; | ||
|
||
const test = require('ava'); | ||
const { recursivelyDeleteS3Bucket, s3 } = require('../aws'); | ||
const { randomString } = require('../test-utils'); | ||
const CollectionConfigStore = require('../collection-config-store'); | ||
|
||
test.beforeEach(async (t) => { | ||
t.context.stackName = randomString(); | ||
t.context.dataType = randomString(); | ||
t.context.collectionConfig = { name: randomString() }; | ||
|
||
t.context.bucket = randomString(); | ||
await s3().createBucket({ Bucket: t.context.bucket }).promise(); | ||
|
||
// Utility function to return the S3 key of a collection config | ||
t.context.collectionConfigKey = (dataType) => | ||
`${t.context.stackName}/collections/${dataType}.json`; | ||
}); | ||
|
||
test.afterEach(async (t) => { | ||
try { | ||
await recursivelyDeleteS3Bucket(t.context.bucket); | ||
} | ||
catch (err) { | ||
// Some tests delete the bucket before this "afterEach" hook is run | ||
if (err.code !== 'NoSuchBucket') throw err; | ||
} | ||
}); | ||
|
||
test('get() fetches a collection config from S3', async (t) => { | ||
await s3().putObject({ | ||
Bucket: t.context.bucket, | ||
Key: t.context.collectionConfigKey(t.context.dataType), | ||
Body: JSON.stringify(t.context.collectionConfig) | ||
}).promise(); | ||
|
||
const collectionConfigStore = new CollectionConfigStore(t.context.bucket, t.context.stackName); | ||
const fetchedCollectionConfig = await collectionConfigStore.get(t.context.dataType); | ||
|
||
t.deepEqual(fetchedCollectionConfig, t.context.collectionConfig); | ||
}); | ||
|
||
test('get() does not hit S3 for a cached collection config', async (t) => { | ||
await s3().putObject({ | ||
Bucket: t.context.bucket, | ||
Key: t.context.collectionConfigKey(t.context.dataType), | ||
Body: JSON.stringify(t.context.collectionConfig) | ||
}).promise(); | ||
|
||
const collectionConfigStore = new CollectionConfigStore(t.context.bucket, t.context.stackName); | ||
|
||
// Fetch the collection config once so it's in the cache | ||
await collectionConfigStore.get(t.context.dataType); | ||
|
||
// Delete the S3 bucket so the config can't be fetched from S3 | ||
await recursivelyDeleteS3Bucket(t.context.bucket); | ||
|
||
// This get() should use the cache | ||
const fetchedCollectionConfig = await collectionConfigStore.get(t.context.dataType); | ||
|
||
t.deepEqual(fetchedCollectionConfig, t.context.collectionConfig); | ||
}); | ||
|
||
test('get() throws an exception if the collection config could not be found', async (t) => { | ||
const invalidDataType = randomString(); | ||
const collectionConfigStore = new CollectionConfigStore(t.context.bucket, t.context.stackName); | ||
|
||
try { | ||
await collectionConfigStore.get(invalidDataType); | ||
t.fail('Expected an error to be thrown'); | ||
} | ||
catch (err) { | ||
t.is(err.message, `A collection config for data type "${invalidDataType}" was not found.`); | ||
} | ||
}); | ||
|
||
test('get() throws an exception if the bucket does not exist', async (t) => { | ||
const invalidBucket = randomString(); | ||
const collectionConfigStore = new CollectionConfigStore(invalidBucket, t.context.stackName); | ||
|
||
try { | ||
await collectionConfigStore.get(t.context.dataType); | ||
t.fail('Expected an error to be thrown'); | ||
} | ||
catch (err) { | ||
t.is(err.message, `Collection config bucket does not exist: ${invalidBucket}`); | ||
} | ||
}); | ||
|
||
test('put() stores a collection config to S3', async (t) => { | ||
const collectionConfigStore = new CollectionConfigStore(t.context.bucket, t.context.stackName); | ||
await collectionConfigStore.put(t.context.dataType, t.context.collectionConfig); | ||
|
||
const getObjectResponse = await s3().getObject({ | ||
Bucket: t.context.bucket, | ||
Key: t.context.collectionConfigKey(t.context.dataType) | ||
}).promise(); | ||
|
||
const storedCollectionConfig = JSON.parse(getObjectResponse.Body.toString()); | ||
t.deepEqual(storedCollectionConfig, t.context.collectionConfig); | ||
}); | ||
|
||
test('put() updates the cache with the new collection config', async (t) => { | ||
const collectionConfigStore = new CollectionConfigStore(t.context.bucket, t.context.stackName); | ||
await collectionConfigStore.put(t.context.dataType, t.context.collectionConfig); | ||
|
||
// Delete the S3 bucket so the config can't be fetched from S3 | ||
await recursivelyDeleteS3Bucket(t.context.bucket); | ||
|
||
// This get() should use the cache | ||
const fetchedCollectionConfig = await collectionConfigStore.get(t.context.dataType); | ||
|
||
t.deepEqual(fetchedCollectionConfig, t.context.collectionConfig); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now have one central piece of code that manages the storage and retrieval of collection configs in S3.