mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-03-14 00:06:39 +00:00
Add first fuota storage functions / API.
This commit is contained in:
parent
0a976c82f4
commit
dae5ba6802
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -902,6 +902,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"validator",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
@ -3439,6 +3440,28 @@ dependencies = [
|
||||
"toml_edit 0.22.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
@ -5290,6 +5313,36 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"url",
|
||||
"validator_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_derive"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"once_cell",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
334
api/proto/api/fuota.proto
vendored
Normal file
334
api/proto/api/fuota.proto
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
|
||||
option java_package = "io.chirpstack.api";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "FuotaProto";
|
||||
option csharp_namespace = "Chirpstack.Api";
|
||||
option php_namespace = "Chirpstack\\Api";
|
||||
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "common/common.proto";
|
||||
import "api/multicast_group.proto";
|
||||
|
||||
// FuotaService is the service providing API methods for FUOTA deployments.
|
||||
service FuotaService {
|
||||
// Create the given FUOTA deployment.
|
||||
rpc CreateDeployment(CreateFuotaDeploymentRequest) returns (CreateFuotaDeploymentResponse) {}
|
||||
|
||||
// Get the FUOTA deployment for the given ID.
|
||||
rpc GetDeployment(GetFuotaDeploymentRequest) returns (GetFuotaDeploymentResponse) {}
|
||||
|
||||
// Update the given FUOTA deployment.
|
||||
rpc UpdateDeployment(UpdateFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Delete the FUOTA deployment for the given ID.
|
||||
rpc DeleteDeployment(DeleteFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List the FUOTA deployments.
|
||||
rpc ListDeployments(ListFuotaDeploymentsRequest) returns (ListFuotaDeploymentsResponse) {}
|
||||
|
||||
// Add the given DevEUIs to the FUOTA deployment.
|
||||
rpc AddDevices(AddDevicesToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Remove the given DevEUIs from the FUOTA deployment.
|
||||
rpc RemoveDevices(RemoveDevicesFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List FUOTA Deployment devices.
|
||||
rpc ListDevices(ListFuotaDeploymentDevicesRequest) returns (ListFuotaDeploymentDevicesResponse) {}
|
||||
|
||||
// Add the given Gateway IDs to the FUOTA deployment.
|
||||
// By default, ChirpStack will automatically select the minimum amount of
|
||||
// gateways needed to cover all devices within the multicast-group. Setting
|
||||
// the gateways manually overrides this behaviour.
|
||||
rpc AddGateways(AddGatewaysToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List the gateways added to the FUOTA deployment.
|
||||
rpc ListGateways(ListFuotaDeploymentGatewaysRequest) returns (ListFuotaDeploymentGatewaysResponse) {}
|
||||
|
||||
// Remove the given Gateway IDs from the FUOTA deployment.
|
||||
rpc RemoveGateways(RemoveGatewaysFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
// GetLogs returns the logs for the FUOTA deployment.
|
||||
}
|
||||
|
||||
enum RequestFragmentationSessionStatus {
|
||||
// Do not request the fragmentation-session status.
|
||||
NO_REQUEST = 0;
|
||||
|
||||
// Enqueue the fragmentation-session status request command directly after
|
||||
// enqueueing the fragmentation-session fragments. This is the recommended
|
||||
// option for Class-A devices as the status request will stay in the
|
||||
// downlink queue until the device sends its next uplink.
|
||||
AFTER_FRAGMENT_ENQUEUE = 1;
|
||||
|
||||
// Enqueue the fragmentation-session status request after the multicast
|
||||
// session-timeout. This is the recommended option for Class-B and -C
|
||||
// devices as selecting AFTER_FRAGMENT_ENQUEUE will likely cause the NS
|
||||
// to schedule the downlink frame during the FUOTA multicast-session.
|
||||
AFTER_SESSION_TIMEOUT = 2;
|
||||
|
||||
}
|
||||
|
||||
message FuotaDeployment {
|
||||
// Deployment ID.
|
||||
// This value is automatically set on create.
|
||||
string id = 1;
|
||||
|
||||
// Application ID.
|
||||
string application_id = 2;
|
||||
|
||||
// Device-profile ID.
|
||||
string device_profile_id = 3;
|
||||
|
||||
// Deployment name.
|
||||
string name = 4;
|
||||
|
||||
// Multicast-group type.
|
||||
MulticastGroupType multicast_group_type = 5;
|
||||
|
||||
// Multicast-group scheduling type (Class-C only).
|
||||
MulticastGroupSchedulingType multicast_class_c_scheduling_type = 6;
|
||||
|
||||
// Multicast data-rate.
|
||||
uint32 multicast_dr = 7;
|
||||
|
||||
// Multicast ping-slot period (Class-B only).
|
||||
uint32 multicast_class_b_ping_slot_nb_k = 8;
|
||||
|
||||
// Multicast frequency (Hz).
|
||||
uint32 multicast_frequency = 9;
|
||||
|
||||
// Multicast timeout.
|
||||
// This defines the timeout of the multicast-session.
|
||||
// Please refer to the Remote Multicast Setup specification as this field
|
||||
// has a different meaning for Class-B and Class-C groups.
|
||||
uint32 multicast_timeout = 10;
|
||||
|
||||
// Unicast attempt count.
|
||||
// The number of attempts before considering an unicast command
|
||||
// to be failed.
|
||||
uint32 unicast_attempt_count = 11;
|
||||
|
||||
// Fragmentation size.
|
||||
// This defines the size of each payload fragment. Please refer to the
|
||||
// Regional Parameters specification for the maximum payload sizes
|
||||
// per data-rate and region.
|
||||
uint32 fragmentation_fragment_size = 12;
|
||||
|
||||
// Fragmentation redundancy.
|
||||
// The number represents the additional redundant frames to send.
|
||||
uint32 fragmentation_redundancy = 13;
|
||||
|
||||
// Fragmentation session index.
|
||||
uint32 fragmentation_session_index = 14;
|
||||
|
||||
// Fragmentation matrix.
|
||||
uint32 fragmentation_matrix = 15;
|
||||
|
||||
// Block ack delay.
|
||||
uint32 fragmentation_block_ack_delay = 16;
|
||||
|
||||
// Descriptor (4 bytes).
|
||||
bytes fragmentation_descriptor = 17;
|
||||
|
||||
// Request fragmentation session status.
|
||||
RequestFragmentationSessionStatus request_fragmentation_session_status = 18;
|
||||
|
||||
// Payload.
|
||||
// The FUOTA payload to send.
|
||||
bytes payload = 19;
|
||||
}
|
||||
|
||||
message FuotaDeploymentListItem {
|
||||
// ID.
|
||||
string id = 1;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 2;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
|
||||
// Started at timestamp.
|
||||
google.protobuf.Timestamp started_at = 4;
|
||||
|
||||
// Completed at timestamp.
|
||||
google.protobuf.Timestamp completed_at = 5;
|
||||
|
||||
// Name.
|
||||
string name = 6;
|
||||
}
|
||||
|
||||
message FuotaDeploymentDeviceListItem {
|
||||
// ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUI.
|
||||
string dev_eui = 2;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 4;
|
||||
|
||||
// McGroupSetup completed at timestamp.
|
||||
google.protobuf.Timestamp mc_group_setup_completed_at = 5;
|
||||
|
||||
// McSession completed at timestamp.
|
||||
google.protobuf.Timestamp mc_session_completed_at = 6;
|
||||
|
||||
// FragSessionSetup completed at timestamp.
|
||||
google.protobuf.Timestamp frag_session_setup_completed_at = 7;
|
||||
|
||||
// FragStatus completed at timestamp.
|
||||
google.protobuf.Timestamp frag_status_completed_at = 8;
|
||||
}
|
||||
|
||||
message FuotaDeploymentGatewayListItem {
|
||||
// ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway ID.
|
||||
string gateway_id = 2;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
}
|
||||
|
||||
message CreateFuotaDeploymentRequest {
|
||||
// Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
}
|
||||
|
||||
message CreateFuotaDeploymentResponse {
|
||||
// ID of the created deployment.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message GetFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message GetFuotaDeploymentResponse {
|
||||
// FUOTA Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 2;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
}
|
||||
|
||||
message UpdateFuotaDeploymentRequest {
|
||||
// Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
}
|
||||
|
||||
message DeleteFuotaDeploymentRequest {
|
||||
// FUOTA deployment ID.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentsRequest {
|
||||
// Max number of FUOTA deployments to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// Application ID to list the FUOTA Deployments for.
|
||||
// This filter is mandatory.
|
||||
string application_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentsResponse {
|
||||
// Total number of FUOTA Deployments.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-test.
|
||||
repeated FuotaDeploymentListItem result = 2;
|
||||
}
|
||||
|
||||
message AddDevicesToFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUIs.
|
||||
// Note that the DevEUIs must share the same device-profile as assigned to
|
||||
// the FUOTA Deployment.
|
||||
repeated string dev_euis = 2;
|
||||
}
|
||||
|
||||
message RemoveDevicesFromFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUIs.
|
||||
repeated string dev_euis = 2;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentDevicesRequest {
|
||||
// Max number of devices to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentDevicesResponse {
|
||||
// Total number of devices.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-set.
|
||||
repeated FuotaDeploymentDeviceListItem result = 2;
|
||||
}
|
||||
|
||||
message AddGatewaysToFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway IDs.
|
||||
// Note that the Gateways must be under the same tenant as the FUOTA Deployment.
|
||||
repeated string gateway_ids = 2;
|
||||
}
|
||||
|
||||
message RemoveGatewaysFromFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway IDs.
|
||||
repeated string gateway_ids = 2;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentGatewaysRequest {
|
||||
// Max number of gateways to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentGatewaysResponse {
|
||||
// Total number of gateways.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-set.
|
||||
repeated FuotaDeploymentGatewayListItem result = 2;
|
||||
}
|
1
api/rust/build.rs
vendored
1
api/rust/build.rs
vendored
@ -215,6 +215,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
cs_dir.join("api").join("relay.proto").to_str().unwrap(),
|
||||
cs_dir.join("api").join("fuota.proto").to_str().unwrap(),
|
||||
],
|
||||
&[
|
||||
proto_dir.join("chirpstack").to_str().unwrap(),
|
||||
|
334
api/rust/proto/chirpstack/api/fuota.proto
vendored
Normal file
334
api/rust/proto/chirpstack/api/fuota.proto
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
|
||||
option java_package = "io.chirpstack.api";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "FuotaProto";
|
||||
option csharp_namespace = "Chirpstack.Api";
|
||||
option php_namespace = "Chirpstack\\Api";
|
||||
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "common/common.proto";
|
||||
import "api/multicast_group.proto";
|
||||
|
||||
// FuotaService is the service providing API methods for FUOTA deployments.
|
||||
service FuotaService {
|
||||
// Create the given FUOTA deployment.
|
||||
rpc CreateDeployment(CreateFuotaDeploymentRequest) returns (CreateFuotaDeploymentResponse) {}
|
||||
|
||||
// Get the FUOTA deployment for the given ID.
|
||||
rpc GetDeployment(GetFuotaDeploymentRequest) returns (GetFuotaDeploymentResponse) {}
|
||||
|
||||
// Update the given FUOTA deployment.
|
||||
rpc UpdateDeployment(UpdateFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Delete the FUOTA deployment for the given ID.
|
||||
rpc DeleteDeployment(DeleteFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List the FUOTA deployments.
|
||||
rpc ListDeployments(ListFuotaDeploymentsRequest) returns (ListFuotaDeploymentsResponse) {}
|
||||
|
||||
// Add the given DevEUIs to the FUOTA deployment.
|
||||
rpc AddDevices(AddDevicesToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// Remove the given DevEUIs from the FUOTA deployment.
|
||||
rpc RemoveDevices(RemoveDevicesFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List FUOTA Deployment devices.
|
||||
rpc ListDevices(ListFuotaDeploymentDevicesRequest) returns (ListFuotaDeploymentDevicesResponse) {}
|
||||
|
||||
// Add the given Gateway IDs to the FUOTA deployment.
|
||||
// By default, ChirpStack will automatically select the minimum amount of
|
||||
// gateways needed to cover all devices within the multicast-group. Setting
|
||||
// the gateways manually overrides this behaviour.
|
||||
rpc AddGateways(AddGatewaysToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
// List the gateways added to the FUOTA deployment.
|
||||
rpc ListGateways(ListFuotaDeploymentGatewaysRequest) returns (ListFuotaDeploymentGatewaysResponse) {}
|
||||
|
||||
// Remove the given Gateway IDs from the FUOTA deployment.
|
||||
rpc RemoveGateways(RemoveGatewaysFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
|
||||
// GetLogs returns the logs for the FUOTA deployment.
|
||||
}
|
||||
|
||||
enum RequestFragmentationSessionStatus {
|
||||
// Do not request the fragmentation-session status.
|
||||
NO_REQUEST = 0;
|
||||
|
||||
// Enqueue the fragmentation-session status request command directly after
|
||||
// enqueueing the fragmentation-session fragments. This is the recommended
|
||||
// option for Class-A devices as the status request will stay in the
|
||||
// downlink queue until the device sends its next uplink.
|
||||
AFTER_FRAGMENT_ENQUEUE = 1;
|
||||
|
||||
// Enqueue the fragmentation-session status request after the multicast
|
||||
// session-timeout. This is the recommended option for Class-B and -C
|
||||
// devices as selecting AFTER_FRAGMENT_ENQUEUE will likely cause the NS
|
||||
// to schedule the downlink frame during the FUOTA multicast-session.
|
||||
AFTER_SESSION_TIMEOUT = 2;
|
||||
|
||||
}
|
||||
|
||||
message FuotaDeployment {
|
||||
// Deployment ID.
|
||||
// This value is automatically set on create.
|
||||
string id = 1;
|
||||
|
||||
// Application ID.
|
||||
string application_id = 2;
|
||||
|
||||
// Device-profile ID.
|
||||
string device_profile_id = 3;
|
||||
|
||||
// Deployment name.
|
||||
string name = 4;
|
||||
|
||||
// Multicast-group type.
|
||||
MulticastGroupType multicast_group_type = 5;
|
||||
|
||||
// Multicast-group scheduling type (Class-C only).
|
||||
MulticastGroupSchedulingType multicast_class_c_scheduling_type = 6;
|
||||
|
||||
// Multicast data-rate.
|
||||
uint32 multicast_dr = 7;
|
||||
|
||||
// Multicast ping-slot period (Class-B only).
|
||||
uint32 multicast_class_b_ping_slot_nb_k = 8;
|
||||
|
||||
// Multicast frequency (Hz).
|
||||
uint32 multicast_frequency = 9;
|
||||
|
||||
// Multicast timeout.
|
||||
// This defines the timeout of the multicast-session.
|
||||
// Please refer to the Remote Multicast Setup specification as this field
|
||||
// has a different meaning for Class-B and Class-C groups.
|
||||
uint32 multicast_timeout = 10;
|
||||
|
||||
// Unicast attempt count.
|
||||
// The number of attempts before considering an unicast command
|
||||
// to be failed.
|
||||
uint32 unicast_attempt_count = 11;
|
||||
|
||||
// Fragmentation size.
|
||||
// This defines the size of each payload fragment. Please refer to the
|
||||
// Regional Parameters specification for the maximum payload sizes
|
||||
// per data-rate and region.
|
||||
uint32 fragmentation_fragment_size = 12;
|
||||
|
||||
// Fragmentation redundancy.
|
||||
// The number represents the additional redundant frames to send.
|
||||
uint32 fragmentation_redundancy = 13;
|
||||
|
||||
// Fragmentation session index.
|
||||
uint32 fragmentation_session_index = 14;
|
||||
|
||||
// Fragmentation matrix.
|
||||
uint32 fragmentation_matrix = 15;
|
||||
|
||||
// Block ack delay.
|
||||
uint32 fragmentation_block_ack_delay = 16;
|
||||
|
||||
// Descriptor (4 bytes).
|
||||
bytes fragmentation_descriptor = 17;
|
||||
|
||||
// Request fragmentation session status.
|
||||
RequestFragmentationSessionStatus request_fragmentation_session_status = 18;
|
||||
|
||||
// Payload.
|
||||
// The FUOTA payload to send.
|
||||
bytes payload = 19;
|
||||
}
|
||||
|
||||
message FuotaDeploymentListItem {
|
||||
// ID.
|
||||
string id = 1;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 2;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
|
||||
// Started at timestamp.
|
||||
google.protobuf.Timestamp started_at = 4;
|
||||
|
||||
// Completed at timestamp.
|
||||
google.protobuf.Timestamp completed_at = 5;
|
||||
|
||||
// Name.
|
||||
string name = 6;
|
||||
}
|
||||
|
||||
message FuotaDeploymentDeviceListItem {
|
||||
// ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUI.
|
||||
string dev_eui = 2;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 4;
|
||||
|
||||
// McGroupSetup completed at timestamp.
|
||||
google.protobuf.Timestamp mc_group_setup_completed_at = 5;
|
||||
|
||||
// McSession completed at timestamp.
|
||||
google.protobuf.Timestamp mc_session_completed_at = 6;
|
||||
|
||||
// FragSessionSetup completed at timestamp.
|
||||
google.protobuf.Timestamp frag_session_setup_completed_at = 7;
|
||||
|
||||
// FragStatus completed at timestamp.
|
||||
google.protobuf.Timestamp frag_status_completed_at = 8;
|
||||
}
|
||||
|
||||
message FuotaDeploymentGatewayListItem {
|
||||
// ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway ID.
|
||||
string gateway_id = 2;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
}
|
||||
|
||||
message CreateFuotaDeploymentRequest {
|
||||
// Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
}
|
||||
|
||||
message CreateFuotaDeploymentResponse {
|
||||
// ID of the created deployment.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message GetFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message GetFuotaDeploymentResponse {
|
||||
// FUOTA Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
|
||||
// Created at timestamp.
|
||||
google.protobuf.Timestamp created_at = 2;
|
||||
|
||||
// Updated at timestamp.
|
||||
google.protobuf.Timestamp updated_at = 3;
|
||||
}
|
||||
|
||||
message UpdateFuotaDeploymentRequest {
|
||||
// Deployment.
|
||||
FuotaDeployment deployment = 1;
|
||||
}
|
||||
|
||||
message DeleteFuotaDeploymentRequest {
|
||||
// FUOTA deployment ID.
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentsRequest {
|
||||
// Max number of FUOTA deployments to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// Application ID to list the FUOTA Deployments for.
|
||||
// This filter is mandatory.
|
||||
string application_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentsResponse {
|
||||
// Total number of FUOTA Deployments.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-test.
|
||||
repeated FuotaDeploymentListItem result = 2;
|
||||
}
|
||||
|
||||
message AddDevicesToFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUIs.
|
||||
// Note that the DevEUIs must share the same device-profile as assigned to
|
||||
// the FUOTA Deployment.
|
||||
repeated string dev_euis = 2;
|
||||
}
|
||||
|
||||
message RemoveDevicesFromFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// DevEUIs.
|
||||
repeated string dev_euis = 2;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentDevicesRequest {
|
||||
// Max number of devices to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentDevicesResponse {
|
||||
// Total number of devices.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-set.
|
||||
repeated FuotaDeploymentDeviceListItem result = 2;
|
||||
}
|
||||
|
||||
message AddGatewaysToFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway IDs.
|
||||
// Note that the Gateways must be under the same tenant as the FUOTA Deployment.
|
||||
repeated string gateway_ids = 2;
|
||||
}
|
||||
|
||||
message RemoveGatewaysFromFuotaDeploymentRequest {
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 1;
|
||||
|
||||
// Gateway IDs.
|
||||
repeated string gateway_ids = 2;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentGatewaysRequest {
|
||||
// Max number of gateways to return in the result-set.
|
||||
// If not set, it will be treated as 0, and the response will only return the total_count.
|
||||
uint32 limit = 1;
|
||||
|
||||
// Offset in the result-set (for pagination).
|
||||
uint32 offset = 2;
|
||||
|
||||
// FUOTA Deployment ID.
|
||||
string fuota_deployment_id = 3;
|
||||
}
|
||||
|
||||
message ListFuotaDeploymentGatewaysResponse {
|
||||
// Total number of gateways.
|
||||
uint32 total_count = 1;
|
||||
|
||||
// Result-set.
|
||||
repeated FuotaDeploymentGatewayListItem result = 2;
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
humantime-serde = "1.1"
|
||||
toml = "0.8"
|
||||
handlebars = "6.2"
|
||||
validator = { version = "0.20", features = ["derive"] }
|
||||
|
||||
# Database
|
||||
email_address = "0.2"
|
||||
|
@ -0,0 +1,3 @@
|
||||
drop table fuota_deployment_gateway;
|
||||
drop table fuota_deployment_device;
|
||||
drop table fuota_deployment;
|
@ -0,0 +1,47 @@
|
||||
create table fuota_deployment (
|
||||
id uuid primary key,
|
||||
created_at timestamp with time zone not null,
|
||||
updated_at timestamp with time zone not null,
|
||||
started_at timestamp with time zone null,
|
||||
completed_at timestamp with time zone null,
|
||||
name varchar(100) not null,
|
||||
application_id uuid not null references application on delete cascade,
|
||||
device_profile_id uuid not null references device_profile on delete cascade,
|
||||
multicast_group_type char(1) not null,
|
||||
multicast_class_c_scheduling_type varchar(20) not null,
|
||||
multicast_dr smallint not null,
|
||||
multicast_class_b_ping_slot_nb_k smallint not null,
|
||||
multicast_frequency bigint not null,
|
||||
multicast_timeout smallint not null,
|
||||
unicast_attempt_count smallint not null,
|
||||
fragmentation_fragment_size smallint not null,
|
||||
fragmentation_redundancy smallint not null,
|
||||
fragmentation_session_index smallint not null,
|
||||
fragmentation_matrix smallint not null,
|
||||
fragmentation_block_ack_delay smallint not null,
|
||||
fragmentation_descriptor bytea not null,
|
||||
request_fragmentation_session_status varchar(20) not null,
|
||||
payload bytea not null
|
||||
);
|
||||
|
||||
create table fuota_deployment_device (
|
||||
fuota_deployment_id uuid not null references fuota_deployment on delete cascade,
|
||||
dev_eui bytea not null references device on delete cascade,
|
||||
created_at timestamp with time zone not null,
|
||||
updated_at timestamp with time zone not null,
|
||||
|
||||
mc_group_setup_completed_at timestamp with time zone null,
|
||||
mc_session_completed_at timestamp with time zone null,
|
||||
frag_session_setup_completed_at timestamp with time zone null,
|
||||
frag_status_completed_at timestamp with time zone null,
|
||||
|
||||
primary key (fuota_deployment_id, dev_eui)
|
||||
);
|
||||
|
||||
create table fuota_deployment_gateway (
|
||||
fuota_deployment_id uuid not null references fuota_deployment on delete cascade,
|
||||
gateway_id bytea not null references gateway on delete cascade,
|
||||
created_at timestamp with time zone not null,
|
||||
|
||||
primary key (fuota_deployment_id, gateway_id)
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
drop table fuota_deployment_gateway;
|
||||
drop table fuota_deployment_device;
|
||||
drop table fuota_deployment;
|
@ -0,0 +1,47 @@
|
||||
create table fuota_deployment (
|
||||
id text not null primary key,
|
||||
created_at datetime not null,
|
||||
updated_at datetime not null,
|
||||
started_at datetime null,
|
||||
completed_at datetime null,
|
||||
name varchar(100) not null,
|
||||
application_id text not null references application on delete cascade,
|
||||
device_profile_id text not null references device_profile on delete cascade,
|
||||
multicast_group_type char(1) not null,
|
||||
multicast_class_c_scheduling_type varchar(20) not null,
|
||||
multicast_dr smallint not null,
|
||||
multicast_class_b_ping_slot_nb_k smallint not null,
|
||||
multicast_frequency bigint not null,
|
||||
multicast_timeout smallint not null,
|
||||
unicast_attempt_count smallint not null,
|
||||
fragmentation_fragment_size smallint not null,
|
||||
fragmentation_redundancy smallint not null,
|
||||
fragmentation_session_index smallint not null,
|
||||
fragmentation_matrix smallint not null,
|
||||
fragmentation_block_ack_delay smallint not null,
|
||||
fragmentation_descriptor blob not null,
|
||||
request_fragmentation_session_status varchar(20) not null,
|
||||
payload blob not null
|
||||
);
|
||||
|
||||
create table fuota_deployment_device (
|
||||
fuota_deployment_id text not null references fuota_deployment on delete cascade,
|
||||
dev_eui blob not null references device on delete cascade,
|
||||
created_at datetime not null,
|
||||
updated_at datetime not null,
|
||||
|
||||
mc_group_setup_completed_at datetime null,
|
||||
mc_session_completed_at datetime null,
|
||||
frag_session_setup_completed_at datetime null,
|
||||
frag_status_completed_at datetime null,
|
||||
|
||||
primary key (fuota_deployment_id, dev_eui)
|
||||
);
|
||||
|
||||
create table fuota_deployment_gateway (
|
||||
fuota_deployment_id text not null references fuota_deployment on delete cascade,
|
||||
gateway_id blob not null references gateway on delete cascade,
|
||||
created_at datetime not null,
|
||||
|
||||
primary key (fuota_deployment_id, gateway_id)
|
||||
);
|
@ -12,7 +12,8 @@ use super::error::Error;
|
||||
use crate::api::auth::AuthID;
|
||||
use crate::helpers::errors::PrintFullError;
|
||||
use crate::storage::schema::{
|
||||
api_key, application, device, device_profile, gateway, multicast_group, tenant_user, user,
|
||||
api_key, application, device, device_profile, fuota_deployment, gateway, multicast_group,
|
||||
tenant_user, user,
|
||||
};
|
||||
use crate::storage::{fields, get_async_db_conn};
|
||||
|
||||
@ -2032,11 +2033,227 @@ impl Validator for ValidateMulticastGroupQueueAccess {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValidateFuotaDeploymentsAccess {
|
||||
flag: Flag,
|
||||
application_id: Uuid,
|
||||
}
|
||||
|
||||
impl ValidateFuotaDeploymentsAccess {
|
||||
pub fn new(flag: Flag, application_id: Uuid) -> Self {
|
||||
ValidateFuotaDeploymentsAccess {
|
||||
flag,
|
||||
application_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Validator for ValidateFuotaDeploymentsAccess {
|
||||
async fn validate_user(&self, id: &Uuid) -> Result<i64, Error> {
|
||||
let mut q = user::dsl::user
|
||||
.select(dsl::count_star())
|
||||
.filter(
|
||||
user::dsl::id
|
||||
.eq(fields::Uuid::from(id))
|
||||
.and(user::dsl::is_active.eq(true)),
|
||||
)
|
||||
.into_boxed();
|
||||
|
||||
match self.flag {
|
||||
// admin user
|
||||
// tenant admin
|
||||
// tenant device admin
|
||||
Flag::Create => {
|
||||
q =
|
||||
q.filter(
|
||||
user::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
application::dsl::application
|
||||
.inner_join(tenant_user::table.on(
|
||||
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
|
||||
))
|
||||
.filter(
|
||||
application::dsl::id
|
||||
.eq(fields::Uuid::from(self.application_id))
|
||||
.and(tenant_user::dsl::user_id.eq(user::dsl::id))
|
||||
.and(
|
||||
tenant_user::dsl::is_admin
|
||||
.eq(true)
|
||||
.or(tenant_user::dsl::is_device_admin.eq(true)),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
// admin user
|
||||
// tenant user
|
||||
Flag::List => {
|
||||
q =
|
||||
q.filter(
|
||||
user::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
application::dsl::application
|
||||
.inner_join(tenant_user::table.on(
|
||||
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
|
||||
))
|
||||
.filter(
|
||||
application::dsl::id
|
||||
.eq(fields::Uuid::from(self.application_id))
|
||||
.and(tenant_user::dsl::user_id.eq(user::dsl::id)),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => return Ok(0),
|
||||
}
|
||||
|
||||
Ok(q.first(&mut get_async_db_conn().await?).await?)
|
||||
}
|
||||
|
||||
async fn validate_key(&self, id: &Uuid) -> Result<i64, Error> {
|
||||
let mut q = api_key::dsl::api_key
|
||||
.select(dsl::count_star())
|
||||
.filter(api_key::dsl::id.eq(fields::Uuid::from(id)))
|
||||
.into_boxed();
|
||||
|
||||
match self.flag {
|
||||
// admin api key
|
||||
// tenant api key
|
||||
Flag::Create | Flag::List => {
|
||||
q = q.filter(
|
||||
api_key::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
application::dsl::application.filter(
|
||||
application::dsl::id
|
||||
.eq(fields::Uuid::from(self.application_id))
|
||||
.and(
|
||||
api_key::dsl::tenant_id
|
||||
.eq(application::dsl::tenant_id.nullable()),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(q.first(&mut get_async_db_conn().await?).await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValidateFuotaDeploymentAccess {
|
||||
flag: Flag,
|
||||
fuota_deployment_id: Uuid,
|
||||
}
|
||||
|
||||
impl ValidateFuotaDeploymentAccess {
|
||||
pub fn new(flag: Flag, fuota_deployment_id: Uuid) -> Self {
|
||||
ValidateFuotaDeploymentAccess {
|
||||
flag,
|
||||
fuota_deployment_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Validator for ValidateFuotaDeploymentAccess {
|
||||
async fn validate_user(&self, id: &Uuid) -> Result<i64, Error> {
|
||||
let mut q = user::dsl::user
|
||||
.select(dsl::count_star())
|
||||
.filter(
|
||||
user::dsl::id
|
||||
.eq(fields::Uuid::from(id))
|
||||
.and(user::dsl::is_active.eq(true)),
|
||||
)
|
||||
.into_boxed();
|
||||
|
||||
match self.flag {
|
||||
// admin user
|
||||
// tenant user
|
||||
Flag::Read => {
|
||||
q =
|
||||
q.filter(
|
||||
user::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.inner_join(application::table)
|
||||
.inner_join(tenant_user::table.on(
|
||||
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
|
||||
))
|
||||
.filter(
|
||||
fuota_deployment::dsl::id
|
||||
.eq(fields::Uuid::from(self.fuota_deployment_id))
|
||||
.and(tenant_user::dsl::user_id.eq(user::dsl::id)),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
// admin user
|
||||
// tenant admin
|
||||
// tenant device admin
|
||||
Flag::Update | Flag::Delete => {
|
||||
q =
|
||||
q.filter(
|
||||
user::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.inner_join(application::table)
|
||||
.inner_join(tenant_user::table.on(
|
||||
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
|
||||
))
|
||||
.filter(
|
||||
fuota_deployment::dsl::id
|
||||
.eq(fields::Uuid::from(self.fuota_deployment_id))
|
||||
.and(tenant_user::dsl::user_id.eq(user::dsl::id))
|
||||
.and(
|
||||
tenant_user::dsl::is_admin
|
||||
.eq(true)
|
||||
.or(tenant_user::dsl::is_device_admin.eq(true)),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => return Ok(0),
|
||||
}
|
||||
|
||||
Ok(q.first(&mut get_async_db_conn().await?).await?)
|
||||
}
|
||||
|
||||
async fn validate_key(&self, id: &Uuid) -> Result<i64, Error> {
|
||||
let mut q = api_key::dsl::api_key
|
||||
.select(dsl::count_star())
|
||||
.filter(api_key::dsl::id.eq(fields::Uuid::from(id)))
|
||||
.into_boxed();
|
||||
|
||||
match self.flag {
|
||||
// admin api key
|
||||
// tenant api key
|
||||
Flag::Read | Flag::Update | Flag::Delete => {
|
||||
q = q.filter(
|
||||
api_key::dsl::is_admin.eq(true).or(dsl::exists(
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.inner_join(application::table)
|
||||
.filter(
|
||||
fuota_deployment::dsl::id
|
||||
.eq(fields::Uuid::from(self.fuota_deployment_id))
|
||||
.and(
|
||||
api_key::dsl::tenant_id
|
||||
.eq(application::dsl::tenant_id.nullable()),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => return Ok(0),
|
||||
}
|
||||
|
||||
Ok(q.first(&mut get_async_db_conn().await?).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::storage::{
|
||||
api_key, application, device, device_profile, gateway, multicast, tenant, user,
|
||||
api_key, application, device, device_profile, fuota, gateway, multicast, tenant, user,
|
||||
};
|
||||
use crate::test;
|
||||
use std::str::FromStr;
|
||||
@ -4619,4 +4836,298 @@ pub mod test {
|
||||
];
|
||||
run_tests(tests).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fuota_deployment() {
|
||||
let _guard = test::prepare().await;
|
||||
|
||||
let user_active = user::User {
|
||||
email: "user@user".into(),
|
||||
is_active: true,
|
||||
..Default::default()
|
||||
};
|
||||
let user_admin = user::User {
|
||||
email: "admin@user".into(),
|
||||
is_active: true,
|
||||
is_admin: true,
|
||||
..Default::default()
|
||||
};
|
||||
let tenant_admin = user::User {
|
||||
email: "tenant-admin@user".into(),
|
||||
is_active: true,
|
||||
..Default::default()
|
||||
};
|
||||
let tenant_device_admin = user::User {
|
||||
email: "tenant-device-admin@user".into(),
|
||||
is_active: true,
|
||||
..Default::default()
|
||||
};
|
||||
let tenant_gateway_admin = user::User {
|
||||
email: "tenant-gateway-admin@user".into(),
|
||||
is_active: true,
|
||||
..Default::default()
|
||||
};
|
||||
let tenant_user = user::User {
|
||||
email: "tenant-user@user".into(),
|
||||
is_active: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for u in [
|
||||
&user_active,
|
||||
&user_admin,
|
||||
&tenant_admin,
|
||||
&tenant_gateway_admin,
|
||||
&tenant_device_admin,
|
||||
&tenant_user,
|
||||
] {
|
||||
user::create(u.clone()).await.unwrap();
|
||||
}
|
||||
|
||||
let api_key_admin = api_key::test::create_api_key(true, false).await;
|
||||
let api_key_tenant = api_key::test::create_api_key(false, true).await;
|
||||
let api_key_other_tenant = api_key::test::create_api_key(false, true).await;
|
||||
|
||||
let app =
|
||||
application::test::create_application(Some(api_key_tenant.tenant_id.unwrap().into()))
|
||||
.await;
|
||||
|
||||
tenant::add_user(tenant::TenantUser {
|
||||
tenant_id: api_key_tenant.tenant_id.unwrap(),
|
||||
user_id: tenant_admin.id,
|
||||
is_admin: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
tenant::add_user(tenant::TenantUser {
|
||||
tenant_id: api_key_tenant.tenant_id.unwrap().into(),
|
||||
user_id: tenant_device_admin.id,
|
||||
is_device_admin: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
tenant::add_user(tenant::TenantUser {
|
||||
tenant_id: api_key_tenant.tenant_id.unwrap(),
|
||||
user_id: tenant_gateway_admin.id,
|
||||
is_gateway_admin: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
tenant::add_user(tenant::TenantUser {
|
||||
tenant_id: api_key_tenant.tenant_id.unwrap(),
|
||||
user_id: tenant_user.id,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// fuota deployments with user
|
||||
let tests = vec![
|
||||
// admin user can create and list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::User(user_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant admin can create and list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::User(tenant_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant device admin can create and list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::User(tenant_device_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant user can list
|
||||
ValidatorTest {
|
||||
validators: vec![ValidateFuotaDeploymentsAccess::new(
|
||||
Flag::List,
|
||||
app.id.into(),
|
||||
)],
|
||||
id: AuthID::User(tenant_user.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant user can not create
|
||||
ValidatorTest {
|
||||
validators: vec![ValidateFuotaDeploymentsAccess::new(
|
||||
Flag::Create,
|
||||
app.id.into(),
|
||||
)],
|
||||
id: AuthID::User(tenant_user.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
// other user can not create or list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::User(user_active.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
];
|
||||
run_tests(tests).await;
|
||||
|
||||
// fuota deployments with api key
|
||||
let tests = vec![
|
||||
// admin api key can create and list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant api key can create and list
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_tenant.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant api key can not create or list for other tenant
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
|
||||
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_other_tenant.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
];
|
||||
run_tests(tests).await;
|
||||
|
||||
let dp = device_profile::create(device_profile::DeviceProfile {
|
||||
tenant_id: app.tenant_id,
|
||||
name: "test-dp".into(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let fuota = fuota::create_deployment(fuota::FuotaDeployment {
|
||||
name: "test-fuota".into(),
|
||||
application_id: app.id,
|
||||
device_profile_id: dp.id,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// fuota deployment with user
|
||||
let tests = vec![
|
||||
// admin user can read, update and delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::User(user_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant admin can read, update and delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::User(tenant_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant device admin can read, update and delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::User(tenant_device_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant user can read
|
||||
ValidatorTest {
|
||||
validators: vec![ValidateFuotaDeploymentAccess::new(
|
||||
Flag::Read,
|
||||
fuota.id.into(),
|
||||
)],
|
||||
id: AuthID::User(tenant_user.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant user can not update or delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::User(tenant_user.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
// other user can not read, update or delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::User(user_active.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
];
|
||||
run_tests(tests).await;
|
||||
|
||||
// fuota deployment with api key
|
||||
let tests = vec![
|
||||
// admin api key can read, update and delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// tenant api key can read, update and delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_admin.id.into()),
|
||||
ok: true,
|
||||
},
|
||||
// other api key can not read, update or delete
|
||||
ValidatorTest {
|
||||
validators: vec![
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
|
||||
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
|
||||
],
|
||||
id: AuthID::Key(api_key_other_tenant.id.into()),
|
||||
ok: false,
|
||||
},
|
||||
];
|
||||
run_tests(tests).await;
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,17 @@ impl ToStatus for storage::error::Error {
|
||||
storage::error::Error::ProstDecode(_) => {
|
||||
Status::new(Code::Internal, format!("{:#}", self))
|
||||
}
|
||||
storage::error::Error::ValidatorValidate(_) => {
|
||||
Status::new(Code::InvalidArgument, format!("{:#}", self))
|
||||
}
|
||||
storage::error::Error::MultiError(errors) => {
|
||||
let errors = errors
|
||||
.into_iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
Status::new(Code::InvalidArgument, errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
497
chirpstack/src/api/fuota.rs
Normal file
497
chirpstack/src/api/fuota.rs
Normal file
@ -0,0 +1,497 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
|
||||
use chirpstack_api::api;
|
||||
use chirpstack_api::api::fuota_service_server::FuotaService;
|
||||
use lrwn::EUI64;
|
||||
|
||||
use crate::api::auth::validator;
|
||||
use crate::api::error::ToStatus;
|
||||
use crate::api::helpers::{self, FromProto, ToProto};
|
||||
use crate::storage::fuota;
|
||||
|
||||
pub struct Fuota {
|
||||
validator: validator::RequestValidator,
|
||||
}
|
||||
|
||||
impl Fuota {
|
||||
pub fn new(validator: validator::RequestValidator) -> Self {
|
||||
Fuota { validator }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl FuotaService for Fuota {
|
||||
async fn create_deployment(
|
||||
&self,
|
||||
request: Request<api::CreateFuotaDeploymentRequest>,
|
||||
) -> Result<Response<api::CreateFuotaDeploymentResponse>, Status> {
|
||||
let req_dp = match &request.get_ref().deployment {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(Status::invalid_argument("deployment is missing"));
|
||||
}
|
||||
};
|
||||
|
||||
let app_id = Uuid::from_str(&req_dp.application_id).map_err(|e| e.status())?;
|
||||
let dp_id = Uuid::from_str(&req_dp.device_profile_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentsAccess::new(validator::Flag::Create, app_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let dp = fuota::FuotaDeployment {
|
||||
name: req_dp.name.clone(),
|
||||
application_id: app_id.into(),
|
||||
device_profile_id: dp_id.into(),
|
||||
multicast_group_type: match req_dp.multicast_group_type() {
|
||||
api::MulticastGroupType::ClassB => "B",
|
||||
api::MulticastGroupType::ClassC => "C",
|
||||
}
|
||||
.to_string(),
|
||||
multicast_class_c_scheduling_type: req_dp
|
||||
.multicast_class_c_scheduling_type()
|
||||
.from_proto(),
|
||||
multicast_dr: req_dp.multicast_dr as i16,
|
||||
multicast_class_b_ping_slot_nb_k: req_dp.multicast_class_b_ping_slot_nb_k as i16,
|
||||
multicast_frequency: req_dp.multicast_frequency as i64,
|
||||
multicast_timeout: req_dp.multicast_timeout as i16,
|
||||
unicast_attempt_count: req_dp.unicast_attempt_count as i16,
|
||||
fragmentation_fragment_size: req_dp.fragmentation_fragment_size as i16,
|
||||
fragmentation_redundancy: req_dp.fragmentation_redundancy as i16,
|
||||
fragmentation_session_index: req_dp.fragmentation_session_index as i16,
|
||||
fragmentation_matrix: req_dp.fragmentation_matrix as i16,
|
||||
fragmentation_block_ack_delay: req_dp.fragmentation_block_ack_delay as i16,
|
||||
fragmentation_descriptor: req_dp.fragmentation_descriptor.clone(),
|
||||
request_fragmentation_session_status: req_dp
|
||||
.request_fragmentation_session_status()
|
||||
.from_proto(),
|
||||
payload: req_dp.payload.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let dp = fuota::create_deployment(dp).await.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(api::CreateFuotaDeploymentResponse {
|
||||
id: dp.id.to_string(),
|
||||
});
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
dp.id.to_string().parse().unwrap(),
|
||||
);
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn get_deployment(
|
||||
&self,
|
||||
request: Request<api::GetFuotaDeploymentRequest>,
|
||||
) -> Result<Response<api::GetFuotaDeploymentResponse>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let dp = fuota::get_deployment(dp_id).await.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(api::GetFuotaDeploymentResponse {
|
||||
deployment: Some(api::FuotaDeployment {
|
||||
id: dp.id.to_string(),
|
||||
application_id: dp.application_id.to_string(),
|
||||
device_profile_id: dp.device_profile_id.to_string(),
|
||||
name: dp.name.clone(),
|
||||
multicast_group_type: match dp.multicast_group_type.as_ref() {
|
||||
"B" => api::MulticastGroupType::ClassB,
|
||||
"C" => api::MulticastGroupType::ClassC,
|
||||
_ => return Err(Status::invalid_argument("Invalid multicast_group_type")),
|
||||
}
|
||||
.into(),
|
||||
multicast_class_c_scheduling_type: dp
|
||||
.multicast_class_c_scheduling_type
|
||||
.to_proto()
|
||||
.into(),
|
||||
multicast_dr: dp.multicast_dr as u32,
|
||||
multicast_class_b_ping_slot_nb_k: dp.multicast_class_b_ping_slot_nb_k as u32,
|
||||
multicast_frequency: dp.multicast_frequency as u32,
|
||||
multicast_timeout: dp.multicast_timeout as u32,
|
||||
unicast_attempt_count: dp.unicast_attempt_count as u32,
|
||||
fragmentation_fragment_size: dp.fragmentation_fragment_size as u32,
|
||||
fragmentation_redundancy: dp.fragmentation_redundancy as u32,
|
||||
fragmentation_session_index: dp.fragmentation_session_index as u32,
|
||||
fragmentation_matrix: dp.fragmentation_matrix as u32,
|
||||
fragmentation_block_ack_delay: dp.fragmentation_block_ack_delay as u32,
|
||||
fragmentation_descriptor: dp.fragmentation_descriptor.clone(),
|
||||
request_fragmentation_session_status: dp
|
||||
.request_fragmentation_session_status
|
||||
.to_proto()
|
||||
.into(),
|
||||
payload: dp.payload.clone(),
|
||||
}),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)),
|
||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)),
|
||||
});
|
||||
resp.metadata_mut()
|
||||
.insert("x-log-fuota_deployment_id", req.id.parse().unwrap());
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn update_deployment(
|
||||
&self,
|
||||
request: Request<api::UpdateFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req_dp = match &request.get_ref().deployment {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(Status::invalid_argument("deployment is missing"));
|
||||
}
|
||||
};
|
||||
|
||||
let id = Uuid::from_str(&req_dp.id).map_err(|e| e.status())?;
|
||||
let app_id = Uuid::from_str(&req_dp.application_id).map_err(|e| e.status())?;
|
||||
let dp_id = Uuid::from_str(&req_dp.device_profile_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = fuota::update_deployment(fuota::FuotaDeployment {
|
||||
id: id.into(),
|
||||
name: req_dp.name.clone(),
|
||||
application_id: app_id.into(),
|
||||
device_profile_id: dp_id.into(),
|
||||
multicast_group_type: match req_dp.multicast_group_type() {
|
||||
api::MulticastGroupType::ClassB => "B",
|
||||
api::MulticastGroupType::ClassC => "C",
|
||||
}
|
||||
.to_string(),
|
||||
multicast_class_c_scheduling_type: req_dp
|
||||
.multicast_class_c_scheduling_type()
|
||||
.from_proto(),
|
||||
multicast_dr: req_dp.multicast_dr as i16,
|
||||
multicast_class_b_ping_slot_nb_k: req_dp.multicast_class_b_ping_slot_nb_k as i16,
|
||||
multicast_frequency: req_dp.multicast_frequency as i64,
|
||||
multicast_timeout: req_dp.multicast_timeout as i16,
|
||||
unicast_attempt_count: req_dp.unicast_attempt_count as i16,
|
||||
fragmentation_fragment_size: req_dp.fragmentation_fragment_size as i16,
|
||||
fragmentation_redundancy: req_dp.fragmentation_redundancy as i16,
|
||||
fragmentation_session_index: req_dp.fragmentation_session_index as i16,
|
||||
fragmentation_matrix: req_dp.fragmentation_matrix as i16,
|
||||
fragmentation_block_ack_delay: req_dp.fragmentation_block_ack_delay as i16,
|
||||
fragmentation_descriptor: req_dp.fragmentation_descriptor.clone(),
|
||||
request_fragmentation_session_status: req_dp
|
||||
.request_fragmentation_session_status()
|
||||
.from_proto(),
|
||||
payload: req_dp.payload.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut()
|
||||
.insert("x-log-fuota_deployment_id", req_dp.id.parse().unwrap());
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn delete_deployment(
|
||||
&self,
|
||||
request: Request<api::DeleteFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req = request.get_ref();
|
||||
let id = Uuid::from_str(&req.id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Delete, id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = fuota::delete_deployment(id).await.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut()
|
||||
.insert("x-log-fuota_deployment_id", req.id.parse().unwrap());
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn list_deployments(
|
||||
&self,
|
||||
request: Request<api::ListFuotaDeploymentsRequest>,
|
||||
) -> Result<Response<api::ListFuotaDeploymentsResponse>, Status> {
|
||||
let req = request.get_ref();
|
||||
let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentsAccess::new(validator::Flag::List, app_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count = fuota::get_deployment_count(app_id)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
let items = fuota::list_deployments(app_id, req.limit as i64, req.offset as i64)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(api::ListFuotaDeploymentsResponse {
|
||||
total_count: count as u32,
|
||||
result: items
|
||||
.iter()
|
||||
.map(|d| api::FuotaDeploymentListItem {
|
||||
id: d.id.to_string(),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
|
||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
|
||||
started_at: d
|
||||
.started_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
completed_at: d
|
||||
.completed_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
name: d.name.clone(),
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
resp.metadata_mut()
|
||||
.insert("x-log-application_id", req.application_id.parse().unwrap());
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn add_devices(
|
||||
&self,
|
||||
request: Request<api::AddDevicesToFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut dev_euis = Vec::with_capacity(req.dev_euis.len());
|
||||
for dev_eui in &req.dev_euis {
|
||||
dev_euis.push(EUI64::from_str(dev_eui).map_err(|e| e.status())?);
|
||||
}
|
||||
|
||||
fuota::add_devices(dp_id, dev_euis)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn remove_devices(
|
||||
&self,
|
||||
request: Request<api::RemoveDevicesFromFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut dev_euis = Vec::with_capacity(req.dev_euis.len());
|
||||
for dev_eui in &req.dev_euis {
|
||||
dev_euis.push(EUI64::from_str(dev_eui).map_err(|e| e.status())?);
|
||||
}
|
||||
|
||||
fuota::remove_devices(dp_id, dev_euis)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn list_devices(
|
||||
&self,
|
||||
request: Request<api::ListFuotaDeploymentDevicesRequest>,
|
||||
) -> Result<Response<api::ListFuotaDeploymentDevicesResponse>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count = fuota::get_device_count(dp_id)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
let items = fuota::get_devices(dp_id, req.limit as i64, req.offset as i64)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(api::ListFuotaDeploymentDevicesResponse {
|
||||
total_count: count as u32,
|
||||
result: items
|
||||
.iter()
|
||||
.map(|d| api::FuotaDeploymentDeviceListItem {
|
||||
fuota_deployment_id: d.fuota_deployment_id.to_string(),
|
||||
dev_eui: d.dev_eui.to_string(),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
|
||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&d.updated_at)),
|
||||
mc_group_setup_completed_at: d
|
||||
.mc_group_setup_completed_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
mc_session_completed_at: d
|
||||
.mc_session_completed_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
frag_session_setup_completed_at: d
|
||||
.frag_session_setup_completed_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
frag_status_completed_at: d
|
||||
.frag_status_completed_at
|
||||
.as_ref()
|
||||
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn add_gateways(
|
||||
&self,
|
||||
request: Request<api::AddGatewaysToFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut gateway_ids = Vec::with_capacity(req.gateway_ids.len());
|
||||
for gateway_id in &req.gateway_ids {
|
||||
gateway_ids.push(EUI64::from_str(gateway_id).map_err(|e| e.status())?);
|
||||
}
|
||||
|
||||
fuota::add_gateways(dp_id, gateway_ids)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn remove_gateways(
|
||||
&self,
|
||||
request: Request<api::RemoveGatewaysFromFuotaDeploymentRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut gateway_ids = Vec::with_capacity(req.gateway_ids.len());
|
||||
for gateway_id in &req.gateway_ids {
|
||||
gateway_ids.push(EUI64::from_str(gateway_id).map_err(|e| e.status())?);
|
||||
}
|
||||
|
||||
fuota::remove_gateways(dp_id, gateway_ids)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(());
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn list_gateways(
|
||||
&self,
|
||||
request: Request<api::ListFuotaDeploymentGatewaysRequest>,
|
||||
) -> Result<Response<api::ListFuotaDeploymentGatewaysResponse>, Status> {
|
||||
let req = request.get_ref();
|
||||
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
|
||||
|
||||
self.validator
|
||||
.validate(
|
||||
request.extensions(),
|
||||
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count = fuota::get_gateway_count(dp_id)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
let items = fuota::get_gateway(dp_id, req.limit as i64, req.offset as i64)
|
||||
.await
|
||||
.map_err(|e| e.status())?;
|
||||
|
||||
let mut resp = Response::new(api::ListFuotaDeploymentGatewaysResponse {
|
||||
total_count: count as u32,
|
||||
result: items
|
||||
.iter()
|
||||
.map(|gw| api::FuotaDeploymentGatewayListItem {
|
||||
fuota_deployment_id: gw.fuota_deployment_id.to_string(),
|
||||
gateway_id: gw.gateway_id.to_string(),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&gw.created_at)),
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
resp.metadata_mut().insert(
|
||||
"x-log-fuota_deployment_id",
|
||||
req.fuota_deployment_id.parse().unwrap(),
|
||||
);
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ use chirpstack_api::{api, common};
|
||||
use lrwn::region::{CommonName, MacVersion, Revision};
|
||||
|
||||
use crate::codec::Codec;
|
||||
use crate::storage::fields::{self, MeasurementKind, MulticastGroupSchedulingType};
|
||||
use crate::storage::fields::{
|
||||
self, MeasurementKind, MulticastGroupSchedulingType, RequestFragmentationSessionStatus,
|
||||
};
|
||||
use crate::storage::{device::DeviceClass, metrics::Aggregation};
|
||||
|
||||
pub trait FromProto<T> {
|
||||
@ -318,6 +320,26 @@ impl FromProto<Option<fields::device_profile::Ts005Version>> for api::Ts005Versi
|
||||
}
|
||||
}
|
||||
|
||||
impl ToProto<api::RequestFragmentationSessionStatus> for RequestFragmentationSessionStatus {
|
||||
fn to_proto(self) -> api::RequestFragmentationSessionStatus {
|
||||
match self {
|
||||
Self::NoRequest => api::RequestFragmentationSessionStatus::NoRequest,
|
||||
Self::AfterFragEnqueue => api::RequestFragmentationSessionStatus::AfterFragmentEnqueue,
|
||||
Self::AfterSessTimeout => api::RequestFragmentationSessionStatus::AfterSessionTimeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromProto<RequestFragmentationSessionStatus> for api::RequestFragmentationSessionStatus {
|
||||
fn from_proto(self) -> RequestFragmentationSessionStatus {
|
||||
match self {
|
||||
Self::NoRequest => RequestFragmentationSessionStatus::NoRequest,
|
||||
Self::AfterFragmentEnqueue => RequestFragmentationSessionStatus::AfterFragEnqueue,
|
||||
Self::AfterSessionTimeout => RequestFragmentationSessionStatus::AfterSessTimeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn datetime_to_prost_timestamp(dt: &DateTime<Utc>) -> prost_types::Timestamp {
|
||||
let ts = dt.timestamp_nanos_opt().unwrap_or_default();
|
||||
|
||||
|
@ -32,6 +32,7 @@ use chirpstack_api::api::application_service_server::ApplicationServiceServer;
|
||||
use chirpstack_api::api::device_profile_service_server::DeviceProfileServiceServer;
|
||||
use chirpstack_api::api::device_profile_template_service_server::DeviceProfileTemplateServiceServer;
|
||||
use chirpstack_api::api::device_service_server::DeviceServiceServer;
|
||||
use chirpstack_api::api::fuota_service_server::FuotaServiceServer;
|
||||
use chirpstack_api::api::gateway_service_server::GatewayServiceServer;
|
||||
use chirpstack_api::api::internal_service_server::InternalServiceServer;
|
||||
use chirpstack_api::api::multicast_group_service_server::MulticastGroupServiceServer;
|
||||
@ -53,6 +54,7 @@ pub mod device;
|
||||
pub mod device_profile;
|
||||
pub mod device_profile_template;
|
||||
pub mod error;
|
||||
pub mod fuota;
|
||||
pub mod gateway;
|
||||
mod grpc_multiplex;
|
||||
pub mod helpers;
|
||||
@ -175,6 +177,10 @@ pub async fn setup() -> Result<()> {
|
||||
.add_service(RelayServiceServer::with_interceptor(
|
||||
relay::Relay::new(validator::RequestValidator::new()),
|
||||
auth::auth_interceptor,
|
||||
))
|
||||
.add_service(FuotaServiceServer::with_interceptor(
|
||||
fuota::Fuota::new(validator::RequestValidator::new()),
|
||||
auth::auth_interceptor,
|
||||
));
|
||||
|
||||
let backend_handle = tokio::spawn(backend::setup());
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate diesel_migrations;
|
||||
|
@ -33,6 +33,9 @@ pub enum Error {
|
||||
#[error("Not allowed ({0})")]
|
||||
NotAllowed(String),
|
||||
|
||||
#[error("Multiple errors")]
|
||||
MultiError(Vec<Error>),
|
||||
|
||||
#[error(transparent)]
|
||||
Diesel(#[from] diesel::result::Error),
|
||||
|
||||
@ -50,6 +53,9 @@ pub enum Error {
|
||||
|
||||
#[error(transparent)]
|
||||
ProstDecode(#[from] prost::DecodeError),
|
||||
|
||||
#[error(transparent)]
|
||||
ValidatorValidate(#[from] validator::ValidationErrors),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
79
chirpstack/src/storage/fields/fuota.rs
Normal file
79
chirpstack/src/storage/fields/fuota.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use anyhow::Error;
|
||||
use diesel::backend::Backend;
|
||||
use diesel::sql_types::Text;
|
||||
#[cfg(feature = "sqlite")]
|
||||
use diesel::sqlite::Sqlite;
|
||||
use diesel::{deserialize, serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)]
|
||||
#[diesel(sql_type = Text)]
|
||||
pub enum RequestFragmentationSessionStatus {
|
||||
NoRequest,
|
||||
AfterFragEnqueue,
|
||||
AfterSessTimeout,
|
||||
}
|
||||
|
||||
impl From<&RequestFragmentationSessionStatus> for String {
|
||||
fn from(value: &RequestFragmentationSessionStatus) -> Self {
|
||||
match value {
|
||||
RequestFragmentationSessionStatus::NoRequest => "NO_REQUEST",
|
||||
RequestFragmentationSessionStatus::AfterFragEnqueue => "AFTER_FRAG_ENQUEUE",
|
||||
RequestFragmentationSessionStatus::AfterSessTimeout => "AFTER_SESS_TIMEOUT",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RequestFragmentationSessionStatus {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
"NO_REQUEST" => Self::NoRequest,
|
||||
"AFTER_FRAG_ENQUEUE" => Self::AfterFragEnqueue,
|
||||
"AFTER_SESS_TIMEOUT" => Self::AfterSessTimeout,
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Invalid RequestFragmentationSessionStatus value: {}",
|
||||
value
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> deserialize::FromSql<Text, DB> for RequestFragmentationSessionStatus
|
||||
where
|
||||
DB: Backend,
|
||||
*const str: deserialize::FromSql<Text, DB>,
|
||||
{
|
||||
fn from_sql(value: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let string = <*const str>::from_sql(value)?;
|
||||
Ok(Self::try_from(unsafe { &*string })?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
impl serialize::ToSql<Text, diesel::pg::Pg> for RequestFragmentationSessionStatus
|
||||
where
|
||||
str: serialize::ToSql<Text, diesel::pg::Pg>,
|
||||
{
|
||||
fn to_sql<'b>(
|
||||
&'b self,
|
||||
out: &mut serialize::Output<'b, '_, diesel::pg::Pg>,
|
||||
) -> serialize::Result {
|
||||
<str as serialize::ToSql<Text, diesel::pg::Pg>>::to_sql(
|
||||
&String::from(self),
|
||||
&mut out.reborrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
impl serialize::ToSql<Text, Sqlite> for RequestFragmentationSessionStatus {
|
||||
fn to_sql(&self, out: &mut serialize::Output<'_, '_, Sqlite>) -> serialize::Result {
|
||||
out.set_value(String::from(self));
|
||||
Ok(serialize::IsNull::No)
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ mod big_decimal;
|
||||
mod dev_nonces;
|
||||
pub mod device_profile;
|
||||
mod device_session;
|
||||
mod fuota;
|
||||
mod key_value;
|
||||
mod measurements;
|
||||
mod multicast_group_scheduling_type;
|
||||
@ -11,6 +12,7 @@ pub use big_decimal::BigDecimal;
|
||||
pub use dev_nonces::DevNonces;
|
||||
pub use device_profile::{AbpParams, AppLayerParams, ClassBParams, ClassCParams, RelayParams};
|
||||
pub use device_session::DeviceSession;
|
||||
pub use fuota::RequestFragmentationSessionStatus;
|
||||
pub use key_value::KeyValue;
|
||||
pub use measurements::*;
|
||||
pub use multicast_group_scheduling_type::MulticastGroupSchedulingType;
|
||||
|
431
chirpstack/src/storage/fuota.rs
Normal file
431
chirpstack/src/storage/fuota.rs
Normal file
@ -0,0 +1,431 @@
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::{dsl, prelude::*};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::storage::error::Error;
|
||||
use crate::storage::schema::{
|
||||
application, device, fuota_deployment, fuota_deployment_device, fuota_deployment_gateway,
|
||||
gateway, tenant,
|
||||
};
|
||||
use crate::storage::{self, device_profile, fields, get_async_db_conn};
|
||||
use lrwn::EUI64;
|
||||
|
||||
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq, Validate)]
|
||||
#[diesel(table_name = fuota_deployment)]
|
||||
pub struct FuotaDeployment {
|
||||
pub id: fields::Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub started_at: Option<DateTime<Utc>>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
pub name: String,
|
||||
pub application_id: fields::Uuid,
|
||||
pub device_profile_id: fields::Uuid,
|
||||
pub multicast_group_type: String,
|
||||
pub multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType,
|
||||
pub multicast_dr: i16,
|
||||
pub multicast_class_b_ping_slot_nb_k: i16,
|
||||
pub multicast_frequency: i64,
|
||||
pub multicast_timeout: i16,
|
||||
pub unicast_attempt_count: i16,
|
||||
pub fragmentation_fragment_size: i16,
|
||||
pub fragmentation_redundancy: i16,
|
||||
pub fragmentation_session_index: i16,
|
||||
pub fragmentation_matrix: i16,
|
||||
pub fragmentation_block_ack_delay: i16,
|
||||
pub fragmentation_descriptor: Vec<u8>,
|
||||
pub request_fragmentation_session_status: fields::RequestFragmentationSessionStatus,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for FuotaDeployment {
|
||||
fn default() -> Self {
|
||||
let now = Utc::now();
|
||||
|
||||
Self {
|
||||
id: Uuid::new_v4().into(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
name: "".into(),
|
||||
application_id: Uuid::nil().into(),
|
||||
device_profile_id: Uuid::nil().into(),
|
||||
multicast_group_type: "".into(),
|
||||
multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType::DELAY,
|
||||
multicast_dr: 0,
|
||||
multicast_class_b_ping_slot_nb_k: 0,
|
||||
multicast_frequency: 0,
|
||||
multicast_timeout: 0,
|
||||
unicast_attempt_count: 0,
|
||||
fragmentation_fragment_size: 0,
|
||||
fragmentation_redundancy: 0,
|
||||
fragmentation_session_index: 0,
|
||||
fragmentation_matrix: 0,
|
||||
fragmentation_block_ack_delay: 0,
|
||||
fragmentation_descriptor: Vec::new(),
|
||||
request_fragmentation_session_status:
|
||||
fields::RequestFragmentationSessionStatus::NoRequest,
|
||||
payload: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, PartialEq, Eq, Debug)]
|
||||
pub struct FuotaDeploymentListItem {
|
||||
pub id: fields::Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub started_at: Option<DateTime<Utc>>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq)]
|
||||
#[diesel(table_name = fuota_deployment_device)]
|
||||
pub struct FuotaDeploymentDevice {
|
||||
pub fuota_deployment_id: fields::Uuid,
|
||||
pub dev_eui: EUI64,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub mc_group_setup_completed_at: Option<DateTime<Utc>>,
|
||||
pub mc_session_completed_at: Option<DateTime<Utc>>,
|
||||
pub frag_session_setup_completed_at: Option<DateTime<Utc>>,
|
||||
pub frag_status_completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Default for FuotaDeploymentDevice {
|
||||
fn default() -> Self {
|
||||
let now = Utc::now();
|
||||
|
||||
Self {
|
||||
fuota_deployment_id: Uuid::nil().into(),
|
||||
dev_eui: EUI64::default(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
mc_group_setup_completed_at: None,
|
||||
mc_session_completed_at: None,
|
||||
frag_session_setup_completed_at: None,
|
||||
frag_status_completed_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq)]
|
||||
#[diesel(table_name = fuota_deployment_gateway)]
|
||||
pub struct FuotaDeploymentGateway {
|
||||
pub fuota_deployment_id: fields::Uuid,
|
||||
pub gateway_id: EUI64,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Default for FuotaDeploymentGateway {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fuota_deployment_id: Uuid::nil().into(),
|
||||
gateway_id: EUI64::default(),
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_deployment(d: FuotaDeployment) -> Result<FuotaDeployment, Error> {
|
||||
d.validate()?;
|
||||
|
||||
let app = storage::application::get(&d.application_id).await?;
|
||||
let dp = device_profile::get(&d.device_profile_id).await?;
|
||||
if app.tenant_id != dp.tenant_id {
|
||||
return Err(Error::Validation(
|
||||
"The application and device-profile must be under the samen tenant".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let d: FuotaDeployment = diesel::insert_into(fuota_deployment::table)
|
||||
.values(&d)
|
||||
.get_result(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, d.id.to_string()))?;
|
||||
|
||||
info!(id = %d.id, "FUOTA deployment created");
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
pub async fn get_deployment(id: Uuid) -> Result<FuotaDeployment, Error> {
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.find(&fields::Uuid::from(id))
|
||||
.first(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, id.to_string()))
|
||||
}
|
||||
|
||||
pub async fn update_deployment(d: FuotaDeployment) -> Result<FuotaDeployment, Error> {
|
||||
d.validate()?;
|
||||
|
||||
let d: FuotaDeployment = diesel::update(fuota_deployment::dsl::fuota_deployment.find(&d.id))
|
||||
.set((
|
||||
fuota_deployment::updated_at.eq(&Utc::now()),
|
||||
fuota_deployment::started_at.eq(&d.started_at),
|
||||
fuota_deployment::completed_at.eq(&d.completed_at),
|
||||
fuota_deployment::name.eq(&d.name),
|
||||
fuota_deployment::multicast_group_type.eq(&d.multicast_group_type),
|
||||
fuota_deployment::multicast_class_c_scheduling_type
|
||||
.eq(&d.multicast_class_c_scheduling_type),
|
||||
fuota_deployment::multicast_dr.eq(&d.multicast_dr),
|
||||
fuota_deployment::multicast_class_b_ping_slot_nb_k
|
||||
.eq(&d.multicast_class_b_ping_slot_nb_k),
|
||||
fuota_deployment::multicast_frequency.eq(&d.multicast_frequency),
|
||||
fuota_deployment::multicast_timeout.eq(&d.multicast_timeout),
|
||||
fuota_deployment::unicast_attempt_count.eq(&d.unicast_attempt_count),
|
||||
fuota_deployment::fragmentation_fragment_size.eq(&d.fragmentation_fragment_size),
|
||||
fuota_deployment::fragmentation_redundancy.eq(&d.fragmentation_redundancy),
|
||||
fuota_deployment::fragmentation_session_index.eq(&d.fragmentation_session_index),
|
||||
fuota_deployment::fragmentation_matrix.eq(&d.fragmentation_matrix),
|
||||
fuota_deployment::fragmentation_block_ack_delay.eq(&d.fragmentation_block_ack_delay),
|
||||
fuota_deployment::fragmentation_descriptor.eq(&d.fragmentation_descriptor),
|
||||
fuota_deployment::request_fragmentation_session_status
|
||||
.eq(&d.request_fragmentation_session_status),
|
||||
fuota_deployment::payload.eq(&d.payload),
|
||||
))
|
||||
.get_result(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, d.id.to_string()))?;
|
||||
|
||||
info!(id = %d.id, "FUOTA deployment updated");
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
pub async fn delete_deployment(id: Uuid) -> Result<(), Error> {
|
||||
let ra = diesel::delete(fuota_deployment::dsl::fuota_deployment.find(&fields::Uuid::from(id)))
|
||||
.execute(&mut get_async_db_conn().await?)
|
||||
.await?;
|
||||
if ra == 0 {
|
||||
return Err(Error::NotFound(id.to_string()));
|
||||
}
|
||||
info!(id = %id, "FUOTA deployment deleted");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_deployment_count(application_id: Uuid) -> Result<i64, Error> {
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.select(dsl::count_star())
|
||||
.filter(fuota_deployment::dsl::application_id.eq(fields::Uuid::from(application_id)))
|
||||
.first(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
|
||||
pub async fn list_deployments(
|
||||
application_id: Uuid,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> Result<Vec<FuotaDeploymentListItem>, Error> {
|
||||
fuota_deployment::dsl::fuota_deployment
|
||||
.select((
|
||||
fuota_deployment::id,
|
||||
fuota_deployment::created_at,
|
||||
fuota_deployment::updated_at,
|
||||
fuota_deployment::started_at,
|
||||
fuota_deployment::completed_at,
|
||||
fuota_deployment::name,
|
||||
))
|
||||
.filter(fuota_deployment::dsl::application_id.eq(fields::Uuid::from(application_id)))
|
||||
.order_by(fuota_deployment::dsl::name)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
|
||||
pub async fn add_devices(fuota_deployment_id: Uuid, dev_euis: Vec<EUI64>) -> Result<(), Error> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let dev_euis_filtered: Vec<EUI64> = device::dsl::device
|
||||
.select(device::dsl::dev_eui)
|
||||
.inner_join(
|
||||
fuota_deployment::table
|
||||
.on(fuota_deployment::dsl::device_profile_id.eq(device::dsl::device_profile_id)),
|
||||
)
|
||||
.filter(fuota_deployment::dsl::id.eq(fields::Uuid::from(fuota_deployment_id)))
|
||||
.filter(device::dsl::dev_eui.eq_any(&dev_euis))
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))?;
|
||||
|
||||
if dev_euis_filtered.len() != dev_euis.len() {
|
||||
return Err(Error::Validation(
|
||||
"All devices must have the same device-profile as the FUOTA deployment".into(),
|
||||
));
|
||||
}
|
||||
|
||||
for dev_eui in dev_euis {
|
||||
let res = diesel::insert_into(fuota_deployment_device::table)
|
||||
.values(&FuotaDeploymentDevice {
|
||||
fuota_deployment_id: fuota_deployment_id.into(),
|
||||
dev_eui: dev_eui,
|
||||
..Default::default()
|
||||
})
|
||||
.execute(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, dev_eui.to_string()));
|
||||
|
||||
if let Err(e) = res {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
info!(fuota_deployment_id = %fuota_deployment_id, "Added DeEUIs to FUOTA Deployment");
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::MultiError(errors))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_devices(
|
||||
fuota_deployment_id: Uuid,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> Result<Vec<FuotaDeploymentDevice>, Error> {
|
||||
fuota_deployment_device::dsl::fuota_deployment_device
|
||||
.filter(
|
||||
fuota_deployment_device::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.order_by(fuota_deployment_device::dsl::dev_eui)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
|
||||
pub async fn remove_devices(fuota_deployment_id: Uuid, dev_euis: Vec<EUI64>) -> Result<(), Error> {
|
||||
diesel::delete(
|
||||
fuota_deployment_device::table
|
||||
.filter(
|
||||
fuota_deployment_device::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.filter(fuota_deployment_device::dsl::dev_eui.eq_any(&dev_euis)),
|
||||
)
|
||||
.execute(&mut get_async_db_conn().await?)
|
||||
.await?;
|
||||
|
||||
info!(fuota_deployment_id = %fuota_deployment_id, "DevEUIs removed from FUOTA Deployment");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_device_count(fuota_deployment_id: Uuid) -> Result<i64, Error> {
|
||||
fuota_deployment_device::dsl::fuota_deployment_device
|
||||
.select(dsl::count_star())
|
||||
.filter(
|
||||
fuota_deployment_device::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.first(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
|
||||
pub async fn add_gateways(fuota_deployment_id: Uuid, gateway_ids: Vec<EUI64>) -> Result<(), Error> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let gateway_ids_filtered: Vec<EUI64> = gateway::dsl::gateway
|
||||
.select(gateway::dsl::gateway_id)
|
||||
.inner_join(tenant::table.on(tenant::dsl::id.eq(gateway::dsl::tenant_id)))
|
||||
.inner_join(application::table.on(application::dsl::tenant_id.eq(tenant::dsl::id)))
|
||||
.inner_join(
|
||||
fuota_deployment::table
|
||||
.on(fuota_deployment::dsl::application_id.eq(application::dsl::id)),
|
||||
)
|
||||
.filter(fuota_deployment::dsl::id.eq(fields::Uuid::from(fuota_deployment_id)))
|
||||
.filter(gateway::dsl::gateway_id.eq_any(&gateway_ids))
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))?;
|
||||
|
||||
if gateway_ids_filtered.len() != gateway_ids.len() {
|
||||
return Err(Error::Validation(
|
||||
"All gateways must be under the same tenant as the FUOTA deployment".into(),
|
||||
));
|
||||
}
|
||||
|
||||
for gateway_id in gateway_ids {
|
||||
let res = diesel::insert_into(fuota_deployment_gateway::table)
|
||||
.values(&FuotaDeploymentGateway {
|
||||
fuota_deployment_id: fuota_deployment_id.into(),
|
||||
gateway_id: gateway_id,
|
||||
..Default::default()
|
||||
})
|
||||
.execute(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, gateway_id.to_string()));
|
||||
|
||||
if let Err(e) = res {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
info!(fuota_deployment_id = %fuota_deployment_id, "Added Gateway IDs to FUOTA Deployment");
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::MultiError(errors))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_gateways(
|
||||
fuota_deployment_id: Uuid,
|
||||
gateway_ids: Vec<EUI64>,
|
||||
) -> Result<(), Error> {
|
||||
diesel::delete(
|
||||
fuota_deployment_gateway::table
|
||||
.filter(
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.filter(fuota_deployment_gateway::dsl::gateway_id.eq_any(gateway_ids)),
|
||||
)
|
||||
.execute(&mut get_async_db_conn().await?)
|
||||
.await?;
|
||||
|
||||
info!(fuota_deployment_id = %fuota_deployment_id, "Gateway IDs removed from FUOTA Deployment");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_gateway_count(fuota_deployment_id: Uuid) -> Result<i64, Error> {
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_gateway
|
||||
.select(dsl::count_star())
|
||||
.filter(
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.first(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
|
||||
pub async fn get_gateway(
|
||||
fuota_deployment_id: Uuid,
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> Result<Vec<FuotaDeploymentGateway>, Error> {
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_gateway
|
||||
.filter(
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.order_by(fuota_deployment_gateway::dsl::gateway_id)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
@ -24,6 +24,7 @@ pub mod device_session;
|
||||
pub mod downlink_frame;
|
||||
pub mod error;
|
||||
pub mod fields;
|
||||
pub mod fuota;
|
||||
pub mod gateway;
|
||||
pub mod helpers;
|
||||
pub mod mac_command;
|
||||
|
@ -181,6 +181,59 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment (id) {
|
||||
id -> Uuid,
|
||||
created_at -> Timestamptz,
|
||||
updated_at -> Timestamptz,
|
||||
started_at -> Nullable<Timestamptz>,
|
||||
completed_at -> Nullable<Timestamptz>,
|
||||
#[max_length = 100]
|
||||
name -> Varchar,
|
||||
application_id -> Uuid,
|
||||
device_profile_id -> Uuid,
|
||||
#[max_length = 1]
|
||||
multicast_group_type -> Bpchar,
|
||||
#[max_length = 20]
|
||||
multicast_class_c_scheduling_type -> Varchar,
|
||||
multicast_dr -> Int2,
|
||||
multicast_class_b_ping_slot_nb_k -> Int2,
|
||||
multicast_frequency -> Int8,
|
||||
multicast_timeout -> Int2,
|
||||
unicast_attempt_count -> Int2,
|
||||
fragmentation_fragment_size -> Int2,
|
||||
fragmentation_redundancy -> Int2,
|
||||
fragmentation_session_index -> Int2,
|
||||
fragmentation_matrix -> Int2,
|
||||
fragmentation_block_ack_delay -> Int2,
|
||||
fragmentation_descriptor -> Bytea,
|
||||
#[max_length = 20]
|
||||
request_fragmentation_session_status -> Varchar,
|
||||
payload -> Bytea,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment_device (fuota_deployment_id, dev_eui) {
|
||||
fuota_deployment_id -> Uuid,
|
||||
dev_eui -> Bytea,
|
||||
created_at -> Timestamptz,
|
||||
updated_at -> Timestamptz,
|
||||
mc_group_setup_completed_at -> Nullable<Timestamptz>,
|
||||
mc_session_completed_at -> Nullable<Timestamptz>,
|
||||
frag_session_setup_completed_at -> Nullable<Timestamptz>,
|
||||
frag_status_completed_at -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment_gateway (fuota_deployment_id, gateway_id) {
|
||||
fuota_deployment_id -> Uuid,
|
||||
gateway_id -> Bytea,
|
||||
created_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
gateway (gateway_id) {
|
||||
gateway_id -> Bytea,
|
||||
@ -333,6 +386,12 @@ diesel::joinable!(device -> device_profile (device_profile_id));
|
||||
diesel::joinable!(device_keys -> device (dev_eui));
|
||||
diesel::joinable!(device_profile -> tenant (tenant_id));
|
||||
diesel::joinable!(device_queue_item -> device (dev_eui));
|
||||
diesel::joinable!(fuota_deployment -> application (application_id));
|
||||
diesel::joinable!(fuota_deployment -> device_profile (device_profile_id));
|
||||
diesel::joinable!(fuota_deployment_device -> device (dev_eui));
|
||||
diesel::joinable!(fuota_deployment_device -> fuota_deployment (fuota_deployment_id));
|
||||
diesel::joinable!(fuota_deployment_gateway -> fuota_deployment (fuota_deployment_id));
|
||||
diesel::joinable!(fuota_deployment_gateway -> gateway (gateway_id));
|
||||
diesel::joinable!(gateway -> tenant (tenant_id));
|
||||
diesel::joinable!(multicast_group -> application (application_id));
|
||||
diesel::joinable!(multicast_group_device -> device (dev_eui));
|
||||
@ -354,6 +413,9 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
device_profile,
|
||||
device_profile_template,
|
||||
device_queue_item,
|
||||
fuota_deployment,
|
||||
fuota_deployment_device,
|
||||
fuota_deployment_gateway,
|
||||
gateway,
|
||||
multicast_group,
|
||||
multicast_group_device,
|
||||
|
@ -161,6 +161,55 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment (id) {
|
||||
id -> Text,
|
||||
created_at -> TimestamptzSqlite,
|
||||
updated_at -> TimestamptzSqlite,
|
||||
started_at -> Nullable<TimestamptzSqlite>,
|
||||
completed_at -> Nullable<TimestamptzSqlite>,
|
||||
name -> Text,
|
||||
application_id -> Text,
|
||||
device_profile_id -> Text,
|
||||
multicast_group_type -> Text,
|
||||
multicast_class_c_scheduling_type -> Text,
|
||||
multicast_dr -> SmallInt,
|
||||
multicast_class_b_ping_slot_nb_k -> SmallInt,
|
||||
multicast_frequency -> BigInt,
|
||||
multicast_timeout -> SmallInt,
|
||||
unicast_attempt_count -> SmallInt,
|
||||
fragmentation_fragment_size -> SmallInt,
|
||||
fragmentation_redundancy -> SmallInt,
|
||||
fragmentation_session_index -> SmallInt,
|
||||
fragmentation_matrix -> SmallInt,
|
||||
fragmentation_block_ack_delay -> SmallInt,
|
||||
fragmentation_descriptor -> Binary,
|
||||
request_fragmentation_session_status -> Text,
|
||||
payload -> Binary,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment_device (fuota_deployment_id, dev_eui) {
|
||||
fuota_deployment_id -> Text,
|
||||
dev_eui -> Binary,
|
||||
created_at -> TimestamptzSqlite,
|
||||
updated_at -> TimestamptzSqlite,
|
||||
mc_group_setup_completed_at -> Nullable<TimestamptzSqlite>,
|
||||
mc_session_completed_at -> Nullable<TimestamptzSqlite>,
|
||||
frag_session_setup_completed_at -> Nullable<TimestamptzSqlite>,
|
||||
frag_status_completed_at -> Nullable<TimestamptzSqlite>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
fuota_deployment_gateway (fuota_deployment_id, gateway_id) {
|
||||
fuota_deployment_id -> Text,
|
||||
gateway_id -> Binary,
|
||||
created_at -> TimestamptzSqlite,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
gateway (gateway_id) {
|
||||
gateway_id -> Binary,
|
||||
@ -304,6 +353,12 @@ diesel::joinable!(device -> device_profile (device_profile_id));
|
||||
diesel::joinable!(device_keys -> device (dev_eui));
|
||||
diesel::joinable!(device_profile -> tenant (tenant_id));
|
||||
diesel::joinable!(device_queue_item -> device (dev_eui));
|
||||
diesel::joinable!(fuota_deployment -> application (application_id));
|
||||
diesel::joinable!(fuota_deployment -> device_profile (device_profile_id));
|
||||
diesel::joinable!(fuota_deployment_device -> device (dev_eui));
|
||||
diesel::joinable!(fuota_deployment_device -> fuota_deployment (fuota_deployment_id));
|
||||
diesel::joinable!(fuota_deployment_gateway -> fuota_deployment (fuota_deployment_id));
|
||||
diesel::joinable!(fuota_deployment_gateway -> gateway (gateway_id));
|
||||
diesel::joinable!(gateway -> tenant (tenant_id));
|
||||
diesel::joinable!(multicast_group -> application (application_id));
|
||||
diesel::joinable!(multicast_group_device -> device (dev_eui));
|
||||
@ -325,6 +380,9 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
device_profile,
|
||||
device_profile_template,
|
||||
device_queue_item,
|
||||
fuota_deployment,
|
||||
fuota_deployment_device,
|
||||
fuota_deployment_gateway,
|
||||
gateway,
|
||||
multicast_group,
|
||||
multicast_group_device,
|
||||
|
Loading…
x
Reference in New Issue
Block a user