Skip to content

Commit f6a2267

Browse files
authored
Merge pull request #7544 from TheThingsNetwork/feature/unassign-mac-settings-profile
Disassociate MAC settings profile
2 parents 95dfaa8 + ea0c8f0 commit f6a2267

20 files changed

+1179
-860
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ For details about compatibility between different releases, see the **Commitment
1111

1212
### Added
1313

14+
- Support to associate/disassociate MAC settings profiles to end devices
15+
- This feature is experimental and subject to change.
16+
1417
### Changed
1518

16-
- Support wildcards in the supported hosts for TLS certifictes obtained via ACME (`tls.acme.hosts`).
19+
- Support wildcards in the supported hosts for TLS certificates obtained via ACME (`tls.acme.hosts`).
1720
- Increase downlink capacity by raising duty-cycle budgets per priority.
1821

1922
### Deprecated

api/ttn/lorawan/v3/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4378,6 +4378,7 @@ This is used internally by the Network Server.
43784378
| ----- | ---- | ----- | ----------- |
43794379
| `ids` | [`MACSettingsProfileIdentifiers`](#ttn.lorawan.v3.MACSettingsProfileIdentifiers) | | Profile identifiers. |
43804380
| `mac_settings` | [`MACSettings`](#ttn.lorawan.v3.MACSettings) | | MAC settings. |
4381+
| `end_devices_count` | [`uint32`](#uint32) | | Associated end devices counter. |
43814382

43824383
#### Field Rules
43834384

api/ttn/lorawan/v3/api.swagger.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26661,6 +26661,11 @@
2666126661
"mac_settings": {
2666226662
"$ref": "#/definitions/v3MACSettings",
2666326663
"description": "MAC settings."
26664+
},
26665+
"end_devices_count": {
26666+
"type": "integer",
26667+
"format": "int64",
26668+
"description": "Associated end devices counter."
2666426669
}
2666526670
}
2666626671
},
@@ -27978,6 +27983,11 @@
2797827983
"mac_settings": {
2797927984
"$ref": "#/definitions/v3MACSettings",
2798027985
"description": "MAC settings."
27986+
},
27987+
"end_devices_count": {
27988+
"type": "integer",
27989+
"format": "int64",
27990+
"description": "Associated end devices counter."
2798127991
}
2798227992
},
2798327993
"description": "The MAC settings profile to create.",

api/ttn/lorawan/v3/end_device.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,8 @@ message MACSettingsProfile {
705705
];
706706
// MAC settings.
707707
MACSettings mac_settings = 2 [(validate.rules).message.required = true];
708+
// Associated end devices counter.
709+
uint32 end_devices_count = 3;
708710
}
709711

710712
// MACState represents the state of MAC layer of the device.

config/messages.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8270,6 +8270,15 @@
82708270
"file": "device_state.go"
82718271
}
82728272
},
8273+
"error:pkg/networkserver:field_mask": {
8274+
"translations": {
8275+
"en": "invalid field mask"
8276+
},
8277+
"description": {
8278+
"package": "pkg/networkserver",
8279+
"file": "grpc_mac_settings_profile.go"
8280+
}
8281+
},
82738282
"error:pkg/networkserver:field_not_zero": {
82748283
"translations": {
82758284
"en": "field `{name}` is not zero"
@@ -8324,6 +8333,15 @@
83248333
"file": "grpc_mac_settings_profile.go"
83258334
}
83268335
},
8336+
"error:pkg/networkserver:mac_settings_profile_used": {
8337+
"translations": {
8338+
"en": "MAC settings profile is used"
8339+
},
8340+
"description": {
8341+
"package": "pkg/networkserver",
8342+
"file": "grpc_mac_settings_profile.go"
8343+
}
8344+
},
83278345
"error:pkg/networkserver:no_downlink": {
83288346
"translations": {
83298347
"en": "no downlink to send"

pkg/networkserver/grpc_deviceregistry.go

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -410,29 +410,40 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
410410
return nil, err
411411
}
412412

413-
var profile *ttnpb.MACSettingsProfile
413+
var (
414+
profile *ttnpb.MACSettingsProfile
415+
macSettingsProfileChanged bool
416+
)
417+
414418
if st.HasSetField(
415419
"mac_settings_profile_ids",
416420
"mac_settings_profile_ids.application_ids",
417421
"mac_settings_profile_ids.application_ids.application_id",
418422
"mac_settings_profile_ids.profile_id",
419423
) {
420-
// If mac_settings_profile_ids is set, mac_settings must not be set.
421-
if st.HasSetField(macSettingsFields...) {
422-
return nil, newInvalidFieldValueError("mac_settings")
423-
}
424-
profile, err = ns.macSettingsProfiles.Get(ctx, st.Device.MacSettingsProfileIds, []string{"mac_settings"})
425-
if err != nil {
426-
return nil, err
427-
}
424+
if st.Device.MacSettingsProfileIds != nil {
425+
// If mac_settings_profile_ids is set, mac_settings must not be set.
426+
if st.HasSetField(macSettingsFields...) {
427+
return nil, newInvalidFieldValueError("mac_settings")
428+
}
429+
profile, err = ns.macSettingsProfiles.Get(
430+
ctx,
431+
st.Device.MacSettingsProfileIds,
432+
[]string{"ids", "mac_settings"},
433+
)
434+
if err != nil {
435+
return nil, err
436+
}
428437

429-
if err = validateProfile(profile.GetMacSettings(), st, fps); err != nil {
430-
return nil, err
431-
}
438+
if err = validateProfile(profile.GetMacSettings(), st, fps); err != nil {
439+
return nil, err
440+
}
432441

433-
// If mac_settings_profile_ids is set, mac_settings must not be set.
434-
st.Device.MacSettings = nil
435-
st.AddSetFields(macSettingsFields...)
442+
// If mac_settings_profile_ids is set, mac_settings must not be set.
443+
st.Device.MacSettings = nil
444+
st.AddSetFields(macSettingsFields...)
445+
}
446+
macSettingsProfileChanged = true
436447
}
437448

438449
if err := validateADR(st); err != nil {
@@ -1434,6 +1445,56 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
14341445
)
14351446
}
14361447
}
1448+
if macSettingsProfileChanged &&
1449+
!(stored.GetMacSettingsProfileIds() == nil &&
1450+
st.Device.MacSettingsProfileIds == nil) {
1451+
id := st.Device.MacSettingsProfileIds
1452+
if id == nil {
1453+
id = stored.GetMacSettingsProfileIds()
1454+
}
1455+
1456+
profile, err = ns.macSettingsProfiles.Get(
1457+
ctx,
1458+
id,
1459+
[]string{"ids", "mac_settings", "end_devices_count"},
1460+
)
1461+
if err != nil {
1462+
return err
1463+
}
1464+
1465+
var changed string
1466+
// Mac Settings profile is added
1467+
if stored.GetMacSettingsProfileIds() == nil && st.Device.MacSettingsProfileIds != nil {
1468+
changed = "inc"
1469+
}
1470+
1471+
// Mac Settings profile is deleted
1472+
if stored.GetMacSettingsProfileIds() != nil && st.Device.MacSettingsProfileIds == nil {
1473+
changed = "dec"
1474+
1475+
st.Device.MacSettings = profile.MacSettings
1476+
st.AddSetFields(macSettingsFields...)
1477+
}
1478+
1479+
_, err := ns.macSettingsProfiles.Set(
1480+
ctx,
1481+
id,
1482+
[]string{"ids", "mac_settings", "end_devices_count"},
1483+
func(_ context.Context, existing *ttnpb.MACSettingsProfile) (*ttnpb.MACSettingsProfile, []string, error) {
1484+
switch changed {
1485+
case "inc":
1486+
existing.EndDevicesCount++
1487+
case "dec":
1488+
if existing.EndDevicesCount > 0 {
1489+
existing.EndDevicesCount--
1490+
}
1491+
}
1492+
return existing, []string{"ids", "mac_settings", "end_devices_count"}, nil
1493+
})
1494+
if err != nil {
1495+
return err
1496+
}
1497+
}
14371498

14381499
if stored == nil {
14391500
evt = evtCreateEndDevice.NewWithIdentifiersAndData(ctx, st.Device.Ids, nil)

pkg/networkserver/grpc_deviceregistry_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ func TestDeviceRegistrySet(t *testing.T) {
437437
}
438438

439439
macSettingsProfileOpt := EndDeviceOptions.WithMacSettingsProfileIds(macSettingsProfileID)
440+
emptyMacSettingsProfileOpt := EndDeviceOptions.WithMacSettingsProfileIds(nil)
440441

441442
for createDevice, tcs := range map[*ttnpb.EndDevice][]struct {
442443
SetDevice SetDeviceRequest
@@ -811,6 +812,50 @@ func TestDeviceRegistrySet(t *testing.T) {
811812
StoredDevice: multicastClassBMACSettingsOpt(MakeMulticastEndDevice(ttnpb.Class_CLASS_B, defaultMACSettings, true, activeSessionOptsWithStartedAt, nil)),
812813
},
813814
},
815+
// Update with MAC settings profile
816+
MakeOTAAEndDevice(): {
817+
{
818+
SetDevice: *makeUpdateDeviceRequest([]test.EndDeviceOption{
819+
EndDeviceOptions.WithLorawanVersion(ttnpb.MACVersion_MAC_V1_0_3),
820+
EndDeviceOptions.WithLorawanPhyVersion(ttnpb.PHYVersion_RP001_V1_0_3_REV_A),
821+
EndDeviceOptions.WithDefaultFrequencyPlanID(),
822+
macSettingsProfileOpt,
823+
},
824+
"frequency_plan_id",
825+
"lorawan_version",
826+
"lorawan_phy_version",
827+
"mac_settings_profile_ids",
828+
),
829+
830+
ReturnedDevice: MakeOTAAEndDevice(
831+
EndDeviceOptions.WithLorawanVersion(ttnpb.MACVersion_MAC_V1_0_3),
832+
EndDeviceOptions.WithLorawanPhyVersion(ttnpb.PHYVersion_RP001_V1_0_3_REV_A),
833+
EndDeviceOptions.WithDefaultFrequencyPlanID(),
834+
macSettingsProfileOpt,
835+
),
836+
StoredDevice: MakeOTAAEndDevice(
837+
EndDeviceOptions.WithLorawanVersion(ttnpb.MACVersion_MAC_V1_0_3),
838+
EndDeviceOptions.WithLorawanPhyVersion(ttnpb.PHYVersion_RP001_V1_0_3_REV_A),
839+
EndDeviceOptions.WithDefaultFrequencyPlanID(),
840+
macSettingsProfileOpt,
841+
),
842+
},
843+
},
844+
// Update with empty MAC settings profile
845+
MakeOTAAEndDevice(macSettingsProfileOpt): {
846+
{
847+
SetDevice: *makeUpdateDeviceRequest([]test.EndDeviceOption{
848+
emptyMacSettingsProfileOpt,
849+
},
850+
"mac_settings_profile_ids",
851+
),
852+
853+
ReturnedDevice: MakeOTAAEndDevice(),
854+
StoredDevice: MakeOTAAEndDevice(
855+
customMACSettingsOpt,
856+
),
857+
},
858+
},
814859
} {
815860
for _, tc := range tcs {
816861
createDevice := createDevice

pkg/networkserver/grpc_mac_settings_profile.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import (
2929
var (
3030
errMACSettingsProfileAlreadyExists = errors.DefineAlreadyExists("mac_settings_profile_already_exists", "MAC settings profile already exists") // nolint: lll
3131
errMACSettingsProfileNotFound = errors.DefineNotFound("mac_settings_profile_not_found", "MAC settings profile not found") // nolint: lll
32+
errMACSettingsProfileUsed = errors.DefineFailedPrecondition("mac_settings_profile_used", "MAC settings profile is used") // nolint: lll
33+
errInvalidFieldMask = errors.DefineInvalidArgument("field_mask", "invalid field mask")
3234
)
3335

3436
func setTotalHeader(ctx context.Context, total int64) {
@@ -106,6 +108,9 @@ func (m *NsMACSettingsProfileRegistry) Update(ctx context.Context, req *ttnpb.Up
106108
if req.FieldMask != nil {
107109
paths = req.FieldMask.GetPaths()
108110
}
111+
if ttnpb.HasAnyField(paths, "end_devices_count") {
112+
return nil, errInvalidFieldMask.WithAttributes("field_mask", "end_devices_count")
113+
}
109114
profile, err := m.registry.Set(
110115
ctx,
111116
req.MacSettingsProfile.Ids,
@@ -134,7 +139,7 @@ func (m *NsMACSettingsProfileRegistry) Delete(ctx context.Context, req *ttnpb.De
134139
); err != nil {
135140
return nil, err
136141
}
137-
paths := []string{"ids", "mac_settings"}
142+
paths := []string{"ids", "mac_settings", "end_devices_count"}
138143
_, err := m.registry.Set(
139144
ctx,
140145
req.MacSettingsProfileIds,
@@ -143,6 +148,9 @@ func (m *NsMACSettingsProfileRegistry) Delete(ctx context.Context, req *ttnpb.De
143148
if profile == nil {
144149
return nil, nil, errMACSettingsProfileNotFound.New()
145150
}
151+
if profile.EndDevicesCount > 0 {
152+
return nil, nil, errMACSettingsProfileUsed.New()
153+
}
146154
return nil, nil, nil
147155
})
148156
if err != nil {

0 commit comments

Comments
 (0)