Skip to content

Commit 41bada3

Browse files
Merge pull request #8801 from alphaprinz/notif_backports
[Backport int 5.18] bucket notification - backports
2 parents fc5584f + 290ceeb commit 41bada3

16 files changed

+481
-89
lines changed

config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ config.NOTIFICATION_LOG_NS = 'notification_logging';
716716
config.NOTIFICATION_LOG_DIR = process.env.NOTIFICATION_LOG_DIR;
717717
config.NOTIFICATION_BATCH = process.env.BATCH || 10;
718718
config.NOTIFICATION_REQ_PER_SPACE_CHECK = process.env.NOTIFICATION_REQ_PER_SPACE_CHECK || 0;
719-
config.NOTIFICATION_SPACE_CHECK_THRESHOLD = parseInt(process.env.NOTIFICATION_SPACE_CHECK_THRESHOLD, 10) || 0.1;
719+
config.NOTIFICATION_SPACE_CHECK_THRESHOLD = parseFloat(process.env.NOTIFICATION_SPACE_CHECK_THRESHOLD) || 0.2;
720720

721721
///////////////////////////
722722
// KEY ROTATOR //

docs/NooBaaNonContainerized/ConfigFileCustomizations.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,18 @@ Warning: After setting this configuration, NooBaa will skip schema validations a
449449
"LOG_TO_STDERR_ENABLED": false
450450
3. systemctl restart noobaa
451451
```
452+
### 30. Notification log directory
453+
* <u>Key</u> `NOTIFICATION_LOG_DIR`
454+
* <u>Type</u> String
455+
* <u>Default</u> empty
456+
* <u>Description</u> Path to directory that will hold pending notifications to be sent,
457+
* <u>Steps</u>
458+
```
459+
1. Open /path/to/config_dir/config.json file.
460+
2. Set the config key -
461+
Example:
462+
"NOTIFICATION_LOG_DIR": "/etc/notif"
463+
3. systemctl restart noobaa
452464
453465
### 31. Prometheus HTTP enable flag -
454466
* <u>Key</u>: `ALLOW_HTTP_METRICS`

docs/NooBaaNonContainerized/Health.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ For more details about NooBaa RPM installation, see - [Getting Started](./Gettin
4242
- checks if there is ongoing upgrade
4343
- returns error if there is no ongoing upgrade, but the config directory phase is locked
4444
- returns message if there is no ongoing upgrade and the config directory is unlocked
45+
- `Bucket event notifications connections health`
46+
- Sends out a test notification for each connection.
4547

4648
* Health CLI requires root permissions.
4749

@@ -53,7 +55,7 @@ The `health` command is used to analyze NooBaa health with customizable options.
5355

5456
```sh
5557
noobaa-cli diagnose health [--deployment_type][--https_port]
56-
[--all_account_details][--all_bucket_details][--config_root][--debug]
58+
[--all_account_details][--all_bucket_details][--all_connection_details][--notif_storage_threshold][--config_root][--debug]
5759
```
5860
### Flags -
5961

@@ -75,7 +77,17 @@ noobaa-cli diagnose health [--deployment_type][--https_port]
7577
- `all_bucket_details`
7678
- Type: Boolean
7779
- Default: false
78-
- Description: Indicates if health output should contain valid buckets.
80+
- Description: Indicates if health output should contain valid buckets.
81+
82+
- `all_connection_details`
83+
- Type: Boolean
84+
- Default: false
85+
- Description: Indicates if health output should contain connection test result.
86+
87+
- `notif_storage_threshold`
88+
- Type: Boolean
89+
- Default: false
90+
- Description: Whether health ouput should check if notification storage FS is below threshold.
7991

8092
- `config_root`
8193
- Type: String
@@ -143,6 +155,10 @@ The output of the Health CLI is a JSON object containing the following propertie
143155
2. The account doesn't have RW access to its `new_buckets_path` or having invalid config JSON file.
144156
3. The account has an invalid config JSON file.
145157

158+
- `invalid connections`:
159+
- Type: [{ "name": "connection_name", "config_path": "/connection/file/path", "code": String }]
160+
- Description: Array of connections that failed test notification.
161+
146162
- `valid_accounts`
147163
- Type: [{ "name": account_name, "storage_path": "/path/to/accounts/new_buckets_path" }]
148164
- Description: Array of all the valid accounts. If the all_account_details flag is set to true, valid_accounts will be included in the Health response.
@@ -151,6 +167,10 @@ The output of the Health CLI is a JSON object containing the following propertie
151167
- Type: [{ "name": bucket_name, "storage_path": "/path/to/bucket/path" }]
152168
- Description: Array of all the valid buckets. If the all_bucket_details flag is set to true, valid_buckets will be included in the Health response.
153169

170+
- `valid_connections`:
171+
- Type: [{ "name": "connection_name", "config_path": "/connection/file/path" }]
172+
- Description: Array of all connections to which test notification was send successfully.
173+
154174
- `error_type`
155175
- Type: String
156176
- Enum: 'PERSISTENT' | 'TEMPORARY'
@@ -239,6 +259,26 @@ Output:
239259
],
240260
"error_type": "PERSISTENT"
241261
},
262+
"connectoins_status": {
263+
"invalid_connections": [
264+
{
265+
"name": "notif_invalid",
266+
"config_path": "/etc/noobaa.conf.d/connections/notif_invalid.json",
267+
"code": "ECONNREFUSED"
268+
}
269+
],
270+
"valid_connections": [
271+
{
272+
"name": "notif_valid",
273+
"config_path": "/etc/noobaa.conf.d/connections/notif_valid.json"
274+
}
275+
]
276+
},
277+
"notif_storage_threshold_details": {
278+
"threshold": 0.2,
279+
"ratio": 0.9,
280+
"result": "above threshold"
281+
}
242282
"config_directory": {
243283
"phase": "CONFIG_DIR_UNLOCKED",
244284
"config_dir_version": "1.0.0",

docs/NooBaaNonContainerized/NooBaaCLI.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,16 @@ noobaa-cli connection add --from_file
569569
- Type: Object
570570
- Description: An object given as options to node http(s) request. If "auth" field is specified, it's value is encrypted.
571571

572+
- `kafka_options_object`
573+
- Type: Object
574+
- Description: An object given as options to kafka client.
575+
Options are listed in https://github.com/edenhill/librdkafka/blob/v2.8.0/CONFIGURATION.md.
576+
Specifically, 'metadata.broker.list' is used to specify the external kafka server.
577+
578+
- `topic`
579+
- Type: String
580+
- Description - Topic for kafka messages.
581+
572582
- `from_file`
573583
- Type: String
574584
- Description: Path to a JSON file which includes connection properties. When using `from_file` flag the connection details must only appear inside the options JSON file. See example below.

docs/bucket-notifications.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Bucket Notifications
2+
3+
## Bucket Notification Configuration
4+
5+
Bucket's notifications can be configured with the s3api operation [put-bucket-notification-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-notification-configuration.html).
6+
Specify notifications under the "TopicConfigurations" field, which is an array of jsons, one for each notification.
7+
A notification json has these fields:
8+
- Id: Mandatory. A unique string identifying the notification configuration.
9+
- Events: Optional. An array of [events](https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-event-types-and-destinations.html) for which the notification is relevant.
10+
If not specified, the notification is relevant for all events.
11+
- TopicArn: The connection file (see below). (To specify a Kafka target topic, see "Kafka Connection Fields" below).
12+
13+
Example for a bucket's notification configuration, in a file:
14+
{
15+
"TopicConfigurations": [
16+
{
17+
"Id": "created_from_s3op",
18+
"TopicArn": "secret-name/connect.json",
19+
"Events": [
20+
"s3:ObjectCreated:*"
21+
]
22+
}
23+
]
24+
}
25+
26+
27+
## Connection File
28+
A connection file contains some fields that specify the target notification server.
29+
The connection file name is specified in TopicArn field of the [notification configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-notification-configuration.html)
30+
31+
-In a containerized environment, the operator will mount the secrets as file in the core pod (see operator doc for more info).
32+
The mount path is `/etc/notif_connect/<secret-name>/<secret failename>`, and the notification reference the file by the `<secret-name>/<secret filename>` path in TopicArn field.
33+
For example, if seceret was created with:
34+
`kubectl create secret generic notif-secret --from_file connect.json`
35+
Then TopicArn should be `notif-secret/connect.json`.
36+
37+
-In a non-containerized system, you must create the relevant file using the 'connections' CRUD cli.
38+
See the NC cli doc for more info.
39+
Connect file contains a single json with the fields specified below.
40+
41+
### Common Connection Fields
42+
- name: A string identifying the connection (mandatory).
43+
- notification_protocol: One of: http, https, kafka (mandatory).
44+
45+
### Http(s) Connection Fields
46+
- agent_request_object: Value is a JSON that is passed to to nodejs' http(s) agent (mandatory).
47+
Any field supported by nodejs' http(s) agent can be used, specifically 'host' and 'port'.
48+
A full list of options is [here](https://nodejs.org/docs/latest-v22.x/api/http.html#new-agentoptions).
49+
Notable fields include:
50+
- host: hostname (or ip) of external server to receive the notifications
51+
- port: http(s) port on which the external server listens
52+
- timeout: connection timeout in milliseconds.
53+
- local_file_ca - path to CA pem file that signed server's TLS cert (if CA needs to be customized)
54+
- rejectUnauthorized - set to true to accept self-signed certs (useful for testing, do not use in production).
55+
- local_file_cert: path to client's private TLS certificate PEM file.
56+
- local_file_key: path to client's private TLS key PEM file.
57+
58+
- request_options_object: Value is a JSON that is passed to nodejs' http(s) request (optional).
59+
Any field supported by nodejs' http(s) request option can be used, specifically:
60+
-- path: used to specify the url path
61+
-- auth: used for http simple auth. Value for 'auth' is of the syntax: <name>:<passowrd>.
62+
63+
A full list of options is [here](https://nodejs.org/docs/latest-v22.x/api/http.html#httprequesturl-options-callback).
64+
65+
### Kafka Connection Fields
66+
- metadata.broker.list: A CSV list of Kafka brokers (mandatory).
67+
- topic: A topic for the Kafka message (mandatory).
68+
69+
## Event Types
70+
S3 spec lists several events and "sub events".
71+
72+
The list of supported events are:
73+
74+
- s3:ObjectCreated:*
75+
- s3:ObjectCreated:Put
76+
- s3:ObjectCreated:Post
77+
- s3:ObjectCreated:Copy
78+
- s3:ObjectCreated:CompleteMultipartUpload
79+
- s3:ObjectRemoved:*
80+
- s3:ObjectRemoved:Delete
81+
- s3:ObjectRemoved:DeleteMarkerCreated
82+
- s3:ObjectRestore:*
83+
- s3:ObjectRestore:Post
84+
- s3:ObjectRestore:Completed
85+
- s3:ObjectRestore:Delete
86+
- s3:ObjectTagging:*
87+
- s3:ObjectTagging:Put
88+
- s3:ObjectTagging:Delete
89+
- s3:LifecycleExpiration:*
90+
- s3:LifecycleExpiration:Delete
91+
- s3:LifecycleExpiration:DeleteMarkerCreated
92+
93+
## Event Processing and Failure Handling
94+
Once NooBaa finds an event with a relevant notification configuration, the notification
95+
is written to a persistent file.
96+
Location of persistent files is determined by-
97+
- For containerized, the pvc specified in NooBaa Bucket Notification spec (see Operator docs for more info).
98+
- For NC, the env variable NOTIFICATION_LOG_DIR (see NC docs for more info).
99+
100+
Files are processed by-
101+
- For containerized, files are contantly being processed in the background of the core pod.
102+
- For NC, files are processed by running manage_nsfs with 'notification' action.
103+
104+
If a notification fails to be sent to the external server, it will be re-written to a persistent file (assuming the
105+
notification is still configured).
106+
Once this new file is processed, NooBaa will try to re-send the failed notification.

src/cmd/manage_nsfs.js

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -758,40 +758,48 @@ async function notification_management() {
758758
async function connection_management(action, user_input) {
759759
manage_nsfs_validations.validate_connection_args(user_input, action);
760760

761+
//don't reply the internal '_' field.
762+
delete user_input._;
763+
761764
let response = {};
762765
let data;
763766

764-
switch (action) {
765-
case ACTIONS.ADD:
766-
data = await notifications_util.add_connect_file(user_input, config_fs);
767-
response = { code: ManageCLIResponse.ConnectionCreated, detail: data };
768-
break;
769-
case ACTIONS.DELETE:
770-
await config_fs.delete_connection_config_file(user_input.name);
771-
response = { code: ManageCLIResponse.ConnectionDeleted, detail: {name: user_input.name} };
772-
break;
773-
case ACTIONS.UPDATE:
774-
await notifications_util.update_connect_file(user_input.name, user_input.key,
775-
user_input.value, user_input.remove_key, config_fs);
776-
response = { code: ManageCLIResponse.ConnectionUpdated, detail: {name: user_input.name} };
777-
break;
778-
case ACTIONS.STATUS:
779-
data = await new notifications_util.Notificator({
780-
fs_context: config_fs.fs_context,
781-
connect_files_dir: config_fs.connections_dir_path,
782-
nc_config_fs: config_fs,
783-
}).parse_connect_file(user_input.name, user_input.decrypt);
784-
response = { code: ManageCLIResponse.ConnectionStatus, detail: data };
785-
break;
786-
case ACTIONS.LIST:
787-
data = await list_connections();
788-
response = { code: ManageCLIResponse.ConnectionList, detail: data };
789-
break;
790-
default:
791-
throw_cli_error(ManageCLIError.InvalidAction);
767+
try {
768+
switch (action) {
769+
case ACTIONS.ADD:
770+
data = await notifications_util.add_connect_file(user_input, config_fs);
771+
response = { code: ManageCLIResponse.ConnectionCreated, detail: data };
772+
break;
773+
case ACTIONS.DELETE:
774+
await config_fs.delete_connection_config_file(user_input.name);
775+
response = { code: ManageCLIResponse.ConnectionDeleted, detail: {name: user_input.name} };
776+
break;
777+
case ACTIONS.UPDATE:
778+
await notifications_util.update_connect_file(user_input.name, user_input.key,
779+
user_input.value, user_input.remove_key, config_fs);
780+
response = { code: ManageCLIResponse.ConnectionUpdated, detail: {name: user_input.name} };
781+
break;
782+
case ACTIONS.STATUS:
783+
data = await new notifications_util.Notificator({
784+
fs_context: config_fs.fs_context,
785+
connect_files_dir: config_fs.connections_dir_path,
786+
nc_config_fs: config_fs,
787+
}).parse_connect_file(user_input.name, user_input.decrypt);
788+
response = { code: ManageCLIResponse.ConnectionStatus, detail: data };
789+
break;
790+
case ACTIONS.LIST:
791+
data = await list_connections();
792+
response = { code: ManageCLIResponse.ConnectionList, detail: data };
793+
break;
794+
default:
795+
throw_cli_error(ManageCLIError.InvalidAction);
796+
}
797+
write_stdout_response(response.code, response.detail, response.event_arg);
798+
} catch (err) {
799+
if (err.code === 'EEXIST') throw_cli_error(ManageCLIError.ConnectionAlreadyExists, user_input.name);
800+
if (err.code === 'ENOENT') throw_cli_error(ManageCLIError.NoSuchConnection, user_input.name);
801+
throw err;
792802
}
793-
794-
write_stdout_response(response.code, response.detail, response.event_arg);
795803
}
796804

797805
////////////////////

src/endpoint/s3/ops/s3_put_object.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ async function put_object(req, res) {
6565
}
6666
s3_utils.set_encryption_response_headers(req, res, reply.encryption);
6767

68+
res.size_for_notif = size || reply.size;
69+
6870
if (copy_source) {
6971
// TODO: This needs to be checked regarding copy between diff namespaces
7072
// In that case we do not have the copy_source property and just read and upload the stream

src/endpoint/s3/s3_bucket_logging.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const http_utils = require('../../util/http_utils');
66
const dgram = require('node:dgram');
77
const { Buffer } = require('node:buffer');
88
const config = require('../../../config');
9-
const {compose_notification_req, check_notif_relevant, check_free_space} = require('../../util/notifications_util');
9+
const {compose_notification_req, check_notif_relevant, check_free_space_if_needed} = require('../../util/notifications_util');
1010

1111
async function send_bucket_op_logs(req, res) {
1212
if (req.params && req.params.bucket &&
@@ -44,7 +44,7 @@ async function send_bucket_op_logs(req, res) {
4444
buffer: JSON.stringify(notif)
4545
});
4646

47-
check_free_space(req);
47+
check_free_space_if_needed(req);
4848
}
4949
}
5050
}

0 commit comments

Comments
 (0)