-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: propagate Subscriber deletion to Device Groups #299
base: main
Are you sure you want to change the base?
feat: propagate Subscriber deletion to Device Groups #299
Conversation
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
configapi/api_sub_config_test.go
Outdated
{ | ||
name: "Create a new subscriber success", | ||
route: "/api/subscriber/imsi-208930100007487", | ||
inputData: `{"UeId":"208930100007487", "plmnId":"12345", "opc":"981d464c7c52eb6e5036234984ad0bcf","key":"5122250214c33e723a5dd523fc145fc0", "sequenceNumber":"16f3b3f70fc2"}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inputData: `{"UeId":"208930100007487", "plmnId":"12345", "opc":"981d464c7c52eb6e5036234984ad0bcf","key":"5122250214c33e723a5dd523fc145fc0", "sequenceNumber":"16f3b3f70fc2"}`, | |
inputData: `{"plmnID":"12345", "opc":"981d464c7c52eb6e5036234984ad0bcf","key":"5122250214c33e723a5dd523fc145fc0", "sequenceNumber":"16f3b3f70fc2"}`, |
configapi/api_sub_config_test.go
Outdated
expectedCode int | ||
expectedBody string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expectedCode int | |
expectedBody string |
configapi/api_sub_config_test.go
Outdated
MsgMethod: configmodels.Post_op, | ||
AuthSubData: &models.AuthenticationSubscription{ | ||
AuthenticationManagementField: "8000", | ||
AuthenticationMethod: "5G_AKA", // "5G_AKA", "EAP_AKA_PRIME" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthenticationMethod: "5G_AKA", // "5G_AKA", "EAP_AKA_PRIME" | |
AuthenticationMethod: "5G_AKA", |
configapi/api_sub_config_test.go
Outdated
Op: &models.Op{ | ||
EncryptionAlgorithm: 0, | ||
EncryptionKey: 0, | ||
OpValue: "", // Required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace OpValue
with real expected value and remove comment
configapi/api_sub_config_test.go
Outdated
Opc: &models.Opc{ | ||
EncryptionAlgorithm: 0, | ||
EncryptionKey: 0, | ||
// OpcValue: "8e27b6af0e692e750f32667a3b14605d", // Required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace OpcValue
with real expected value and remove comment
configapi/api_sub_config_test.go
Outdated
if tc.expectedMessage.AuthSubData != nil { | ||
if msg.AuthSubData == nil { | ||
t.Errorf("expected AuthSubData %+v, but got nil", tc.expectedMessage.AuthSubData) | ||
} | ||
if tc.expectedMessage.Imsi != msg.Imsi { | ||
t.Errorf("expected IMSI %+v, but got %+v", tc.expectedMessage.Imsi, msg.Imsi) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if tc.expectedMessage.AuthSubData != nil { | |
if msg.AuthSubData == nil { | |
t.Errorf("expected AuthSubData %+v, but got nil", tc.expectedMessage.AuthSubData) | |
} | |
if tc.expectedMessage.Imsi != msg.Imsi { | |
t.Errorf("expected IMSI %+v, but got %+v", tc.expectedMessage.Imsi, msg.Imsi) | |
} | |
} | |
if tc.expectedMessage.Imsi != msg.Imsi { | |
t.Errorf("expected IMSI %+v, but got %+v", tc.expectedMessage.Imsi, msg.Imsi) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherwise you are not really checking the imsi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can also add a check to verify that the AuthSubData
in the received message is nil. Or maybe use reflect.DeepEqual
to check the whole message in one step instead of checking each field
configapi/api_sub_config_test.go
Outdated
} | ||
} | ||
|
||
func TestSubscriberDeleteHandlers(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test in which a Subscriber belongs to a DG is missing
configapi/api_sub_config.go
Outdated
rawDeviceGroups, err := dbadapter.CommonDBClient.RestfulAPIGetMany(devGroupDataColl, filterByImsi) | ||
if err != nil { | ||
logger.WebUILog.Errorf("failed to fetch device groups: %w", err) | ||
return |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
configapi/api_sub_config.go
Outdated
var deviceGroup configmodels.DeviceGroups | ||
if err = json.Unmarshal(configmodels.MapToByte(rawDeviceGroup), &deviceGroup); err != nil { | ||
logger.WebUILog.Errorf("error unmarshaling device group: %v", err) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return | |
continue |
} | ||
deviceGroupUpdateMessages = append(deviceGroupUpdateMessages, &deviceGroupUpdateMessage) | ||
} | ||
for _, msg := range deviceGroupUpdateMessages { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
Please add to the description the fact that you are changing the behavior of the getter to return [ ] instead of null |
Signed-off-by: Dario Faccin <[email protected]>
configapi/api_sub_config_test.go
Outdated
{ | ||
name: "Create a new subscriber success", | ||
route: "/api/subscriber/imsi-208930100007487", | ||
inputData: `{"plmnId":"12345", "opc":"8e27b6af0e692e750f32667a3b14605d","key":"8baf473f2f8fd09487cccbd7097c6862", "sequenceNumber":"16f3b3f70fc2"}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inputData: `{"plmnId":"12345", "opc":"8e27b6af0e692e750f32667a3b14605d","key":"8baf473f2f8fd09487cccbd7097c6862", "sequenceNumber":"16f3b3f70fc2"}`, | |
inputData: `{"plmnID":"12345", "opc":"8e27b6af0e692e750f32667a3b14605d","key":"8baf473f2f8fd09487cccbd7097c6862", "sequenceNumber":"16f3b3f70fc2"}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the json representation is plmnID
. This does not affect the test but if in the future if someone wants to do something with the plmnID, this subtlety can be a struggling point.
configapi/api_sub_config_test.go
Outdated
if tc.expectedMessage.AuthSubData != nil { | ||
if msg.AuthSubData == nil { | ||
t.Errorf("expected AuthSubData %+v, but got nil", tc.expectedMessage.AuthSubData) | ||
} | ||
if tc.expectedMessage.Imsi != msg.Imsi { | ||
t.Errorf("expected IMSI %+v, but got %+v", tc.expectedMessage.Imsi, msg.Imsi) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this part of code is never executed because tc.expectedMessage.AuthSubData != nil
condition is never true.
it's not recommended to have complicated logic in the checks.
We need to test that in the first tc only one message is sent, and in the second tc we need to check that 2 messages are sent, if the logic becomes complicated it's better to separate en 2 tests.
Complicated logic in tests is error prone, and hard to read
configapi/api_sub_config_test.go
Outdated
} | ||
|
||
func deviceGroupWithImsis(name string, imsis []string) configmodels.DeviceGroups { | ||
traffic_class := configmodels.TrafficClassInfo{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
traffic_class := configmodels.TrafficClassInfo{ | |
trafficClass := configmodels.TrafficClassInfo{ |
configapi/api_sub_config_test.go
Outdated
DnnMbrUplink: 10000000, | ||
DnnMbrDownlink: 10000000, | ||
BitrateUnit: "kbps", | ||
TrafficClass: &traffic_class, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TrafficClass: &traffic_class, | |
TrafficClass: &trafficClass, |
configapi/api_sub_config_test.go
Outdated
BitrateUnit: "kbps", | ||
TrafficClass: &traffic_class, | ||
} | ||
ipdomain := configmodels.DeviceGroupsIpDomainExpanded{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ipdomain := configmodels.DeviceGroupsIpDomainExpanded{ | |
ipDomain := configmodels.DeviceGroupsIpDomainExpanded{ |
configapi/api_sub_config_test.go
Outdated
Imsis: imsis, | ||
SiteInfo: "demo", | ||
IpDomainName: "pool1", | ||
IpDomainExpanded: ipdomain, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IpDomainExpanded: ipdomain, | |
IpDomainExpanded: ipDomain, |
@@ -0,0 +1,354 @@ | |||
// SPDX-License-Identifier: Apache-2.0 | |||
// Copyright 2024 Canonical Ltd. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Copyright 2024 Canonical Ltd. | |
// Copyright 2025 Canonical Ltd. |
Signed-off-by: Dario Faccin <[email protected]>
} | ||
default: | ||
t.Error("expected message in configChannel, but none received") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check over the second message is missing
default: | ||
t.Error("expected message in configChannel, but none received") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can check that only one message is sent: read again from the channel and if it does not go to the default
clause, it should raise an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to try to read message again from the channel and msg should not exist.
filterByImsi := bson.M{ | ||
"imsis": imsi, | ||
} | ||
rawDeviceGroups, err := dbadapter.CommonDBClient.RestfulAPIGetMany(devGroupDataColl, filterByImsi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In RestfulAPIGetMany var resultArray []map[string]interface{}
initial value is nil. According to cur, err := collection.Find(ctx, filter)
, if filter does not match it can stay as nil and it can return rawDeviceGroups as nil and err as nil.
In line 509, as we only check the err is nil or not, execution continue.
On line 513, iterating over rawDeviceGroups which is nil may cause panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial value of var resultArray []map[string]interface{}
is not nil
but []
. Actually, there is a test (TestSubscriberDeleteNoDeviceGroup
) where the IMSI to be deleted does not belong to any device group and it does not panic. From the debugger: []map[string]interface {}=[]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. Please discard this comment.
configapi/api_sub_config.go
Outdated
for _, rawDeviceGroup := range rawDeviceGroups { | ||
var deviceGroup configmodels.DeviceGroups | ||
if err = json.Unmarshal(configmodels.MapToByte(rawDeviceGroup), &deviceGroup); err != nil { | ||
logger.WebUILog.Errorf("error unmarshaling device group: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can improve logging here by improving device group name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point we are not sure that the DeviceGroupName
field is available in the object we are trying to unmarshal: logging the raw object could simplify the debugging operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
configapi/api_sub_config.go
Outdated
@@ -485,6 +489,9 @@ func DeleteSubscriberByID(c *gin.Context) { | |||
|
|||
c.JSON(http.StatusNoContent, gin.H{}) | |||
|
|||
imsi := strings.TrimPrefix(ueId, "imsi-") | |||
updateSubscriberInDeviceGroups(imsi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to line 490, before doing any operation, DeleteSubscriberByID
function send HTTP 204 no content response, then it calls updateSubscriberInDeviceGroups(imsi) method. I saw two problems here:
- Order: response is sent without checking the success of device group update
- Error propagation: updateSubscriberInDeviceGroups log the errors but does not propagate them to caller method. The caller always report a successful operation regardless from what happened in the updateSubscriberInDeviceGroups.
filterByImsi := bson.M{ | ||
"imsis": imsi, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to validate the existence of imsi here.
@dariofaccin, you need to sign every commit you make (-s), otherwise, the DCO will fail |
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
dcd98c9
to
fe11864
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
default: | ||
t.Error("expected message in configChannel, but none received") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to try to read message again from the channel and msg should not exist.
dbAdapter := &MockMongoClientDeviceGroupsWithSubscriber{} | ||
dbadapter.CommonDBClient = dbAdapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally speaking, we are accessing the dbadapter.CommonDBClient
and configChannel
global variables and changing their states in the tests. I saw some tests doing it but some are not. All the tests that accessing those global variables need to save their original states and after test, we need to restore them.
For example:
// Save original state
origDBClient := dbadapter.CommonDBClient
origConfigChannel := configChannel
// Restore them after the test finishes
defer func() {
dbadapter.CommonDBClient = origDBClient
configChannel = origConfigChannel
}()
// Set up a mock database adapter
dbAdapter := &MockMongoClientDeviceGroupsWithSubscriber{}
dbadapter.CommonDBClient = dbAdapter
// Create a new llocal config channel for the test
configChannel = make(chan *configmodels.ConfigMessage, 2)
...
Signed-off-by: Dario Faccin <[email protected]>
Signed-off-by: Dario Faccin <[email protected]>
Issue
When we DELETE a subscriber, information is removed from
subscriptionData.provisionedData.amData
andsubscriptionData.authenticationData.authenticationSubscription
. However, if a device group contains a reference to this subscriber inimsis
list, the reference is not removed.Description
This PR propagates the DELETE subscriber operation to all the device groups that contains the deletedsubscriber. This way, the removal of the subscriber is done in a single operation, no need to manually update the device groups to remove the dangling reference anymore.
This PR improves the response when the list of subscribers is empty: now it returns an empty list (
[]
) instead ofnull
.This PR also improves the response in case of subscriber deletion failure: now it returns 500 Internal Server Error.
How the code works
Webconsole behavior on Subscriber POST before this PR
When a subscriber is created (POST), it creates entries in:
Webconsole behavior on Subscriber DELETE before this PR
When a subscriber is deleted (DELETE) , it removes entries for each subscriber in:
This PR
imsis
How to reproduce
webconsoleData.snapshots.devGroupData
(imsis
list)What you should see
imsis
listsubscriptionData.authenticationData.authenticationSubscription
subscriptionData.provisionedData.amData
subscriptionData.provisionedData.smData
subscriptionData.provisionedData.smfSelectionSubscriptionData
policyData.ues.amData
policyData.ues.smData