Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- **CUMULUS-4388**
- Added cnm_to_cma task (lambda).
- Original cnm_to_cma was written in Java. Converted to Python.
- **CUMULUS-4381**
- Added cma_to_cnm task (lambda).
- **CUMULUS-4382**
- Migrated the granule-invalidator task to the `tasks` directory as part of a coreification task in support of providing rolling archive functionality.
- **CUMULUS-4385**
Expand Down
6 changes: 6 additions & 0 deletions tasks/cma-to-cnm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.venv
.coverage
build
__pycache__
coverage_html
.nyc_output
2 changes: 2 additions & 0 deletions tasks/cma-to-cnm/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/nyc.config.js
/tests/
248 changes: 248 additions & 0 deletions tasks/cma-to-cnm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# @cumulus/granule-to-cnm

This lambda function converts Cloud Notification Mechanism format to CMA message
format.

CNM schema is defined in schemas/input.json
payload file schema defined in schema/output.json
## Message configuration

For more information on configuring a Cumulus Message Adapter task, see [the Cumulus workflow input/output documentation](https://nasa.github.io/cumulus/docs/workflows/input_output).

### Config

The following table describes the properties of the configuration object. Note that the top-level object requires both provider and collection to be valid.

| field name | type | default| required | description |
|------------------------------|--------|--------|----------|-------------------------------------------------------|
| provier | Object | None | Y | Data source provider |
| provider.id | string | None | Y | (Required)he unique identifier for the provider. |
| provider.protocol | string | None | N | The transfer protocol used (e.g.s3, ftp, http). |
| provider.host | string | None | N | The network host address for the provider. |
| collection | object | None | Y | Contains metadata regarding the data collection. |
| collection.name | string | None | Y | The name of the collection. |
| cumulus_meta | object | None | N | Internal metadata for Cumulus execution tracking. |
| cumulus_meta.state_machine | string | None | N | The name of the specific state machine being invoked. |
| cumulus_meta.execution_name | string | None | N | The specific identifier for the current execution. |

##### Example of config input:

```json
{
"provider": {
"id": "PROV_123",
"protocol": "s3",
"host": "my-data-bucket"
},
"collection": {
"name": "landsat_8_records"
},
"cumulus_meta": {
"state_machine": "IngestWorkflow",
"execution_name": "exec-001-alpha"
}
}
```


Example of workflow configuraton:

```angular2html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```angular2html
```json

"GranuleToCNM": {
"Parameters": {
"cma": {
"event.$": "$",
"task_config": {
"provider": "{$.meta.provider}",
"provider_path": "{$.meta.collection.meta.provider_path}",
"collection": "{$.meta.collection}",
"cumulus_meta": "{$.cumulus_meta}",
"cumulus_message": ""
},
"ReplaceConfig": {
"MaxSize": 10000,
"Path": "$",
"TargetPath": "$"
}
}
},
"Type": "Task",
"Resource": "${aws_lambda_function.cumulus_granule_to_cnm_task.arn}",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 3,
"MaxAttempts": 1,
"BackoffRate": 3
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.exception",
"Next": "WorkflowFailed"
}
],
"Next": "QueueCNMs"
},
```
The Step Function task definition utilizes a task_config object to manage dynamic inputs.
As example, within this configuration, the collection field is mapped using JSONPath to the metadata:

Config Input: "collection": "{$.meta.collection}"


This task is designed as a modular component. You can load it into your infrastructure using the following Terraform example:
```angular2html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think angular2html is the right language here. I think you want hcl for terraform code if I recall correctly.

Suggested change
```angular2html
```hcl


module "cma_to_cnm_module" {

source = "http://github.com/downloadpath/cma_to_cnm_module.zip"
prefix = var.prefix
region = var.region

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This region should be removed if the variables.tf is updated to remove it.

lambda_role = module.cumulus.lambda_processing_role_arn
security_group_ids = [aws_security_group.no_ingress_all_egress.id]

subnet_ids = var.subnet_ids
memory_size = 128
timeout = 180
tags = merge(local.tags, { Project = var.prefix })
}

resource "aws_cloudwatch_log_group" "cma_to_cnm_task" {
name = "/aws/lambda/${module.cnm_to_cma_module.cnm_to_cma_name}"
retention_in_days = var.task_logs_retention_in_days

}
```

### Input


Input array specification:

| field name | type | default | description
| ---------- |--------| ------- | -----------
| N/A | object | (required) | cnm message

the lambda's event.get('input') is the cnm message.

### Output

Output object fields:

| field name | type | default | description|
|------------|-----------------| ------- | -----------|
| cnm | array\<object\> | N/A | cma payload with list of cma files|
| cnm | object | N/A | the original cnm message.|


Data Mapping and Payload Configuration

When configuring the task, please refer to the example provided in the "Config" section. Ensure the output is mapped according to the following structure:

Granule Mapping: The output_granules field must be mapped directly to the primary payload.

Metadata Attachment: The original Cloud Notification Mechanism (CNM) message should be nested under the meta object.
### Example workflow configuration and use
The output key : cnm_list should be mapped to the payload key in the workflow's output.
Workflow developer could choose to the original cnm message to be stored in meta.cnm key.
Example workflow:
```angular2html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also suggest running your json through an autoformatter before pasting it in here. There's a chance if you have an autoformatter set up in your editor, that putting the correct language here will enable the autoformatter to work on the fenced code as well.

Suggested change
```angular2html
```json

"GranuleToCNM": {
"Parameters": {
"cma": {
"event.$": "$",
"task_config": {
"provider": "{$.meta.provider}",
"provider_path": "{$.meta.collection.meta.provider_path}",
"collection": "{$.meta.collection}",
"cumulus_meta": "{$.cumulus_meta}",
"cumulus_message": ""
},
"ReplaceConfig": {
"MaxSize": 10000,
"Path": "$",
"TargetPath": "$"
}
}
},
"Type": "Task",
"Resource": "${aws_lambda_function.cumulus_granule_to_cnm_task.arn}",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 3,
"MaxAttempts": 1,
"BackoffRate": 3
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.exception",
"Next": "WorkflowFailed"
}
],
"Next": "QueueCNMs"
},
```
## Architecture
```mermaid
architecture-cma-to-cnm
group trigger(cloud) [Starting Task]
translate task(cloud)[Task]

lambda:R --> L:db
```

## Internal Dependencies
Python cumulus-message-adapter library

### External Dependencies
None

## Development and Deployment
#### Developer Notes

- About json schema compiling to Plaint Python classes:
- used datamodel-code-generator tool: https://koxudaxi.github.io/datamodel-code-generator/ to generate pydantic models from json schema files.
- example below
```angular2html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either shell or no language probably would be the best options.

pip install datamodel-code-generator
# Or with HTTP support for remote references
pip install "datamodel-code-generator[http]"

datamodel-codegen \
--input input.json \
--input-file-type jsonschema \
--output models_cma.py \
--output-model-type pydantic_v2.BaseModel
```

## Contributing

To make a contribution, please [see our Cumulus contributing guidelines](https://github.com/nasa/cumulus/blob/master/CONTRIBUTING.md) and our documentation on [adding a task](https://nasa.github.io/cumulus/docs/adding-a-task)


## About Cumulus

Cumulus is a cloud-based data ingest, archive, distribution and management prototype for NASA's future Earth science data streams.

[Cumulus Documentation](https://nasa.github.io/cumulus)

## Contributing

To make a contribution, please [see our contributing guidelines](https://github.com/nasa/cumulus/blob/master/CONTRIBUTING.md).
5 changes: 5 additions & 0 deletions tasks/cma-to-cnm/build-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"runtime": "python3.13",
"uv_version": "0.9.21",
"architecture": "x86_64"
}
34 changes: 34 additions & 0 deletions tasks/cma-to-cnm/deploy/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
locals {
build_config = jsondecode(file("${path.module}/../build-config.json"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears this build_config is unused

}

resource "aws_lambda_function" "cma_to_cnm" {
filename = "${path.module}/../dist/final/lambda.zip"
function_name = "${var.prefix}-CMAToCNM"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at other tasks that have acronyms in the name (such as PDR) it seems that only the first letter of the acronym is capitalized. So to be consistent with the naming of other core tasks I would suggest naming it like this:

Suggested change
function_name = "${var.prefix}-CMAToCNM"
function_name = "${var.prefix}-CmaToCnm"

filebase64sha256 ="${path.module}/../dist/final/lambda.zip")
handler = "cma_to_cnm.cma_to_cnm.handler"
role = var.lambda_role
runtime = "python3.12"
timeout = var.timeout
memory_size = var.memory_size

environment {
variables = {
stackName = var.prefix
CUMULUS_MESSAGE_ADAPTER_DIR = "/opt/"
}
}

vpc_config {
subnet_ids = var.subnet_ids
security_group_ids = var.security_group_ids
}
Comment on lines +18 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the vpc_config section of cnm-response, cnm-to-cma, or granule-invalidator and copy from there. It's got a slightly different setup that allows for the vpc_config to be optional.


tags = local.tags
}

resource "aws_cloudwatch_log_group" "cma_to_cnm" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note here, I was doing more research on this and I found a recommendation that the lambda function should have a depends_on on the log group so that terraform knows to create the log group before the lambda. Otherwise it could be possible for terraform to create the lambda first, which auto-creates a log group with this name and then terraform fails to create the log group due to the resource already existing.

However, that change also requires moving the lambda function name to a local otherwise you get a cyclic dependency between the two resources.

I was working on adding those changes here: #4268

name = "/aws/lambda/${aws_lambda_function.cma_to_cnm.function_name}"
retention_in_days = var.default_log_retention_days
tags = local.tags
}
9 changes: 9 additions & 0 deletions tasks/cma-to-cnm/deploy/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "lambda_function" {
description = "The task lambda function"
value = aws_lambda_function.cma_to_cnm
}

output "log_group" {
description = "Name of the Lambda's CloudWatch log group"
value = aws_cloudwatch_log_group.cma_to_cnm
}
44 changes: 44 additions & 0 deletions tasks/cma-to-cnm/deploy/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
variable "prefix" {
type = string
}

# The location of footprint dataset-config file. Ex. s3://my-internal/datset-config/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't appear to match this file

variable "lambda_role" {
type = string
}

variable "security_group_ids" {
type = list(string)
}

variable "subnet_ids" {
type = list(string)
}

variable "region" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears this is unused

type = string
}

variable "app_name" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears this is unused

default = "workflow-normalizer"
}

variable "tags" {
type = map(string)
default = {}
}

variable "memory_size" {
type = number
default = 512
}

variable "timeout" {
type = number
default = 120
}

variable "default_log_retention_days" {
description = "The number of days to retain logs in CloudWatch"
type = number
}
10 changes: 10 additions & 0 deletions tasks/cma-to-cnm/deploy/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
Loading