-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2556a56
commit 43d6bb6
Showing
5 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# `grafana_datasource_exchange` | ||
|
||
The `grafana_datasource_exchange` interface allows charms that generate telemetry and have a reference to the datasources where said telemetry is queriable, | ||
to share those references to other charms for correlation and cross-referencing purposes. | ||
|
||
## Usage | ||
|
||
This document describes the expected behavior of any charm exposing a `grafana_datasource_exchange` endpoint. | ||
|
||
The reference implementation for this interface is implemented in the [`cosl`](https://github.com/canonical/cos-lib) library. | ||
Charm developers are free to provide alternative libraries as long as they fulfill the behavioral and schematic requirements described in this document. | ||
|
||
## Direction | ||
The `grafana_datasource_exchange` interface implements a symmetrical provider/requirer pattern. | ||
Symmetrical, in the sense that the role doesn't matter and the data to be exchanged is the same for the requirer and the provider. | ||
|
||
```mermaid | ||
flowchart TD | ||
Provider -- Datasources --> Requirer | ||
Requirer -- Datasources --> Provider | ||
``` | ||
|
||
## Behavior | ||
|
||
The requirer and the provider need to adhere to a certain set of criteria to be considered compatible with the interface. | ||
|
||
### Provider & Requirer | ||
|
||
- Is expected to expose a server implementing [the grafana source HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/data_source/). In other words, it's expected to expose one or more valid grafana datasources. | ||
- Is expected to register each datasource endpoint (one per unit) with a central grafana application and obtain a Datasource UID for each one of them. | ||
- Is expected to share via application data, as a json payload, the following information: | ||
- for each datasource (which technically will likely mean, for each unit of the application): | ||
- the datasource UID | ||
- the datasource type | ||
|
||
To avoid complexity, we stipulate that the data will be provided in bulk: only 'fully specified' datasources will be shared, i.e. this is not a valid databag state: | ||
|
||
```yaml | ||
application_data: { | ||
datasources: | ||
[ | ||
{ | ||
type: tempo, | ||
}, | ||
] | ||
} | ||
``` | ||
|
||
In other words, if the application still has to hear back from Grafana what the UID of the datasource is, it should **not** add it to its `grafana_datasource_exchange` endpoints. | ||
Only when it knows what UID is assigned to a datasource, then will it add the datasource to this relation. | ||
|
||
|
||
## Relation Data | ||
|
||
[\[Pydantic model\]](./schema.py) | ||
|
||
#### Example | ||
|
||
```yaml | ||
application_data: { | ||
datasources: | ||
[ | ||
{ | ||
type: tempo, | ||
uid: 0000-0000-0000-0001 | ||
}, | ||
{ | ||
type: prometheus, | ||
uid: 0000-0000-0000-0002 | ||
}, | ||
] | ||
} | ||
``` | ||
|
||
#### Notes | ||
|
||
- Since this interface is symmetrical, each application will likely have to implement both a requirer and a provider endpoint for it, to avoid having strange constraints on the integration topology. | ||
- The data that is being exchanged comes in part from the application themselves (the datasource type), but in part from another integration (Grafana assigns the UIDs and communicates them back via the `grafana_datasource` interface). Since we cannot assume which integration is created first and what the event sequence will look like, this interface cannot commit to a specific event by which the data should be written. Instead, the only guarantee an implementer should give, is that _eventually_ the data will be provided. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: grafana_datasource_exchange | ||
|
||
internal: true | ||
|
||
version: 0 | ||
|
||
status: draft | ||
|
||
providers: | ||
- name: tempo-coordinator-k8s | ||
url: https://github.com/canonical/tempo-coordinator-k8s-operator | ||
branch: grafana-datasource-exchange | ||
test_setup: | ||
location: tests/interface/conftest.py | ||
identifier: grafana_datasource_exchange_tester | ||
|
||
requirers: | ||
- name: grafana-k8s | ||
url: https://github.com/canonical/tempo-coordinator-k8s-operator | ||
branch: grafana-datasource-exchange | ||
test_setup: | ||
location: tests/interface/conftest.py | ||
identifier: grafana_datasource_exchange_tester | ||
|
||
|
||
maintainer: observability |
176 changes: 176 additions & 0 deletions
176
interfaces/grafana_datasource_exchange/v0/interface_tests/test_provider.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import json | ||
|
||
from interface_tester import Tester | ||
from scenario import State, Relation | ||
|
||
|
||
def test_not_share_partial_datasource(): | ||
# GIVEN the grafana_datasource interface has not shared any source UID yet | ||
source = Relation( | ||
endpoint='grafana-source', | ||
interface='grafana_datasource', | ||
remote_app_name='foo', | ||
local_app_data={"grafana_source_data": json.dumps( | ||
{"model": "somemodel", "model_uuid": "0000-0000-0000-0042", "application": "myapp", | ||
"type": "prometheus", })}, | ||
local_unit_data={"grafana_source_host": "somehost:80"}, | ||
# no remote app data! | ||
) | ||
source_exchange = Relation( | ||
endpoint='grafana-source-exchange', | ||
interface='grafana_datasource_exchange', | ||
remote_app_name='bar' | ||
) | ||
tester = Tester(state_in=State( | ||
relations=[ | ||
source, | ||
source_exchange | ||
] | ||
)) | ||
# WHEN the provider processes any relation event | ||
tester.run('grafana-source-relation-changed') | ||
# THEN the provider publishes valid datasource-exchange data | ||
tester.assert_relation_data_empty() | ||
|
||
|
||
def test_not_share_partial_datasource_remote_shared(): | ||
# GIVEN the grafana_datasource interface has not shared any source UID yet | ||
source = Relation( | ||
endpoint='grafana-source', | ||
interface='grafana_datasource', | ||
remote_app_name='foo', | ||
local_app_data={"grafana_source_data": json.dumps( | ||
{"model": "somemodel", "model_uuid": "0000-0000-0000-0042", "application": "myapp", | ||
"type": "prometheus", })}, | ||
local_unit_data={"grafana_source_host": "somehost:80"}, | ||
# no remote app data! | ||
) | ||
# AND GIVEN the remote end of the source-exchange interface has shared some datasource UIDs already | ||
source_exchange = Relation( | ||
endpoint='grafana-source-exchange', | ||
interface='grafana_datasource_exchange', | ||
remote_app_name='bar', | ||
remote_app_data={"datasources": json.dumps([ | ||
{ | ||
"type": "tempo", | ||
"uid": "0000-0000-0000-0000" | ||
} | ||
])} | ||
) | ||
tester = Tester(state_in=State( | ||
relations=[ | ||
source, | ||
source_exchange | ||
] | ||
)) | ||
# WHEN the provider processes any relation event | ||
tester.run('grafana-source-exchange-relation-changed') | ||
# THEN the provider doesn't share its side (as it doesn't have it yet) | ||
tester.assert_relation_data_empty() | ||
|
||
|
||
def test_not_share_partial_datasource_remote_shared_invalid(): | ||
# GIVEN the grafana_datasource interface has not shared any source UID yet | ||
source = Relation( | ||
endpoint='grafana-source', | ||
interface='grafana_datasource', | ||
remote_app_name='foo', | ||
local_app_data={"grafana_source_data": json.dumps( | ||
{"model": "somemodel", "model_uuid": "0000-0000-0000-0042", "application": "myapp", | ||
"type": "prometheus", })}, | ||
local_unit_data={"grafana_source_host": "somehost:80"}, | ||
# no remote app data! | ||
) | ||
# AND GIVEN the remote end of the source-exchange interface has shared some invalid data | ||
source_exchange = Relation( | ||
endpoint='grafana-source-exchange', | ||
interface='grafana_datasource_exchange', | ||
remote_app_name='bar', | ||
remote_app_data={"datasources": json.dumps([ | ||
{ | ||
"type": "tempo", | ||
"nope": "hope" | ||
} | ||
])} | ||
) | ||
tester = Tester(state_in=State( | ||
relations=[ | ||
source, | ||
source_exchange | ||
] | ||
)) | ||
# WHEN the provider processes any relation event | ||
tester.run('grafana-source-exchange-relation-changed') | ||
# THEN the provider doesn't share its side (as it doesn't have it yet) | ||
tester.assert_relation_data_empty() | ||
|
||
|
||
def test_datasource_exchange_remote_shared_invalid(): | ||
# GIVEN the grafana_datasource interface has shared one or more sourdce UID | ||
source = Relation( | ||
endpoint='grafana-source', | ||
interface='grafana_datasource', | ||
remote_app_name='foo', | ||
local_app_data={"grafana_source_data": json.dumps( | ||
{"model": "somemodel", "model_uuid": "0000-0000-0000-0042", "application": "myapp", | ||
"type": "prometheus", })}, | ||
local_unit_data={"grafana_source_host": "somehost:80"}, | ||
remote_app_data={"datasource_uids": json.dumps({ | ||
"foo/0": "myuid0", | ||
"foo/4": "myuid1" | ||
})} | ||
) | ||
# AND GIVEN the remote end of the source-exchange interface has shared some invalid data | ||
source_exchange = Relation( | ||
endpoint='grafana-source-exchange', | ||
interface='grafana_datasource_exchange', | ||
remote_app_name='bar', | ||
remote_app_data={"datasources": json.dumps([ | ||
{ | ||
"type": "tempo", | ||
"nope": "hope" | ||
} | ||
])} | ||
) | ||
tester = Tester(state_in=State( | ||
relations=[ | ||
source, | ||
source_exchange | ||
] | ||
)) | ||
# WHEN the provider processes any relation event | ||
tester.run('grafana-source-exchange-relation-changed') | ||
# THEN the provider shares valid data even if the remote side is invalid | ||
tester.assert_schema_valid() | ||
|
||
|
||
def test_datasource_exchange(): | ||
# GIVEN the grafana_datasource interface has shared one or more sourdce UID | ||
source = Relation( | ||
endpoint='grafana-source', | ||
interface='grafana_datasource', | ||
remote_app_name='foo', | ||
local_app_data={"grafana_source_data": json.dumps( | ||
{"model": "somemodel", "model_uuid": "0000-0000-0000-0042", "application": "myapp", | ||
"type": "prometheus", })}, | ||
local_unit_data={"grafana_source_host": "somehost:80"}, | ||
remote_app_data={"datasource_uids": json.dumps({ | ||
"foo/0": "myuid0", | ||
"foo/4": "myuid1" | ||
})} | ||
) | ||
source_exchange = Relation( | ||
endpoint='grafana-source-exchange', | ||
interface='grafana_datasource_exchange', | ||
remote_app_name='bar' | ||
) | ||
tester = Tester(state_in=State( | ||
relations=[ | ||
source, | ||
source_exchange | ||
] | ||
)) | ||
# WHEN the provider processes any relation event | ||
tester.run('grafana-source-relation-changed') | ||
# THEN the provider publishes valid datasource-exchange data | ||
tester.assert_schema_valid() |
2 changes: 2 additions & 0 deletions
2
interfaces/grafana_datasource_exchange/v0/interface_tests/test_requirer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# given that this interface is symmetric, and we expect each provider | ||
# to also be a requirer, we omit the requirer tests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from typing import List | ||
|
||
from interface_tester.schema_base import DataBagSchema | ||
from pydantic import Json, BaseModel, Field | ||
|
||
|
||
class GrafanaDatasource(BaseModel): | ||
type: str = Field(description="Type of the datasource.", | ||
examples=['tempo', 'loki', 'prometheus']) | ||
uid: str = Field(description="Grafana datasource UID, as assigned by Grafana.") | ||
|
||
|
||
class GrafanaSourceAppData(BaseModel): | ||
"""Application databag model for the requirer side of this interface.""" | ||
datasources: Json[List[GrafanaDatasource]] | ||
|
||
|
||
class ProviderSchema(DataBagSchema): | ||
"""The schemas for the requirer side of this interface.""" | ||
app: GrafanaSourceAppData | ||
|
||
|
||
class RequirerSchema(DataBagSchema): | ||
"""The schemas for the provider side of this interface.""" | ||
app: GrafanaSourceAppData |