From 85ecb9fb99e660ef5f404a31a1a20fd9812ed4a1 Mon Sep 17 00:00:00 2001 From: Dario Faccin <35623244+dariofaccin@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:01:25 +0100 Subject: [PATCH 1/6] feat: add the fiveg_core_gnb interface (#194) * feat: add the fiveg_core_gnb interface Signed-off-by: Dario Faccin * fix: use correct names for provider and requirer app data classes Signed-off-by: Dario Faccin * chore: update relation according to new spec Signed-off-by: Dario Faccin * fix: use List from typing to avoid TypeError Signed-off-by: Dario Faccin * chore: address review comments Signed-off-by: Dario Faccin * chore: improve schema Signed-off-by: Dario Faccin * fix: make Slice Differentiator optional Signed-off-by: Dario Faccin --------- Signed-off-by: Dario Faccin --- .../fiveg_core_gnb/v0/provider.json | 122 ++++++++++++++++++ .../fiveg_core_gnb/v0/requirer.json | 48 +++++++ interfaces/fiveg_core_gnb/v0/README.md | 67 ++++++++++ interfaces/fiveg_core_gnb/v0/interface.yaml | 14 ++ interfaces/fiveg_core_gnb/v0/schema.py | 87 +++++++++++++ 5 files changed, 338 insertions(+) create mode 100644 docs/json_schemas/fiveg_core_gnb/v0/provider.json create mode 100644 docs/json_schemas/fiveg_core_gnb/v0/requirer.json create mode 100644 interfaces/fiveg_core_gnb/v0/README.md create mode 100644 interfaces/fiveg_core_gnb/v0/interface.yaml create mode 100644 interfaces/fiveg_core_gnb/v0/schema.py diff --git a/docs/json_schemas/fiveg_core_gnb/v0/provider.json b/docs/json_schemas/fiveg_core_gnb/v0/provider.json new file mode 100644 index 00000000..8269d932 --- /dev/null +++ b/docs/json_schemas/fiveg_core_gnb/v0/provider.json @@ -0,0 +1,122 @@ +{ + "$defs": { + "BaseModel": { + "properties": {}, + "title": "BaseModel", + "type": "object" + }, + "FivegCoreGnbProviderAppData": { + "properties": { + "tac": { + "description": "Tracking Area Code", + "examples": [ + 1 + ], + "maximum": 16777215, + "minimum": 1, + "title": "Tac", + "type": "integer" + }, + "plmns": { + "items": { + "$ref": "#/$defs/PLMNConfig" + }, + "title": "Plmns", + "type": "array" + } + }, + "required": [ + "tac", + "plmns" + ], + "title": "FivegCoreGnbProviderAppData", + "type": "object" + }, + "PLMNConfig": { + "properties": { + "mcc": { + "description": "Mobile Country Code", + "examples": [ + "001", + "208", + "302" + ], + "pattern": "[0-9][0-9][0-9]", + "title": "Mcc", + "type": "string" + }, + "mnc": { + "description": "Mobile Network Code", + "examples": [ + "01", + "001", + "999" + ], + "pattern": "[0-9][0-9][0-9]?", + "title": "Mnc", + "type": "string" + }, + "sst": { + "description": "Slice/Service Type", + "examples": [ + 1, + 2, + 3, + 4 + ], + "maximum": 255, + "minimum": 0, + "title": "Sst", + "type": "integer" + }, + "sd": { + "anyOf": [ + { + "maximum": 16777215, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Slice Differentiator", + "examples": [ + 1 + ], + "title": "Sd" + } + }, + "required": [ + "mcc", + "mnc", + "sst" + ], + "title": "PLMNConfig", + "type": "object" + } + }, + "description": "The schema for the provider side of the fiveg_core_gnb interface.", + "properties": { + "unit": { + "anyOf": [ + { + "$ref": "#/$defs/BaseModel" + }, + { + "type": "null" + } + ], + "default": null + }, + "app": { + "$ref": "#/$defs/FivegCoreGnbProviderAppData" + } + }, + "required": [ + "app" + ], + "title": "ProviderSchema", + "type": "object" +} \ No newline at end of file diff --git a/docs/json_schemas/fiveg_core_gnb/v0/requirer.json b/docs/json_schemas/fiveg_core_gnb/v0/requirer.json new file mode 100644 index 00000000..73557dbc --- /dev/null +++ b/docs/json_schemas/fiveg_core_gnb/v0/requirer.json @@ -0,0 +1,48 @@ +{ + "$defs": { + "BaseModel": { + "properties": {}, + "title": "BaseModel", + "type": "object" + }, + "FivegCoreGnbRequirerAppData": { + "properties": { + "cu_name": { + "description": "Unique identifier of the CU/gnB.", + "examples": [ + "gnb001" + ], + "title": "Cu Name", + "type": "string" + } + }, + "required": [ + "cu_name" + ], + "title": "FivegCoreGnbRequirerAppData", + "type": "object" + } + }, + "description": "The schema for the requirer side of the fiveg_core_gnb interface.", + "properties": { + "unit": { + "anyOf": [ + { + "$ref": "#/$defs/BaseModel" + }, + { + "type": "null" + } + ], + "default": null + }, + "app": { + "$ref": "#/$defs/FivegCoreGnbRequirerAppData" + } + }, + "required": [ + "app" + ], + "title": "RequirerSchema", + "type": "object" +} \ No newline at end of file diff --git a/interfaces/fiveg_core_gnb/v0/README.md b/interfaces/fiveg_core_gnb/v0/README.md new file mode 100644 index 00000000..bd50381c --- /dev/null +++ b/interfaces/fiveg_core_gnb/v0/README.md @@ -0,0 +1,67 @@ +# `fiveg_core_gnb` + +## Usage + +Within 5G, the CU is the Central Unit of a RAN (Radio Access Network) and needs to be configured according to the 5G network parameters. + +The `fiveg_core_gnb` relation interface describes the expected behavior of any charm claiming to be able to provide or consume the CU (or gNodeB) configuration information. + +In a typical 5G network, the provider of this interface would be a CU or a gNodeB. The requirer of this interface would be the NMS (Network Management System). + +## Direction + +```mermaid +flowchart TD + Provider -- MCC, MNC, TAC, SST, SD --> Requirer + Requirer -- CU/gNodeB Identifier --> Provider +``` + +As with all Juju relations, the `fiveg_core_gnb` interface consists of two parties: a Provider and a Requirer. + +## Behavior + +Both the Requirer and the Provider need to adhere to criteria to be considered compatible with the interface. + +### Provider + +- Is expected to provide the following data: + - TAC (Tracking Area Code) + - List of PLMNs + +The list of PLMNs should include the following data: + - MCC (Mobile Country Code) + - MNC (Mobile Network Code) + - SST (Slice Service Type) + - SD (Slice Differentiator) + + +### Requirer + +- Is expected to provide a unique identifier of the CU (or gNodeB). + +## Relation Data + +[\[Pydantic Schema\]](./schema.py) + +#### Example + +```yaml +provider: + app: { + "tac": 1, + "plmns": [ + { + "mcc": "001", + "mnc": "01", + "sst": 1, + "sd": 1, + } + ], + } + unit: {} +requirer: + app: { + "cu_name": "gnb001" + } + unit: {} +``` diff --git a/interfaces/fiveg_core_gnb/v0/interface.yaml b/interfaces/fiveg_core_gnb/v0/interface.yaml new file mode 100644 index 00000000..6edceeb2 --- /dev/null +++ b/interfaces/fiveg_core_gnb/v0/interface.yaml @@ -0,0 +1,14 @@ +name: fiveg_core_gnb +internal: true + +version: 0 +status: draft + +providers: + - name: sdcore-nms-k8s-operator + url: https://github.com/canonical/sdcore-nms-k8s-operator +requirers: + - name: sdcore-gnbsim-k8s-operator + url: https://github.com/canonical/sdcore-gnbsim-k8s-operator + +maintainer: telco diff --git a/interfaces/fiveg_core_gnb/v0/schema.py b/interfaces/fiveg_core_gnb/v0/schema.py new file mode 100644 index 00000000..9da5846c --- /dev/null +++ b/interfaces/fiveg_core_gnb/v0/schema.py @@ -0,0 +1,87 @@ +"""This file defines the schemas for the provider and requirer sides of the `fiveg_core_gnb` relation interface. + +It must expose two interfaces.schema_base.DataBagSchema subclasses called: +- ProviderSchema +- RequirerSchema + +Examples: + ProviderSchema: + unit: + app: { + "tac": 1, + "plmns": [ + { + "mcc": "001", + "mnc": "01", + "sst": 1, + "sd": 1, + } + ], + } + RequirerSchema: + unit: + app: { + "cu_name": "gnb001", + } +""" + +from dataclasses import dataclass +from interface_tester.schema_base import DataBagSchema +from pydantic import BaseModel, Field +from typing import List, Optional + + +@dataclass +class PLMNConfig: + """Dataclass representing the configuration for a PLMN.""" + + mcc: str = Field( + description="Mobile Country Code", + examples=["001", "208", "302"], + pattern=r"[0-9][0-9][0-9]", + ) + mnc: str = Field( + description="Mobile Network Code", + examples=["01", "001", "999"], + pattern=r"[0-9][0-9][0-9]?", + ) + sst: int = Field( + description="Slice/Service Type", + examples=[1, 2, 3, 4], + ge=0, + le=255, + ) + sd: Optional[int] = Field( + description="Slice Differentiator", + default=None, + examples=[1], + ge=0, + le=16777215, + ) + + +class FivegCoreGnbProviderAppData(BaseModel): + tac: int = Field( + description="Tracking Area Code", + examples=[1], + ge=1, + le=16777215, + ) + plmns: List[PLMNConfig] + + +class FivegCoreGnbRequirerAppData(BaseModel): + cu_name: str = Field( + description="Unique identifier of the CU/gnB.", + examples=["gnb001"] + ) + + +class ProviderSchema(DataBagSchema): + """The schema for the provider side of the fiveg_core_gnb interface.""" + app: FivegCoreGnbProviderAppData + + +class RequirerSchema(DataBagSchema): + """The schema for the requirer side of the fiveg_core_gnb interface.""" + app: FivegCoreGnbRequirerAppData From 741f208516d8c6d89551e8a50fd11f057e71fab2 Mon Sep 17 00:00:00 2001 From: Tiexin Guo Date: Thu, 28 Nov 2024 08:24:50 +0800 Subject: [PATCH 2/6] ci: update matrix test strategy to fail fast, fix test error, fix json validation error --- .github/workflows/matrix-tests.yaml | 7 ++++++- docs/json_schemas/dns_record/v0/requirer.json | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matrix-tests.yaml b/.github/workflows/matrix-tests.yaml index 82128a27..94fc06c5 100644 --- a/.github/workflows/matrix-tests.yaml +++ b/.github/workflows/matrix-tests.yaml @@ -26,11 +26,16 @@ jobs: needs: set-matrix runs-on: ubuntu-latest strategy: + fail-fast: false matrix: interface: ${{ fromJSON(needs.set-matrix.outputs.matrix_values) }} steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 - name: Set up python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install dependencies diff --git a/docs/json_schemas/dns_record/v0/requirer.json b/docs/json_schemas/dns_record/v0/requirer.json index 91825545..6e7b913a 100644 --- a/docs/json_schemas/dns_record/v0/requirer.json +++ b/docs/json_schemas/dns_record/v0/requirer.json @@ -33,7 +33,6 @@ "type": "object" }, "RecordClass": { - "const": "IN", "description": "Represent the DNS record classes.", "enum": [ "IN" From 58032d849fa49f79162bef79265bfb902df8ddcb Mon Sep 17 00:00:00 2001 From: Patricia Reinoso Date: Thu, 28 Nov 2024 20:36:32 +0100 Subject: [PATCH 3/6] feat: extend interface fiveg_f1 interface (#201) * extend interface fiveg_f1 * generate docs * extend v0 instead of adding v1 * restore dns schema * fix json schemas job * make mcc nad mnc more strict * not allow empty plmns list * update README --- docs/json_schemas/fiveg_f1/v0/provider.json | 85 ++++++++++++++++++++- interfaces/fiveg_f1/v0/README.md | 27 ++++++- interfaces/fiveg_f1/v0/schema.py | 48 +++++++++++- 3 files changed, 153 insertions(+), 7 deletions(-) diff --git a/docs/json_schemas/fiveg_f1/v0/provider.json b/docs/json_schemas/fiveg_f1/v0/provider.json index 39f6fa31..c1a4ec3d 100644 --- a/docs/json_schemas/fiveg_f1/v0/provider.json +++ b/docs/json_schemas/fiveg_f1/v0/provider.json @@ -23,14 +23,97 @@ ], "title": "F1 Port", "type": "integer" + }, + "tac": { + "description": "Tracking Area Code", + "examples": [ + 1 + ], + "maximum": 16777215, + "minimum": 1, + "title": "Tac", + "type": "integer" + }, + "plmns": { + "items": { + "$ref": "#/$defs/PLMNConfig" + }, + "title": "Plmns", + "type": "array" } }, "required": [ "f1_ip_address", - "f1_port" + "f1_port", + "tac", + "plmns" ], "title": "FivegF1ProviderAppData", "type": "object" + }, + "PLMNConfig": { + "properties": { + "mcc": { + "description": "Mobile Country Code", + "examples": [ + "001", + "208", + "302" + ], + "pattern": "^[0-9][0-9][0-9]$", + "title": "Mcc", + "type": "string" + }, + "mnc": { + "description": "Mobile Network Code", + "examples": [ + "01", + "001", + "999" + ], + "pattern": "^[0-9][0-9][0-9]?$", + "title": "Mnc", + "type": "string" + }, + "sst": { + "description": "Slice/Service Type", + "examples": [ + 1, + 2, + 3, + 4 + ], + "maximum": 255, + "minimum": 0, + "title": "Sst", + "type": "integer" + }, + "sd": { + "anyOf": [ + { + "maximum": 16777215, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Slice Differentiator", + "examples": [ + 1 + ], + "title": "Sd" + } + }, + "required": [ + "mcc", + "mnc", + "sst" + ], + "title": "PLMNConfig", + "type": "object" } }, "description": "Provider schema for fiveg_f1.", diff --git a/interfaces/fiveg_f1/v0/README.md b/interfaces/fiveg_f1/v0/README.md index 5c4e5a93..2ace380a 100644 --- a/interfaces/fiveg_f1/v0/README.md +++ b/interfaces/fiveg_f1/v0/README.md @@ -10,7 +10,7 @@ This relation interface describes the expected behavior of any charm claiming to ```mermaid flowchart TD - Provider -- ip_address, port --> Requirer + Provider -- ip_address, port, tac, plmns --> Requirer Requirer -- port --> Provider ``` @@ -22,12 +22,22 @@ Both the Requirer and the Provider need to adhere to criteria to be considered c ### Provider -- Is expected to provide the IP address and port of the CU's F1 interface. +- Is expected to provide the following data: + - IP address of the network interface used for F1 traffic + - Number of the port used for F1 traffic + - TAC (Tracking Area Code) + - List of PLMNs + +The list of PLMNs should include the following data: + - MCC (Mobile Country Code) + - MNC (Mobile Network Code) + - SST (Slice Service Type) + - SD (Slice Differentiator) ### Requirer - Is expected to use the IP address and the port passed by the provider to establish communication over the F1 interface. -- Is expected to provider the number of the port which will handle communication over the F1 interface. +- Is expected to provide the number of the port which will handle communication over the F1 interface. ## Relation Data @@ -39,7 +49,16 @@ Both the Requirer and the Provider need to adhere to criteria to be considered c provider: app: { "f1_ip_address": "192.168.70.132", - "f1_port": 2153 + "f1_port": 2153, + "tac": 1, + "plmns": [ + { + "mcc": "001", + "mnc": "01", + "sst": 1, + "sd": 1, + } + ], } unit: {} requirer: diff --git a/interfaces/fiveg_f1/v0/schema.py b/interfaces/fiveg_f1/v0/schema.py index 257afb35..d783c2e5 100644 --- a/interfaces/fiveg_f1/v0/schema.py +++ b/interfaces/fiveg_f1/v0/schema.py @@ -7,7 +7,16 @@ unit: app: { "f1_ip_address": "192.168.70.132", - "f1_port": 2153 + "f1_port": 2153, + "tac": 1, + "plmns": [ + { + "mcc": "001", + "mnc": "01", + "sst": 1, + "sd": 1, + } + ], } RequirerSchema: unit: @@ -17,9 +26,37 @@ """ from pydantic import BaseModel, IPvAnyAddress, Field - +from dataclasses import dataclass from interface_tester.schema_base import DataBagSchema +from typing import List, Optional, conlist +@dataclass +class PLMNConfig: + """Dataclass representing the configuration for a PLMN.""" + + mcc: str = Field( + description="Mobile Country Code", + examples=["001", "208", "302"], + pattern=r"^[0-9][0-9][0-9]$", + ) + mnc: str = Field( + description="Mobile Network Code", + examples=["01", "001", "999"], + pattern=r"^[0-9][0-9][0-9]?$", + ) + sst: int = Field( + description="Slice/Service Type", + examples=[1, 2, 3, 4], + ge=0, + le=255, + ) + sd: Optional[int] = Field( + description="Slice Differentiator", + default=None, + examples=[1], + ge=0, + le=16777215, + ) class FivegF1ProviderAppData(BaseModel): f1_ip_address: IPvAnyAddress = Field( @@ -30,6 +67,13 @@ class FivegF1ProviderAppData(BaseModel): description="Number of the port used for F1 traffic", examples=[2153] ) + tac: int = Field( + description="Tracking Area Code", + examples=[1], + ge=1, + le=16777215, + ) + plmns: conlist(PLMNConfig, min_length=1) class FivegF1RequirerAppData(BaseModel): From f8ad55c224a9bfac9e39415db68198b182c16f26 Mon Sep 17 00:00:00 2001 From: PietroPasotti Date: Sun, 1 Dec 2024 21:14:24 +0100 Subject: [PATCH 4/6] fixed interface tests for tracing v2 (#199) * fixed interface tests for tracing v2 * correct databags * removed bad paths --- .../v2/interface_tests/test_provider.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/interfaces/tracing/v2/interface_tests/test_provider.py b/interfaces/tracing/v2/interface_tests/test_provider.py index 8a065c00..8e69ce36 100644 --- a/interfaces/tracing/v2/interface_tests/test_provider.py +++ b/interfaces/tracing/v2/interface_tests/test_provider.py @@ -5,6 +5,19 @@ from interface_tester.interface_test import Tester from scenario import State, Relation +_VALID_REQUIRER_APP_DATA = {"receivers": json.dumps( + [ + { + "protocol": { + "name": "otlp_grpc", + "type": "grpc" + }, + "url": "http://192.0.2.0/24" + } + ] +) +} + def test_data_on_created(): tester = Tester( @@ -14,9 +27,7 @@ def test_data_on_created(): endpoint='tracing', interface='tracing', remote_app_name='remote', - remote_app_data={ - "receivers": json.dumps(["otlp_grpc"]) - } + remote_app_data=_VALID_REQUIRER_APP_DATA ) ] ) @@ -33,10 +44,7 @@ def test_data_on_joined(): endpoint='tracing', interface='tracing', remote_app_name='remote', - remote_app_data={ - "receivers": json.dumps(["otlp_grpc"]) - } - ) + remote_app_data=_VALID_REQUIRER_APP_DATA ) ] ) ) @@ -52,10 +60,7 @@ def test_data_on_changed(): endpoint='tracing', interface='tracing', remote_app_name='remote', - remote_app_data={ - "receivers": json.dumps(["otlp_grpc"]) - } - ) + remote_app_data=_VALID_REQUIRER_APP_DATA ) ] ) ) From a04d3b8f1020bd338b2253ab166b7d332afa5707 Mon Sep 17 00:00:00 2001 From: Gulsum Atici Date: Sun, 1 Dec 2024 23:27:07 +0300 Subject: [PATCH 5/6] feat: Extend fiveg_rfsim interface (#202) * feat: Expand fiveg_rfsim interface Signed-off-by: gatici * fix typo Signed-off-by: gatici * use lowercase in the flowchart Signed-off-by: gatici * Update README.md Signed-off-by: gatici --------- Signed-off-by: gatici --- .../json_schemas/fiveg_rfsim/v0/provider.json | 34 ++++++++++++++++++- interfaces/fiveg_rfsim/v0/README.md | 14 +++++--- interfaces/fiveg_rfsim/v0/schema.py | 19 ++++++++++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/docs/json_schemas/fiveg_rfsim/v0/provider.json b/docs/json_schemas/fiveg_rfsim/v0/provider.json index 80a64032..ed0d712d 100644 --- a/docs/json_schemas/fiveg_rfsim/v0/provider.json +++ b/docs/json_schemas/fiveg_rfsim/v0/provider.json @@ -14,10 +14,42 @@ ], "title": "Rfsim Address", "type": "string" + }, + "sst": { + "description": "Slice/Service Type", + "examples": [ + 1, + 2, + 3, + 4 + ], + "maximum": 255, + "minimum": 0, + "title": "Sst", + "type": "integer" + }, + "sd": { + "anyOf": [ + { + "maximum": 16777215, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Slice Differentiator", + "examples": [ + 1 + ], + "title": "Sd" } }, "required": [ - "rfsim_address" + "rfsim_address", + "sst" ], "title": "FivegRFSIMProviderAppData", "type": "object" diff --git a/interfaces/fiveg_rfsim/v0/README.md b/interfaces/fiveg_rfsim/v0/README.md index 926c053e..640b95c5 100644 --- a/interfaces/fiveg_rfsim/v0/README.md +++ b/interfaces/fiveg_rfsim/v0/README.md @@ -4,7 +4,7 @@ Within 5G RAN (Radio Access Network) architecture, the OAI DU charm can be started to act as both the DU and the RU through its RF simulator functionality. -The OAI UE charm requires RF simulator address in order to connect. Hence, the provider of this interface would be a OAI DU charm and the requirer of this interface would be the OAI UE charm. +The OAI UE charm requires RF simulator address and the network information(SST, SD) in order to connect. Hence, the provider of this interface would be a OAI DU charm and the requirer of this interface would be the OAI UE charm. This relation interface describes the expected behavior of charms claiming to be able to provide or consume information on connectivity over the fiveg_rfsim interface. @@ -12,7 +12,7 @@ This relation interface describes the expected behavior of charms claiming to be ```mermaid flowchart TD - Provider -- rfsim_address --> Requirer + Provider -- rfsim_address, sst, sd --> Requirer ``` As with all Juju relations, the `fiveg_rfsim` interface consists of two parties: a Provider and a Requirer. @@ -23,11 +23,15 @@ Both the Requirer and the Provider need to adhere to criteria to be considered c ### Provider -- Is expected to provide the DU's `rfsim` service ip. +Is expected to provide following information: + +- The DU's `rfsim` service ip +- Network Slice/Service Type (SST) +- Slice Differentiator (SD) ### Requirer -- Is expected to use the `rfsim` service address passed by the provider. +- Is expected to use the `rfsim` service address and the network information(SST, SD) passed by the provider. ## Relation Data @@ -39,6 +43,8 @@ Both the Requirer and the Provider need to adhere to criteria to be considered c provider: app: { "rfsim_address": "192.168.70.130", + "sst": 1, + "sd": 1, } unit: {} requirer: diff --git a/interfaces/fiveg_rfsim/v0/schema.py b/interfaces/fiveg_rfsim/v0/schema.py index c53c2700..0363cf96 100644 --- a/interfaces/fiveg_rfsim/v0/schema.py +++ b/interfaces/fiveg_rfsim/v0/schema.py @@ -7,11 +7,14 @@ unit: app: { "rfsim_address": "192.168.70.130", + "sst": 1, + "sd": 1, } RequirerSchema: unit: app: """ +from typing import Optional from pydantic import BaseModel, Field @@ -23,6 +26,20 @@ class FivegRFSIMProviderAppData(BaseModel): description="RF simulator service ip", examples=["192.168.70.130"] ) + sst: int = Field( + description="Slice/Service Type", + examples=[1, 2, 3, 4], + ge=0, + le=255, + ) + sd: Optional[int] = Field( + description="Slice Differentiator", + default=None, + examples=[1], + ge=0, + le=16777215, + ) + class ProviderSchema(DataBagSchema): """Provider schema for the fiveg_rfsim interface.""" @@ -30,4 +47,4 @@ class ProviderSchema(DataBagSchema): class RequirerSchema(DataBagSchema): - """Requirer schema for the fiveg_rfsim interface.""" \ No newline at end of file + """Requirer schema for the fiveg_rfsim interface.""" From 2ccb05cf82ec4dd44ea7fbdfc917433b97fb2a23 Mon Sep 17 00:00:00 2001 From: Michael Dmitry <33381599+michaeldmitry@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:21:25 +0200 Subject: [PATCH 6/6] Update `tempo_cluster` schema (#198) * update tempo_cluster schema * update version * resolve comments * use uppercase enums --- .../tempo_cluster/v1/provider.json | 184 ++++++++++++++++++ .../tempo_cluster/v1/requirer.json | 116 +++++++++++ interfaces/tempo_cluster/v1/README.md | 54 +++++ interfaces/tempo_cluster/v1/interface.yaml | 18 ++ .../v1/interface_tests/test_provider.py | 95 +++++++++ .../v1/interface_tests/test_requirer.py | 31 +++ interfaces/tempo_cluster/v1/schema.py | 93 +++++++++ utils/interface-validator.py | 2 +- 8 files changed, 592 insertions(+), 1 deletion(-) create mode 100644 docs/json_schemas/tempo_cluster/v1/provider.json create mode 100644 docs/json_schemas/tempo_cluster/v1/requirer.json create mode 100644 interfaces/tempo_cluster/v1/README.md create mode 100644 interfaces/tempo_cluster/v1/interface.yaml create mode 100644 interfaces/tempo_cluster/v1/interface_tests/test_provider.py create mode 100644 interfaces/tempo_cluster/v1/interface_tests/test_requirer.py create mode 100644 interfaces/tempo_cluster/v1/schema.py diff --git a/docs/json_schemas/tempo_cluster/v1/provider.json b/docs/json_schemas/tempo_cluster/v1/provider.json new file mode 100644 index 00000000..212ae4d7 --- /dev/null +++ b/docs/json_schemas/tempo_cluster/v1/provider.json @@ -0,0 +1,184 @@ +{ + "$defs": { + "BaseModel": { + "properties": {}, + "title": "BaseModel", + "type": "object" + }, + "TempoClusterProviderAppData": { + "description": "TempoClusterProviderAppData.", + "properties": { + "worker_config": { + "contentMediaType": "application/json", + "contentSchema": { + "type": "string" + }, + "description": "The tempo configuration that the requirer should run with.Yaml-encoded. Must conform to the schema that the presently deployed workload version supports; for example see: https://grafana.com/docs/tempo/latest/configuration/#configure-tempo.", + "title": "Worker Config", + "type": "string" + }, + "loki_endpoints": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "List of loki-push-api endpoints to which the worker node can push any logs it generates.", + "title": "Loki Endpoints" + }, + "ca_cert": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "type": "string" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "CA certificate for tls encryption.", + "title": "Ca Cert" + }, + "server_cert": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "type": "string" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Server certificate for tls encryption.", + "title": "Server Cert" + }, + "privkey_secret_id": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "type": "string" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "ID of a Juju secret that holds the private key used by the coordinator for TLS encryption.", + "title": "Privkey Secret Id" + }, + "remote_write_endpoints": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "items": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "array" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Endpoints to which the workload (and the worker charm) can push metrics to.", + "title": "Remote Write Endpoints" + }, + "charm_tracing_receivers": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Endpoints to which the worker node can push its charm traces to.It is a mapping from protocol names such as `zipkin`, `otlp_grpc`, `otlp_http`.", + "title": "Charm Tracing Receivers" + }, + "workload_tracing_receivers": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Endpoints to which the worker node can push its workload traces to.It is a mapping from protocol names such as `zipkin`, `otlp_grpc`, `otlp_http`.", + "title": "Workload Tracing Receivers" + } + }, + "required": [ + "worker_config" + ], + "title": "TempoClusterProviderAppData", + "type": "object" + } + }, + "description": "The schema for the provider side of this interface.", + "properties": { + "unit": { + "anyOf": [ + { + "$ref": "#/$defs/BaseModel" + }, + { + "type": "null" + } + ], + "default": null + }, + "app": { + "$ref": "#/$defs/TempoClusterProviderAppData" + } + }, + "required": [ + "app" + ], + "title": "ProviderSchema", + "type": "object" +} \ No newline at end of file diff --git a/docs/json_schemas/tempo_cluster/v1/requirer.json b/docs/json_schemas/tempo_cluster/v1/requirer.json new file mode 100644 index 00000000..857fed5d --- /dev/null +++ b/docs/json_schemas/tempo_cluster/v1/requirer.json @@ -0,0 +1,116 @@ +{ + "$defs": { + "TempoClusterRequirerAppData": { + "description": "TempoClusterRequirerAppData.", + "properties": { + "role": { + "contentMediaType": "application/json", + "contentSchema": { + "$ref": "#/$defs/TempoRole" + }, + "title": "Role", + "type": "string" + } + }, + "required": [ + "role" + ], + "title": "TempoClusterRequirerAppData", + "type": "object" + }, + "TempoClusterRequirerUnitData": { + "description": "TempoClusterRequirerUnitData.", + "properties": { + "juju_topology": { + "contentMediaType": "application/json", + "contentSchema": { + "$ref": "#/$defs/_Topology" + }, + "title": "Juju Topology", + "type": "string" + }, + "address": { + "contentMediaType": "application/json", + "contentSchema": { + "type": "string" + }, + "title": "Address", + "type": "string" + } + }, + "required": [ + "juju_topology", + "address" + ], + "title": "TempoClusterRequirerUnitData", + "type": "object" + }, + "TempoRole": { + "description": "Tempo component role names.\n\nReferences:\n arch:\n -> https://grafana.com/docs/tempo/latest/operations/architecture/\n config:\n -> https://grafana.com/docs/tempo/latest/configuration/#server", + "enum": [ + "all", + "querier", + "query-frontend", + "ingester", + "distributor", + "compactor", + "metrics-generator" + ], + "title": "TempoRole", + "type": "string" + }, + "_Topology": { + "description": "JujuTopology as defined by cos-lib.", + "properties": { + "application": { + "title": "Application", + "type": "string" + }, + "charm_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Charm Name" + }, + "unit": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Unit" + } + }, + "required": [ + "application", + "charm_name", + "unit" + ], + "title": "_Topology", + "type": "object" + } + }, + "description": "The schema for the requirer side of this interface.", + "properties": { + "unit": { + "$ref": "#/$defs/TempoClusterRequirerUnitData" + }, + "app": { + "$ref": "#/$defs/TempoClusterRequirerAppData" + } + }, + "required": [ + "unit", + "app" + ], + "title": "RequirerSchema", + "type": "object" +} \ No newline at end of file diff --git a/interfaces/tempo_cluster/v1/README.md b/interfaces/tempo_cluster/v1/README.md new file mode 100644 index 00000000..a3c52953 --- /dev/null +++ b/interfaces/tempo_cluster/v1/README.md @@ -0,0 +1,54 @@ +# `tempo_cluster` + +## Usage + +`tempo_cluster` is an interface meant to exchange cluster configuration in distributed Tempo deployments. +Multiple [Tempo worker](https://github.com/canonical/tempo-worker-k8s-operator) applications can relate to a [Tempo coordinator](https://github.com/canonical/tempo-coordinator-k8s-operator) application over the `tempo_cluster` interface, and send their role and topology, in order to join the cluster. +The coordinator will use the same relation to convey to the workers back the configuration that they must run with. + +## Direction + +This interface implements a provider/requirer pattern. The coordinator charm is the provider of the relation, the worker charm is the requirer. Information flows back and forth: first the requirer shares some data necessary for the coordinator to know the role of the worker, then the provider replies back with the configuration it should run with. + +```mermaid +flowchart TD + Requirer -- Role, JujuTopology --> Provider + Provider -- Config --> Requirer +``` + +## Behavior + +### Provider +The provider is expected to... +- update the gossip rings in all configurations with the addresses of all worker units that are joining the cluster (regardless of their role). +- share the exact same configuration to all nodes, regardless of the role they declare, via application databag. + +### Requirer +The requirer application is expected to... +- publish its role as soon as possible via application databag. +Each requirer unit is expected to... +- publish its address and JujuTopology as soon as possible, via unit databag. + +## Relation Data + +[\[Pydantic Schema\]](./schema.py) + +#### Example +```yaml +provider: + app: + worker_config: + # + unit: {} + +requirer: + app: + role: receiver + unit: + juju_topology: + model: "mymodel", + model_uuid: "1231234120941234", + application: "tempo-receiver", + charm_name: "tempo-worker-k8s", + unit: "tempo-receiver/2", +``` diff --git a/interfaces/tempo_cluster/v1/interface.yaml b/interfaces/tempo_cluster/v1/interface.yaml new file mode 100644 index 00000000..71670dbd --- /dev/null +++ b/interfaces/tempo_cluster/v1/interface.yaml @@ -0,0 +1,18 @@ +name: tempo_cluster +internal: true +version: 1 +status: published + +providers: + - name: tempo-coordinator-k8s + url: https://github.com/canonical/tempo-coordinator-k8s-operator + test_setup: + location: tests/interface/conftest.py + identifier: cluster_tester + +requirers: + - name: tempo-worker-k8s + url: https://github.com/canonical/tempo-worker-k8s-operator + +maintainer: observability + diff --git a/interfaces/tempo_cluster/v1/interface_tests/test_provider.py b/interfaces/tempo_cluster/v1/interface_tests/test_provider.py new file mode 100644 index 00000000..752e68bf --- /dev/null +++ b/interfaces/tempo_cluster/v1/interface_tests/test_provider.py @@ -0,0 +1,95 @@ +# Copyright 2024 Canonical +# See LICENSE file for licensing details. +import json + +from interface_tester.interface_test import Tester +from scenario import Relation, State + + +def test_validation_fails_with_missing_role(): + tester = Tester( + state_in=State( + relations=[ + Relation( + endpoint="tempo_cluster", + interface="tempo_cluster", + remote_app_name="worker", + remote_app_data={}, + remote_units_data={ + 0: { + "juju_topology": json.dumps( + { + "application": "worker", + "unit": "worker/0", + "charm_name": "worker", + } + ), + "address": json.dumps("192.0.2.1"), + } + }, + ) + ] + ) + ) + tester.run("tempo-cluster-relation-created") + tester.assert_relation_data_empty() + + +def test_validation_succeeds_on_joining_with_role(): + tester = Tester( + state_in=State( + relations=[ + Relation( + endpoint="tempo_cluster", + interface="tempo_cluster", + remote_app_name="worker", + remote_app_data={ + "role": json.dumps("all"), + }, + remote_units_data={ + 0: { + "juju_topology": json.dumps( + { + "application": "worker", + "unit": "worker/0", + "charm_name": "worker", + } + ), + "address": json.dumps("192.0.2.1"), + } + }, + ), + ] + ) + ) + tester.run("tempo-cluster-relation-joined") + tester.assert_schema_valid() + + +def test_validation_fails_on_joining_with_invalid_role(): + tester = Tester( + state_in=State( + relations=[ + Relation( + endpoint="tempo_cluster", + interface="tempo_cluster", + remote_app_name="worker", + remote_app_data={"role": json.dumps("imposter")}, + remote_units_data={ + 0: { + "juju_topology": json.dumps( + { + "application": "worker", + "unit": "worker/0", + "charm_name": "worker", + } + ), + "address": json.dumps("192.0.2.1"), + } + }, + ) + ] + ) + ) + tester.run("tempo-cluster-relation-joined") + tester.assert_relation_data_empty() diff --git a/interfaces/tempo_cluster/v1/interface_tests/test_requirer.py b/interfaces/tempo_cluster/v1/interface_tests/test_requirer.py new file mode 100644 index 00000000..dbceb9c5 --- /dev/null +++ b/interfaces/tempo_cluster/v1/interface_tests/test_requirer.py @@ -0,0 +1,31 @@ +# Copyright 2024 Canonical +# See LICENSE file for licensing details. + +import json + +from interface_tester.interface_test import Tester +from scenario import Relation, State + + +def test_data_on_created(): + tester = Tester( + state_in=State( + relations=[ + Relation( + endpoint="tempo_cluster", + interface="tempo_cluster", + remote_app_name="coordinator", + remote_app_data={"worker_config": json.dumps("foo: bar")}, + charm_tracing_receivers={ + "otlp_http": "http://192.0.2.1:4318", + }, + workload_tracing_receivers={ + "otlp_http": "http://192.0.2.2:4318", + "otlp_grpc": "192.0.2.2:4317", + }, + ) + ] + ) + ) + tester.run("tempo-cluster-relation-created") + tester.assert_schema_valid() diff --git a/interfaces/tempo_cluster/v1/schema.py b/interfaces/tempo_cluster/v1/schema.py new file mode 100644 index 00000000..c823f4d3 --- /dev/null +++ b/interfaces/tempo_cluster/v1/schema.py @@ -0,0 +1,93 @@ +"""This file defines the schemas for the provider and requirer sides of this relation interface. + +It must expose two interfaces.schema_base.DataBagSchema subclasses called: +- ProviderSchema +- RequirerSchema +""" +from enum import Enum +from typing import Optional, Dict, List + +from interface_tester.schema_base import DataBagSchema +from pydantic import BaseModel, Field, Json + + +class TempoClusterProviderAppData(BaseModel): + """TempoClusterProviderAppData.""" + worker_config: Json[str] = Field( + description="The tempo configuration that the requirer should run with." + "Yaml-encoded. Must conform to the schema that the presently deployed " + "workload version supports; for example see: " + "https://grafana.com/docs/tempo/latest/configuration/#configure-tempo." + ) + loki_endpoints: Optional[Json[Dict[str, str]]] = Field( + default=None, + description="List of loki-push-api endpoints to which the worker node can push any logs it generates.") + ca_cert: Optional[Json[str]] = Field(default=None, description="CA certificate for tls encryption.") + server_cert: Optional[Json[str]] = Field(default=None, description="Server certificate for tls encryption.") + privkey_secret_id: Optional[Json[str]] = Field( + default=None, + description="ID of a Juju secret that holds the private key used by the coordinator for TLS encryption." + ) + remote_write_endpoints: Optional[Json[List[Dict[str, str]]]] = Field( + default=None, + description="Endpoints to which the workload (and the worker charm) can push metrics to." + ) + charm_tracing_receivers: Optional[Json[Dict[str, str]]] = Field( + default=None, + description="Endpoints to which the worker node can push its charm traces to." + "It is a mapping from protocol names such as `zipkin`, `otlp_grpc`, `otlp_http`." + ) + workload_tracing_receivers: Optional[Json[Dict[str, str]]] = Field( + default=None, + description="Endpoints to which the worker node can push its workload traces to." + "It is a mapping from protocol names such as `zipkin`, `otlp_grpc`, `otlp_http`." + ) + + +class _Topology(BaseModel): + """JujuTopology as defined by cos-lib.""" + application: str + charm_name: Optional[str] + unit: Optional[str] + + +class TempoClusterRequirerUnitData(BaseModel): + """TempoClusterRequirerUnitData.""" + + juju_topology: Json[_Topology] + address: Json[str] + + +class TempoRole(str, Enum): + """Tempo component role names. + + References: + arch: + -> https://grafana.com/docs/tempo/latest/operations/architecture/ + config: + -> https://grafana.com/docs/tempo/latest/configuration/#server + """ + ALL = "all" # default, meta-role. gets remapped to scalable-single-binary by the worker. + QUERIER = "querier" + QUERY_FRONTEND = "query-frontend" + INGESTER = "ingester" + DISTRIBUTOR = "distributor" + COMPACTOR = "compactor" + METRICS_GENERATOR = "metrics-generator" + + +class TempoClusterRequirerAppData(BaseModel): + """TempoClusterRequirerAppData.""" + + role: Json[TempoRole] + + +class ProviderSchema(DataBagSchema): + """The schema for the provider side of this interface.""" + app: TempoClusterProviderAppData + + +class RequirerSchema(DataBagSchema): + """The schema for the requirer side of this interface.""" + app: TempoClusterRequirerAppData + unit: TempoClusterRequirerUnitData diff --git a/utils/interface-validator.py b/utils/interface-validator.py index ac260bc8..b1485756 100644 --- a/utils/interface-validator.py +++ b/utils/interface-validator.py @@ -67,7 +67,7 @@ def _validate_against_path(self, file, model): if model.name != result.group(1): raise MatchError(f"name '{model.name}' does not match folder structure '{result.group(1)}'") if model.version != int(result.group(2)): - raise MatchError("version ({result.group(2)}) does not match folder structure") + raise MatchError(f"version ({result.group(2)}) does not match folder structure") """Runs the validation against all interface definitions.""" def run(self):