From d510b83540931746f96c5b76c2332d35de4067c0 Mon Sep 17 00:00:00 2001 From: Edvin Lindqvist Date: Fri, 21 Aug 2026 14:09:46 +0200 Subject: [PATCH 1/3] feat(nfConfigApi): add guaranteed bit rate to PccQos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PccQos carries maxBrUl and maxBrDl but no guaranteed rates, so a policy served to the PCF can express a ceiling and not a floor. WebConsole already accepts GBRUL and GBRDL on its flow rules and stores them; they have nowhere to go from here, and are dropped on the way out. gbrUl and gbrDl are optional strings, treated exactly as the maximum rates beside them: no pattern, since the existing bit rate fields carry none either. The model and the spec are updated together. There is no committed generator configuration for this package, so the spec is kept in step by hand and a future regeneration keeps the fields rather than dropping them. Tests cover the round trip, omission when unset, and that a supplied value lands in the field rather than in AdditionalProperties — the last verified to fail without the delete() calls in UnmarshalJSON, which is the part easiest to leave out. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GyNr6vp6JaxxzPyVcuHXTf Signed-off-by: Edvin Lindqvist --- nfConfigApi/model_pcc_qos.go | 74 ++++++++++++++++++++++++ nfConfigApi/model_pcc_qos_gbr_test.go | 81 +++++++++++++++++++++++++++ nfConfigApi/webconsole-api.yaml | 4 ++ 3 files changed, 159 insertions(+) create mode 100644 nfConfigApi/model_pcc_qos_gbr_test.go diff --git a/nfConfigApi/model_pcc_qos.go b/nfConfigApi/model_pcc_qos.go index ca81b1e7..8e87c9bb 100644 --- a/nfConfigApi/model_pcc_qos.go +++ b/nfConfigApi/model_pcc_qos.go @@ -31,6 +31,8 @@ type PccQos struct { FiveQi int32 `json:"fiveQi" yaml:"fiveQi"` MaxBrUl *string `json:"maxBrUl,omitempty" yaml:"maxBrUl,omitempty"` MaxBrDl *string `json:"maxBrDl,omitempty" yaml:"maxBrDl,omitempty"` + GbrUl *string `json:"gbrUl,omitempty" yaml:"gbrUl,omitempty"` + GbrDl *string `json:"gbrDl,omitempty" yaml:"gbrDl,omitempty"` Arp Arp `json:"arp" yaml:"arp"` AdditionalProperties map[string]any } @@ -144,6 +146,70 @@ func (o *PccQos) SetMaxBrDl(v string) { o.MaxBrDl = &v } +// GetGbrUl returns the GbrUl field value if set, zero value otherwise. +func (o *PccQos) GetGbrUl() string { + if o == nil || openapi.IsNil(o.GbrUl) { + var ret string + return ret + } + return *o.GbrUl +} + +// GetGbrUlOk returns a tuple with the GbrUl field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *PccQos) GetGbrUlOk() (*string, bool) { + if o == nil || openapi.IsNil(o.GbrUl) { + return nil, false + } + return o.GbrUl, true +} + +// HasGbrUl returns a boolean if a field has been set. +func (o *PccQos) HasGbrUl() bool { + if o != nil && !openapi.IsNil(o.GbrUl) { + return true + } + + return false +} + +// SetGbrUl gets a reference to the given string and assigns it to the GbrUl field. +func (o *PccQos) SetGbrUl(v string) { + o.GbrUl = &v +} + +// GetGbrDl returns the GbrDl field value if set, zero value otherwise. +func (o *PccQos) GetGbrDl() string { + if o == nil || openapi.IsNil(o.GbrDl) { + var ret string + return ret + } + return *o.GbrDl +} + +// GetGbrDlOk returns a tuple with the GbrDl field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *PccQos) GetGbrDlOk() (*string, bool) { + if o == nil || openapi.IsNil(o.GbrDl) { + return nil, false + } + return o.GbrDl, true +} + +// HasGbrDl returns a boolean if a field has been set. +func (o *PccQos) HasGbrDl() bool { + if o != nil && !openapi.IsNil(o.GbrDl) { + return true + } + + return false +} + +// SetGbrDl gets a reference to the given string and assigns it to the GbrDl field. +func (o *PccQos) SetGbrDl(v string) { + o.GbrDl = &v +} + // GetArp returns the Arp field value func (o *PccQos) GetArp() Arp { if o == nil { @@ -188,6 +254,12 @@ func (o PccQos) ToMap() (map[string]any, error) { if !openapi.IsNil(o.MaxBrDl) { toSerialize["maxBrDl"] = o.MaxBrDl } + if !openapi.IsNil(o.GbrUl) { + toSerialize["gbrUl"] = o.GbrUl + } + if !openapi.IsNil(o.GbrDl) { + toSerialize["gbrDl"] = o.GbrDl + } toSerialize["arp"] = o.Arp return toSerialize, nil } @@ -231,6 +303,8 @@ func (o *PccQos) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "fiveQi") delete(additionalProperties, "maxBrUl") delete(additionalProperties, "maxBrDl") + delete(additionalProperties, "gbrUl") + delete(additionalProperties, "gbrDl") delete(additionalProperties, "arp") o.AdditionalProperties = additionalProperties } diff --git a/nfConfigApi/model_pcc_qos_gbr_test.go b/nfConfigApi/model_pcc_qos_gbr_test.go new file mode 100644 index 00000000..e60a54d6 --- /dev/null +++ b/nfConfigApi/model_pcc_qos_gbr_test.go @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2026 Forsway Scandinavia AB +// SPDX-License-Identifier: Apache-2.0 + +package nfConfigApi + +import ( + "encoding/json" + "testing" +) + +// A guaranteed bit rate has to survive the round trip, or it is configured by an operator and +// discarded somewhere between WebConsole and the PCF. +func TestPccQosGuaranteedBitRateRoundTrips(t *testing.T) { + original := NewPccQos(2, *NewArp(1, PREEMPTCAP_MAY_PREEMPT, PREEMPTVULN_PREEMPTABLE)) + original.SetMaxBrUl("50 Mbps") + original.SetMaxBrDl("50 Mbps") + original.SetGbrUl("10 Mbps") + original.SetGbrDl("20 Mbps") + + encoded, err := json.Marshal(original) + if err != nil { + t.Fatalf("marshal failed: %v", err) + } + + var decoded PccQos + if err := json.Unmarshal(encoded, &decoded); err != nil { + t.Fatalf("unmarshal failed: %v", err) + } + + if got := decoded.GetGbrUl(); got != "10 Mbps" { + t.Errorf("gbrUl = %q, want %q", got, "10 Mbps") + } + if got := decoded.GetGbrDl(); got != "20 Mbps" { + t.Errorf("gbrDl = %q, want %q", got, "20 Mbps") + } + if got := decoded.GetMaxBrUl(); got != "50 Mbps" { + t.Errorf("maxBrUl = %q, want it unaffected", got) + } +} + +// The fields are optional, exactly like the maximum bit rates beside them, so a non-GBR flow +// carries neither and serialises without them. +func TestPccQosWithoutGuaranteedBitRateOmitsTheFields(t *testing.T) { + qos := NewPccQos(9, *NewArp(1, PREEMPTCAP_MAY_PREEMPT, PREEMPTVULN_PREEMPTABLE)) + + if qos.HasGbrUl() || qos.HasGbrDl() { + t.Error("a QoS with no guaranteed rate must not report having one") + } + + encoded, err := json.Marshal(qos) + if err != nil { + t.Fatalf("marshal failed: %v", err) + } + + var asMap map[string]any + if err := json.Unmarshal(encoded, &asMap); err != nil { + t.Fatalf("unmarshal failed: %v", err) + } + for _, absent := range []string{"gbrUl", "gbrDl"} { + if _, present := asMap[absent]; present { + t.Errorf("%s must be omitted when unset", absent) + } + } +} + +// A guaranteed rate present in the JSON must decode into the field itself and not into +// AdditionalProperties, which is where unknown keys go — landing there would mean the model +// does not recognise it. +func TestPccQosGuaranteedBitRateIsNotAnAdditionalProperty(t *testing.T) { + var decoded PccQos + if err := json.Unmarshal([]byte(`{"fiveQi":2,"gbrUl":"10 Mbps","arp":{"priorityLevel":1,"preemptCap":"MAY_PREEMPT","preemptVuln":"PREEMPTABLE"}}`), &decoded); err != nil { + t.Fatalf("unmarshal failed: %v", err) + } + + if _, leaked := decoded.AdditionalProperties["gbrUl"]; leaked { + t.Error("gbrUl leaked into AdditionalProperties; the model does not recognise it") + } + if got := decoded.GetGbrUl(); got != "10 Mbps" { + t.Errorf("gbrUl = %q, want it decoded into the field", got) + } +} diff --git a/nfConfigApi/webconsole-api.yaml b/nfConfigApi/webconsole-api.yaml index 66a3fea2..fb7ebe7d 100644 --- a/nfConfigApi/webconsole-api.yaml +++ b/nfConfigApi/webconsole-api.yaml @@ -228,6 +228,10 @@ components: type: string maxBrDl: type: string + gbrUl: + type: string + gbrDl: + type: string arp: $ref: '#/components/schemas/Arp' Arp: From f25b8aefd657a24c3ff7291f4d2ca8941650d355 Mon Sep 17 00:00:00 2001 From: Edvin Lindqvist Date: Fri, 28 Aug 2026 10:52:47 +0200 Subject: [PATCH 2/3] Create patch release 2.2.1 The webconsole and pcf changes that populate gbrUl/gbrDl cannot be opened until these fields are in a released openapi, so drop -dev to have the pipeline tag 2.2.1 on merge. Co-Authored-By: Claude Opus 5 Signed-off-by: Edvin Lindqvist --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 84e6d845..c043eea7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.1-dev +2.2.1 From 0d25c35a1d67befab5dfed60b4a26c298cc5ede2 Mon Sep 17 00:00:00 2001 From: Edvin Lindqvist Date: Fri, 28 Aug 2026 16:30:58 +0200 Subject: [PATCH 3/3] test(nfConfigApi): drop the model test; the consumer already covers this The reviewer's objection was that this is the only model_xxx_test.go in the repo: openapi-generator does not produce them, so it reads as unexplained hand-written code inside a generated tree, and a regeneration that replaces the directory takes it with no sign it was deliberate. His alternative of an api_default_test.go does not hold either. All 311 api_xxx_test.go files in this repo carry the generator's DO NOT EDIT header - that name is the generator's own output slot, one per api file in every package, and nfConfigApi has simply never had one. Hand-written assertions there would be overwritten silently rather than merely deleted. So the assertions go where he suggested, the consumer: webconsole's backend/nfconfig/pcc_qos_gbr_test.go already exercises the same fields through buildPccQos, including the one-directional case, and arrives with the change that populates them. This PR is left as the generated shape and the release. Co-Authored-By: Claude Opus 5 Signed-off-by: Edvin Lindqvist --- nfConfigApi/model_pcc_qos_gbr_test.go | 81 --------------------------- 1 file changed, 81 deletions(-) delete mode 100644 nfConfigApi/model_pcc_qos_gbr_test.go diff --git a/nfConfigApi/model_pcc_qos_gbr_test.go b/nfConfigApi/model_pcc_qos_gbr_test.go deleted file mode 100644 index e60a54d6..00000000 --- a/nfConfigApi/model_pcc_qos_gbr_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Forsway Scandinavia AB -// SPDX-License-Identifier: Apache-2.0 - -package nfConfigApi - -import ( - "encoding/json" - "testing" -) - -// A guaranteed bit rate has to survive the round trip, or it is configured by an operator and -// discarded somewhere between WebConsole and the PCF. -func TestPccQosGuaranteedBitRateRoundTrips(t *testing.T) { - original := NewPccQos(2, *NewArp(1, PREEMPTCAP_MAY_PREEMPT, PREEMPTVULN_PREEMPTABLE)) - original.SetMaxBrUl("50 Mbps") - original.SetMaxBrDl("50 Mbps") - original.SetGbrUl("10 Mbps") - original.SetGbrDl("20 Mbps") - - encoded, err := json.Marshal(original) - if err != nil { - t.Fatalf("marshal failed: %v", err) - } - - var decoded PccQos - if err := json.Unmarshal(encoded, &decoded); err != nil { - t.Fatalf("unmarshal failed: %v", err) - } - - if got := decoded.GetGbrUl(); got != "10 Mbps" { - t.Errorf("gbrUl = %q, want %q", got, "10 Mbps") - } - if got := decoded.GetGbrDl(); got != "20 Mbps" { - t.Errorf("gbrDl = %q, want %q", got, "20 Mbps") - } - if got := decoded.GetMaxBrUl(); got != "50 Mbps" { - t.Errorf("maxBrUl = %q, want it unaffected", got) - } -} - -// The fields are optional, exactly like the maximum bit rates beside them, so a non-GBR flow -// carries neither and serialises without them. -func TestPccQosWithoutGuaranteedBitRateOmitsTheFields(t *testing.T) { - qos := NewPccQos(9, *NewArp(1, PREEMPTCAP_MAY_PREEMPT, PREEMPTVULN_PREEMPTABLE)) - - if qos.HasGbrUl() || qos.HasGbrDl() { - t.Error("a QoS with no guaranteed rate must not report having one") - } - - encoded, err := json.Marshal(qos) - if err != nil { - t.Fatalf("marshal failed: %v", err) - } - - var asMap map[string]any - if err := json.Unmarshal(encoded, &asMap); err != nil { - t.Fatalf("unmarshal failed: %v", err) - } - for _, absent := range []string{"gbrUl", "gbrDl"} { - if _, present := asMap[absent]; present { - t.Errorf("%s must be omitted when unset", absent) - } - } -} - -// A guaranteed rate present in the JSON must decode into the field itself and not into -// AdditionalProperties, which is where unknown keys go — landing there would mean the model -// does not recognise it. -func TestPccQosGuaranteedBitRateIsNotAnAdditionalProperty(t *testing.T) { - var decoded PccQos - if err := json.Unmarshal([]byte(`{"fiveQi":2,"gbrUl":"10 Mbps","arp":{"priorityLevel":1,"preemptCap":"MAY_PREEMPT","preemptVuln":"PREEMPTABLE"}}`), &decoded); err != nil { - t.Fatalf("unmarshal failed: %v", err) - } - - if _, leaked := decoded.AdditionalProperties["gbrUl"]; leaked { - t.Error("gbrUl leaked into AdditionalProperties; the model does not recognise it") - } - if got := decoded.GetGbrUl(); got != "10 Mbps" { - t.Errorf("gbrUl = %q, want it decoded into the field", got) - } -}