Skip to content

Commit 6ba9373

Browse files
committed
Merge branch 'main' into da-176-requested-entities
2 parents b0d83bc + 6145573 commit 6ba9373

File tree

10 files changed

+293
-6
lines changed

10 files changed

+293
-6
lines changed

.github/workflows/dispatch-matrix-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- name: Set up python
4747
uses: actions/setup-python@v5
4848
with:
49-
python-version: 3.8
49+
python-version: '3.10'
5050
- name: Install dependencies
5151
run: pip install tox uv poetry
5252
- name: Run tests
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"$defs": {
3+
"BaseModel": {
4+
"properties": {},
5+
"title": "BaseModel",
6+
"type": "object"
7+
},
8+
"ProviderAppSchema": {
9+
"description": "Application databag schema for the provider side of the profiling interface.",
10+
"properties": {
11+
"otlp_grpc_endpoint_url": {
12+
"description": "Grpc ingestion endpoint for profiles using otlp_grpc.",
13+
"examples": [
14+
"some.hostname:1234",
15+
"10.64.140.43:42424"
16+
],
17+
"title": "Otlp Grpc Endpoint Url",
18+
"type": "string"
19+
},
20+
"insecure": {
21+
"default": false,
22+
"description": "Whether the ingestion endpoints should be accessed without TLS (insecure connection).",
23+
"title": "Insecure",
24+
"type": "boolean"
25+
}
26+
},
27+
"required": [
28+
"otlp_grpc_endpoint_url"
29+
],
30+
"title": "ProviderAppSchema",
31+
"type": "object"
32+
}
33+
},
34+
"description": "The schema for the provider side of this interface.",
35+
"properties": {
36+
"unit": {
37+
"anyOf": [
38+
{
39+
"$ref": "#/$defs/BaseModel"
40+
},
41+
{
42+
"type": "null"
43+
}
44+
],
45+
"default": null
46+
},
47+
"app": {
48+
"$ref": "#/$defs/ProviderAppSchema"
49+
}
50+
},
51+
"required": [
52+
"app"
53+
],
54+
"title": "ProviderSchema",
55+
"type": "object"
56+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$defs": {
3+
"BaseModel": {
4+
"properties": {},
5+
"title": "BaseModel",
6+
"type": "object"
7+
}
8+
},
9+
"description": "The schema for the requirer side of this interface.",
10+
"properties": {
11+
"unit": {
12+
"anyOf": [
13+
{
14+
"$ref": "#/$defs/BaseModel"
15+
},
16+
{
17+
"type": "null"
18+
}
19+
],
20+
"default": null
21+
},
22+
"app": {
23+
"anyOf": [
24+
{
25+
"$ref": "#/$defs/BaseModel"
26+
},
27+
{
28+
"type": "null"
29+
}
30+
],
31+
"default": null
32+
}
33+
},
34+
"title": "RequirerSchema",
35+
"type": "object"
36+
}

index.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@
214214
"version": 0,
215215
"status": "draft"
216216
},
217+
{
218+
"name": "profiling",
219+
"version": 0,
220+
"status": "draft"
221+
},
217222
{
218223
"name": "prometheus_remote_write",
219224
"version": 0,

interfaces/profiling/v0/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# `profiling`
2+
3+
## Usage
4+
5+
This interface can be used by an application able to ingest profiling data to expose the ingestion
6+
endpoint(s) to any application able to push into them.
7+
8+
The reference implementation of this interface (v0) can be found at [this address](https://github.com/canonical/pyroscope-k8s-operator/blob/main/coordinator/lib/charms/pyroscope_coordinator_k8s/v0/profiling.py).
9+
10+
## Direction
11+
12+
This interface implements a provider/requirer pattern. As is customary, the provider is the server
13+
(the profiling data ingester) and the requirer is the profiling data source.
14+
15+
The interface is unidirectional: the provider doesn't need any information from the requirer.
16+
The provider shares any and all supported ingestion endpoints.
17+
18+
```mermaid
19+
flowchart TD
20+
Provider -- ProfilingEndpoints --> Requirer
21+
```
22+
23+
## Behavior
24+
### Provider
25+
26+
- Must publish all active ingestion endpoints.
27+
- Must accept data on those endpoints
28+
- If ingress is available, the provider must advertise its ingressed hostname,
29+
otherwise fall back to using its own cluster-internal fqdn.
30+
- Must pass `insecure=True` if the ingestion endpoints are running with insecure connection.
31+
32+
## Relation Data
33+
34+
[\[Pydantic Schema\]](./schema.py)
35+
36+
#### Example
37+
A yaml/json example of a valid databag state (for the whole relation), in absence of an ingress
38+
integration for the provider:
39+
```yaml
40+
provider:
41+
app: {
42+
otlp_grpc_endpoint_url: "my.fqdn.cluster.local:1234",
43+
insecure: False,
44+
}
45+
unit: {}
46+
requirer:
47+
app: {}
48+
unit: {}
49+
```
50+
51+
If the provider is ingressed:
52+
```yaml
53+
provider:
54+
app: {
55+
# `10.0.0.1` is the ingress hostname of the provider charm
56+
otlp_grpc_endpoint_url: "10.0.0.1:1234",
57+
insecure: False,
58+
}
59+
unit: {}
60+
requirer:
61+
app: {}
62+
unit: {}
63+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: profiling
2+
3+
# `version` starts at `0` and increments with every breaking change in the interface specification.
4+
version: 0
5+
6+
# `status` describes where the interface is in its lifecycle. Valid values are described in LEGEND.md.
7+
status: draft
8+
9+
providers:
10+
- name: pyroscope-coordinator-k8s
11+
url: https://github.com/canonical/pyroscope-operators
12+
branch: feat/interface-tests-profiling
13+
test_setup:
14+
charm_root: coordinator
15+
location: tests/interface/conftest.py
16+
identifier: profiling_tester
17+
pre_run: uv pip compile pyproject.toml --quiet --output-file requirements.txt
18+
19+
requirers:
20+
- name: opentelemetry-collector-k8s
21+
url: https://github.com/canonical/opentelemetry-collector-k8s-operator
22+
test_setup:
23+
pre_run: uv pip compile pyproject.toml --quiet --output-file requirements.txt
24+
25+
maintainer: "observability"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2024 Canonical
2+
# See LICENSE file for licensing details.
3+
import json
4+
5+
from interface_tester.interface_test import Tester
6+
7+
# on created, joined, changed: the provider is expected to publish all data
8+
def test_data_on_created():
9+
tester = Tester()
10+
tester.run('profiling-relation-created')
11+
tester.assert_schema_valid()
12+
13+
14+
def test_data_on_joined():
15+
tester = Tester()
16+
tester.run('profiling-relation-joined')
17+
tester.assert_schema_valid()
18+
19+
20+
def test_data_on_changed():
21+
tester = Tester()
22+
tester.run('profiling-relation-changed')
23+
tester.assert_schema_valid()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2024 Canonical
2+
# See LICENSE file for licensing details.
3+
import json
4+
5+
from interface_tester.interface_test import Tester
6+
from scenario import State, Relation
7+
8+
# on created, joined, changed: the requirer is expected to publish no data
9+
def test_no_data_on_created():
10+
tester = Tester()
11+
tester.run('tracing-relation-created')
12+
tester.assert_relation_data_empty()
13+
14+
15+
def test_no_data_on_joined():
16+
tester = Tester()
17+
tester.run('tracing-relation-joined')
18+
tester.assert_relation_data_empty()
19+
20+
21+
def test_no_data_on_changed():
22+
tester = Tester()
23+
tester.run('tracing-relation-changed')
24+
tester.assert_relation_data_empty()
25+
26+
27+
# if the remote end has sent their side of the deal, we're happy
28+
def test_data_on_changed():
29+
tester = Tester(
30+
state_in=State(
31+
relations=[
32+
Relation(
33+
endpoint='profiling',
34+
interface='profiling',
35+
remote_app_name='remote',
36+
remote_app_data={
37+
"otlp_grpc_endpoint_url": json.dumps("my.fqdn.cluster.local:1234"),
38+
"insecure": json.dumps(False),
39+
}
40+
)
41+
]
42+
)
43+
)
44+
tester.run('tracing-relation-changed')
45+
tester.assert_schema_valid()
46+

interfaces/profiling/v0/schema.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""This file defines the schemas for the provider and requirer sides of this relation interface.
2+
3+
It must expose two interfaces.schema_base.DataBagSchema subclasses called:
4+
- ProviderSchema
5+
- RequirerSchema
6+
"""
7+
8+
from interface_tester.schema_base import DataBagSchema
9+
from pydantic import BaseModel, Field
10+
11+
12+
class ProviderAppSchema(BaseModel):
13+
"""Application databag schema for the provider side of the profiling interface."""
14+
15+
otlp_grpc_endpoint_url: str = Field(
16+
description="Grpc ingestion endpoint for profiles using otlp_grpc.",
17+
examples=["some.hostname:1234", "10.64.140.43:42424"],
18+
)
19+
insecure: bool = Field(
20+
description="Whether the ingestion endpoints should be accessed without TLS (insecure connection).",
21+
default=False,
22+
)
23+
24+
25+
class ProviderSchema(DataBagSchema):
26+
"""The schema for the provider side of this interface."""
27+
28+
app: ProviderAppSchema
29+
30+
31+
class RequirerSchema(DataBagSchema):
32+
"""The schema for the requirer side of this interface."""

run_matrix.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ def _prepare_repo(
8989
repo_root_path = root / charm_config.name
9090

9191
# multi-charm repos might have multiple charms in a single repo.
92-
if charm_root_cfg := charm_config.test_setup.get("charm_root"):
92+
if charm_config.test_setup and (
93+
charm_root_cfg := charm_config.test_setup.get("charm_root")
94+
):
9395
charm_root_path = repo_root_path / charm_root_cfg
9496
if not charm_root_path.resolve().is_relative_to(repo_root_path):
9597
raise SetupError(
@@ -168,10 +170,9 @@ def _get_fixture(charm_config: "_CharmTestConfig", charm_path: Path) -> FixtureS
168170
fixture_path = charm_path / FIXTURE_PATH
169171
fixture_id = FIXTURE_IDENTIFIER
170172
if charm_config.test_setup:
171-
if charm_config.test_setup["location"]:
172-
fixture_path = charm_path / Path(charm_config.test_setup["location"])
173-
if charm_config.test_setup["identifier"]:
174-
fixture_id = charm_config.test_setup["identifier"]
173+
if location := charm_config.test_setup.get("location"):
174+
fixture_path = charm_path / Path(location)
175+
fixture_id = charm_config.test_setup.get("identifier", fixture_id)
175176
return FixtureSpec(fixture_path, fixture_id)
176177

177178

0 commit comments

Comments
 (0)