Skip to content

Commit

Permalink
grafana_datasource interface specification (#204)
Browse files Browse the repository at this point in the history
* added basic schema and interface spec

* initial draft

* base model

* flip requirer and provider

* flip requirer and provider

* newer scenario syntax

* app name fix

* provider tests

* source host field type fix

* extra fields optional

* json schemas

* json schemas

* schema fix for interface.yaml

* default

* stripped branch names
  • Loading branch information
PietroPasotti authored Dec 2, 2024
1 parent 2ccb05c commit 2556a56
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README_INTERFACE_TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ providers:
- name: traefik-k8s
url: https://github.com/canonical/traefik-k8s-operator
test_setup:
- location: foo/bar/baz.py # location of the identifier
location: foo/bar/baz.py # location of the identifier
identifier: qux # name of a pytest fixture yielding a configured InterfaceTester
requirers: []
Expand Down
133 changes: 133 additions & 0 deletions docs/json_schemas/grafana_datasource/v0/provider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"$defs": {
"GrafanaSourceData": {
"properties": {
"model": {
"description": "Name of the Juju model where the source is deployed.",
"examples": [
"cos"
],
"title": "Model",
"type": "string"
},
"model_uuid": {
"description": "UUID of the Juju model where the source is deployed.",
"examples": [
"0000-0000-0000-0000"
],
"title": "Model Uuid",
"type": "string"
},
"application": {
"description": "Name of the Juju model where the source is deployed.",
"examples": [
"tempo",
"loki",
"prometheus"
],
"title": "Application",
"type": "string"
},
"type": {
"description": "Type of the datasource.",
"examples": [
"tempo",
"loki",
"prometheus"
],
"title": "Type",
"type": "string"
},
"extra_fields": {
"anyOf": [
{
"contentMediaType": "application/json",
"contentSchema": {},
"type": "string"
},
{
"type": "null"
}
],
"description": "Any datasource-type-specific additional configuration.",
"title": "Extra Fields"
},
"secure_extra_fields": {
"anyOf": [
{
"contentMediaType": "application/json",
"contentSchema": {},
"type": "string"
},
{
"type": "null"
}
],
"description": "Any secure datasource-type-specific additional configuration.",
"title": "Secure Extra Fields"
}
},
"required": [
"model",
"model_uuid",
"application",
"type",
"extra_fields",
"secure_extra_fields"
],
"title": "GrafanaSourceData",
"type": "object"
},
"GrafanaSourceProviderAppData": {
"description": "Application databag model for the requirer side of this interface.",
"properties": {
"grafana_source_data": {
"contentMediaType": "application/json",
"contentSchema": {
"$ref": "#/$defs/GrafanaSourceData"
},
"title": "Grafana Source Data",
"type": "string"
}
},
"required": [
"grafana_source_data"
],
"title": "GrafanaSourceProviderAppData",
"type": "object"
},
"GrafanaSourceProviderUnitData": {
"description": "Application databag model for the requirer side of this interface.",
"properties": {
"grafana_source_host": {
"description": "Hostname of a source server.",
"examples": [
"localhost:80"
],
"title": "Grafana Source Host",
"type": "string"
}
},
"required": [
"grafana_source_host"
],
"title": "GrafanaSourceProviderUnitData",
"type": "object"
}
},
"description": "The schemas for the requirer side of this interface.",
"properties": {
"unit": {
"$ref": "#/$defs/GrafanaSourceProviderUnitData"
},
"app": {
"$ref": "#/$defs/GrafanaSourceProviderAppData"
}
},
"required": [
"unit",
"app"
],
"title": "ProviderSchema",
"type": "object"
}
52 changes: 52 additions & 0 deletions docs/json_schemas/grafana_datasource/v0/requirer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"$defs": {
"BaseModel": {
"properties": {},
"title": "BaseModel",
"type": "object"
},
"GrafanaSourceRequirerAppData": {
"description": "Application databag model for the requirer side of this interface.",
"properties": {
"datasource_uids": {
"contentMediaType": "application/json",
"contentSchema": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"title": "Datasource Uids",
"type": "string"
}
},
"required": [
"datasource_uids"
],
"title": "GrafanaSourceRequirerAppData",
"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/GrafanaSourceRequirerAppData"
}
},
"required": [
"app"
],
"title": "RequirerSchema",
"type": "object"
}
85 changes: 85 additions & 0 deletions interfaces/grafana_datasource/v0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# `grafana_datasource`

## Usage

This relation interface describes the expected behavior of any charm claiming to be able to provide a grafana datasource.

In most cases, this will be accomplished using the [grafana_source library](https://github.com/canonical/grafana-k8s-operator/blob/main/lib/charms/grafana_k8s/v0/grafana_source.py), although 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` interface implements a provider/requirer pattern.
The provider is a charm that implements a grafana datasource-compatible endpoint, and the requirer is a charm that is able to use such an endpoint to query the data.

The requirer is furthermore expected to share back to the provider a unique identifier assigned to the source. This can be used by the provider to share with other charms for data correlation and cross-referencing purposes.

```mermaid
flowchart TD
Provider -- DatasourceEndpoint --> Requirer
Requirer -- DatasourceUID --> Provider
```

## Behavior

The requirer and the provider need to adhere to a certain set of criteria to be considered compatible with the interface.

### Provider

- Is expected to expose a server implementing [the grafana source HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/data_source/).
- Is expected to communicate said endpoint URL over unit data, as each unit will expose its own server.

### Requirer

- Is expected to share back via application data a mapping from provider unit names to unique datasource IDs.

## Relation Data

[\[Pydantic model\]](./schema.py)


### Requirer


Additionally to a subset of the the (mandatory) [juju topology fields](https://discourse.charmhub.io/t/juju-topology-labels/8874),
the requirer is expected to share the following fields:
- `type`: the grafana datasource type. For the possible values see [the upstream docs](https://grafana.com/docs/grafana/latest/datasources/#built-in-core-data-sources). Required.
- `extra_fields`: used to configure certain datasources. Maps to the `jsonData` field. Optional.
- `secure_extra_fields`: used to configure certain datasources. Maps to the `secureJsonData` field. Optional.

The whole configuration is expected to be json-encoded and nested under a `grafana_source_data`
toplevel field.

#### Example

```yaml
application_data: {
grafana_source_data:
{
model: cos,
model_uuid: 0000-0000-0000-0000,
application: tempo,
type: tempo,
extra_fields: {
some: value
},
secure_extra_fields: {
some: password
},

}
}
```

### Provider

The provider is expected to share back a unique identifier for each unit of the requirer, as a mapping.
This will be encoded as a json dict and nested under the `datasource_uids` field in the application databag.

#### Example
```yaml
application-data: {
datasource_uids: {
"tempo/0": 0000-0000-0000-0001,
"tempo/1": 0000-0000-0000-0002,
}
}
```
24 changes: 24 additions & 0 deletions interfaces/grafana_datasource/v0/interface.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: grafana_datasource

internal: true

version: 0

status: published

providers:
- name: tempo-coordinator-k8s
url: https://github.com/canonical/tempo-coordinator-k8s-operator
test_setup:
location: tests/interface/conftest.py
identifier: grafana_datasource_tester

requirers:
- name: grafana-k8s
url: https://github.com/canonical/grafana-k8s-operator
test_setup:
location: tests/interface/conftest.py
identifier: grafana_source_tester


maintainer: observability
24 changes: 24 additions & 0 deletions interfaces/grafana_datasource/v0/interface_tests/test_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from interface_tester import Tester
from scenario import State, Relation


def test_share_datasource_on_remote_joined():
# GIVEN the remote side hasn't sent anything
tester = Tester(state_in=State(
relations=[
Relation(
endpoint='grafana-source',
interface='grafana_datasource',
remote_app_name='foo',
remote_app_data={},
remote_units_data={
0: {}
}
)
]
))
# WHEN the provider processes a relation-joined event
tester.run('grafana-source-relation-joined')
# THEN the provider publishes valid datasource data
tester.assert_schema_valid()

Loading

0 comments on commit 2556a56

Please sign in to comment.