SD-Core 5G UPF K8s
- Canonical Telco
Channel | Revision | Published | Runs on |
---|---|---|---|
latest/beta | 13 | 03 Jan 2024 | |
latest/edge | 21 | 21 Jan 2024 | |
1.4/beta | 280 | 29 May 2024 | |
1.4/edge | 659 | 29 Oct 2024 | |
1.5/edge | 669 | 17 Nov 2024 | |
1.3/beta | 13 | 22 Jan 2024 | |
1.3/edge | 105 | 05 Apr 2024 |
juju deploy sdcore-upf-k8s --channel 1.4/beta
Deploy Kubernetes operators easily with Juju, the Universal Operator Lifecycle Manager. Need a Kubernetes cluster? Install MicroK8s to create a full CNCF-certified Kubernetes system in under 60 seconds.
Platform:
charms.sdcore_upf_k8s.v0.fiveg_n3
-
- Last updated 07 Aug 2024
- Revision Library version 0.3
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.
"""Library for the `fiveg_n3` relation.
This library offers a way of providing and consuming an IP address of the SDCORE's UPF.
In a typical 5G network, UPF's IP address is consumed by the gNodeBs, in order to establish
communication over the N3 interface.
To get started using the library, you need to fetch the library using `charmcraft`.
```shell
cd some-charm
charmcraft fetch-lib charms.sdcore_upf_k8s.v0.fiveg_n3
```
Add the following libraries to the charm's `requirements.txt` file:
- pydantic
- pytest-interface-tester
Charms providing the `fiveg_n3` relation should use `N3Provides`.
Typical usage of this class would look something like:
```python
...
from charms.sdcore_upf_k8s.v0.fiveg_n3 import N3Provides
...
class SomeProviderCharm(CharmBase):
def __init__(self, *args):
...
self.fiveg_n3 = N3Provides(charm=self, relation_name="fiveg_n3")
...
self.framework.observe(self.fiveg_n3.on.fiveg_n3_request, self._on_fiveg_n3_request)
def _on_fiveg_n3_request(self, event):
...
self.fiveg_n3.publish_upf_information(
relation_id=event.relation_id,
upf_ip_address=ip_address,
)
```
And a corresponding section in charm's `metadata.yaml`:
```
provides:
fiveg_n3: # Relation name
interface: fiveg_n3 # Relation interface
```
Charms that require the `fiveg_n3` relation should use `N3Requires`.
Typical usage of this class would look something like:
```python
...
from charms.sdcore_upf_k8s.v0.fiveg_n3 import N3Requires
...
class SomeRequirerCharm(CharmBase):
def __init__(self, *args):
...
self.fiveg_n3 = N3Requires(charm=self, relation_name="fiveg_n3")
...
self.framework.observe(self.upf.on.fiveg_n3_available, self._on_fiveg_n3_available)
def _on_fiveg_n3_available(self, event):
upf_ip_address = event.upf_ip_address
# Do something with the UPF's IP address
```
And a corresponding section in charm's `metadata.yaml`:
```
requires:
fiveg_n3: # Relation name
interface: fiveg_n3 # Relation interface
```
"""
import logging
from interface_tester.schema_base import DataBagSchema
from ops.charm import CharmBase, CharmEvents, RelationChangedEvent, RelationJoinedEvent
from ops.framework import EventBase, EventSource, Object
from pydantic import BaseModel, Field, IPvAnyAddress, ValidationError
# The unique Charmhub library identifier, never change it
LIBID = "93fa81e7726c4d14ba2b4834866bf30e"
# Increment this major API version when introducing breaking changes
LIBAPI = 0
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 3
PYDEPS = ["pydantic", "pytest-interface-tester"]
logger = logging.getLogger(__name__)
"""Schemas definition for the provider and requirer sides of the `fiveg_n3` interface.
It exposes two interfaces.schema_base.DataBagSchema subclasses called:
- ProviderSchema
- RequirerSchema
Examples:
ProviderSchema:
unit: <empty>
app: {
"upf_ip_address": "1.2.3.4"
}
RequirerSchema:
unit: <empty>
app: <empty>
"""
class ProviderAppData(BaseModel):
"""Provider app data for fiveg_n3."""
upf_ip_address: IPvAnyAddress = Field(description="UPF IP address", examples=["1.2.3.4"])
class ProviderSchema(DataBagSchema):
"""Provider schema for fiveg_n3."""
app_data: ProviderAppData
def data_matches_provider_schema(data: dict) -> bool:
"""Return whether data matches provider schema.
Args:
data (dict): Data to be validated.
Returns:
bool: True if data matches provider schema, False otherwise.
"""
try:
ProviderSchema(app_data=ProviderAppData(**data))
return True
except ValidationError as e:
logger.error("Invalid data: %s", e)
return False
class FiveGN3RequestEvent(EventBase):
"""Dataclass for the `fiveg_n3` request event."""
def __init__(self, handle, relation_id: int):
"""Set relation id."""
super().__init__(handle)
self.relation_id = relation_id
def snapshot(self) -> dict:
"""Return event data."""
return {
"relation_id": self.relation_id,
}
def restore(self, snapshot):
"""Restore event data."""
self.relation_id = snapshot["relation_id"]
class N3ProviderCharmEvents(CharmEvents):
"""Custom events for the N3Provider."""
fiveg_n3_request = EventSource(FiveGN3RequestEvent)
class N3Provides(Object):
"""Class to be instantiated by provider of the `fiveg_n3`."""
on = N3ProviderCharmEvents() # type: ignore
def __init__(self, charm: CharmBase, relation_name: str):
"""Observe relation joined event.
Args:
charm: Juju charm
relation_name (str): Relation name
"""
self.relation_name = relation_name
self.charm = charm
super().__init__(charm, relation_name)
self.framework.observe(charm.on[relation_name].relation_joined, self._on_relation_joined)
def publish_upf_information(self, relation_id: int, upf_ip_address: str) -> None:
"""Set UPF's IP address in the relation data.
Args:
relation_id (str): Relation ID
upf_ip_address (str): UPF's IP address
"""
if not data_matches_provider_schema(data={"upf_ip_address": upf_ip_address}):
raise ValueError(f"Invalid UPF IP address: {upf_ip_address}")
relation = self.model.get_relation(
relation_name=self.relation_name, relation_id=relation_id
)
if not relation:
raise RuntimeError(f"Relation {self.relation_name} not created yet.")
relation.data[self.charm.app]["upf_ip_address"] = upf_ip_address
def _on_relation_joined(self, event: RelationJoinedEvent) -> None:
"""Triggered whenever a requirer charm joins the relation.
Args:
event (RelationJoinedEvent): Juju event
"""
self.on.fiveg_n3_request.emit(relation_id=event.relation.id)
class N3AvailableEvent(EventBase):
"""Dataclass for the `fiveg_n3` available event."""
def __init__(self, handle, upf_ip_address: str):
"""Set certificate."""
super().__init__(handle)
self.upf_ip_address = upf_ip_address
def snapshot(self) -> dict:
"""Return event data."""
return {"upf_ip_address": self.upf_ip_address}
def restore(self, snapshot):
"""Restore event data."""
self.upf_ip_address = snapshot["upf_ip_address"]
class N3RequirerCharmEvents(CharmEvents):
"""Custom events for the N3Requirer."""
fiveg_n3_available = EventSource(N3AvailableEvent)
class N3Requires(Object):
"""Class to be instantiated by requirer of the `fiveg_n3`."""
on = N3RequirerCharmEvents() # type: ignore
def __init__(self, charm: CharmBase, relation_name: str):
"""Observe relation joined and relation changed events.
Args:
charm: Juju charm
relation_name (str): Relation name
"""
self.relation_name = relation_name
self.charm = charm
super().__init__(charm, relation_name)
self.framework.observe(charm.on[relation_name].relation_joined, self._on_relation_changed)
self.framework.observe(charm.on[relation_name].relation_changed, self._on_relation_changed)
def _on_relation_changed(self, event: RelationChangedEvent) -> None:
"""Triggered every time there's a change in relation data.
Args:
event (RelationChangedEvent): Juju event
"""
relation_data = event.relation.data
upf_ip_address = relation_data[event.app].get("upf_ip_address")
if upf_ip_address:
self.on.fiveg_n3_available.emit(upf_ip_address=upf_ip_address)