Skip to content

Commit df19af5

Browse files
Add kubevirt_controller integration (#18186)
* initial scaffolding * report kubevirt_controller.can_connect metric * report kubevirt_controller metrics * add kube_cluster_name, kube_namespace, kube_pod_name tags to all metrics * update spec.yaml and ddev validate config and models * disable TLS verify * update README * refactor / cleanup * ddev validate * update manifest.json * fix title and description * delete dashboard file * fix changelog * add e2e tests * Update README.md * Apply suggestions from code review Co-authored-by: Jen Gilbert <jen.gilbert@datadoghq.com> * apply suggestions * fix spec.yaml and sync config files * Apply suggestions from code review Co-authored-by: Jen Gilbert <jen.gilbert@datadoghq.com> * ddev validate ci * rename Kubevirt Controller -> KubeVirt Controller * rename Kubevirt to KubeVirt * remove time.sleeps from dd_environment setup * remove kube_cluster_name * remove kube_cluster_name from tests, cleanup check.py with the suggestions from other PR * remove unecessary line * remove IMAGES_README.md * ddev validate ci --------- Co-authored-by: Jen Gilbert <jen.gilbert@datadoghq.com>
1 parent 9d6b5a0 commit df19af5

32 files changed

+12485
-0
lines changed

.codecov.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ coverage:
310310
target: 75
311311
flags:
312312
- kong
313+
KubeVirt_Controller:
314+
target: 75
315+
flags:
316+
- kubevirt_controller
313317
KubeVirt_API:
314318
target: 75
315319
flags:
@@ -1162,6 +1166,11 @@ flags:
11621166
paths:
11631167
- kubernetes_state/datadog_checks/kubernetes_state
11641168
- kubernetes_state/tests
1169+
kubevirt_controller:
1170+
carryforward: true
1171+
paths:
1172+
- kubevirt_controller/datadog_checks/kubevirt_controller
1173+
- kubevirt_controller/tests
11651174
kubevirt_api:
11661175
carryforward: true
11671176
paths:

.github/workflows/config/labeler.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ integration/kubernetes_state:
291291
- kubernetes_state/**/*
292292
integration/kubernetes_state_core:
293293
- kubernetes_state_core/**/*
294+
integration/kubevirt_controller:
295+
- kubevirt_controller/**/*
294296
integration/kubevirt_api:
295297
- kubevirt_api/**/*
296298
integration/kyototycoon:

.github/workflows/test-all.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,26 @@ jobs:
21942194
minimum-base-package: ${{ inputs.minimum-base-package }}
21952195
pytest-args: ${{ inputs.pytest-args }}
21962196
secrets: inherit
2197+
jc4c1032:
2198+
uses: ./.github/workflows/test-target.yml
2199+
with:
2200+
job-name: KubeVirt Controller
2201+
target: kubevirt_controller
2202+
platform: linux
2203+
runner: '["ubuntu-22.04"]'
2204+
repo: "${{ inputs.repo }}"
2205+
python-version: "${{ inputs.python-version }}"
2206+
standard: ${{ inputs.standard }}
2207+
latest: ${{ inputs.latest }}
2208+
agent-image: "${{ inputs.agent-image }}"
2209+
agent-image-py2: "${{ inputs.agent-image-py2 }}"
2210+
agent-image-windows: "${{ inputs.agent-image-windows }}"
2211+
agent-image-windows-py2: "${{ inputs.agent-image-windows-py2 }}"
2212+
test-py2: ${{ inputs.test-py2 }}
2213+
test-py3: ${{ inputs.test-py3 }}
2214+
minimum-base-package: ${{ inputs.minimum-base-package }}
2215+
pytest-args: ${{ inputs.pytest-args }}
2216+
secrets: inherit
21972217
j61e565f:
21982218
uses: ./.github/workflows/test-target.yml
21992219
with:

kubevirt_controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# CHANGELOG - KubeVirt Controller
2+
3+
<!-- towncrier release notes start -->
4+

kubevirt_controller/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Agent Check: KubeVirt Controller
2+
3+
## Overview
4+
5+
This check monitors [KubeVirt Controller][1] through the Datadog Agent.
6+
7+
## Setup
8+
9+
Follow the instructions below to install and configure this check for an Agent running on a host. For containerized environments, see the [Autodiscovery Integration Templates][3] for guidance on applying these instructions.
10+
11+
### Installation
12+
13+
The KubeVirt Controller check is included in the [Datadog Agent][2] package.
14+
No additional installation is needed on your server.
15+
16+
### Configuration
17+
18+
The main use case to run the `kubevirt_controller` check is as a [cluster level check][4].
19+
20+
In order to do that, you will need to update some RBAC permissions to give the `datadog-agent` service account read-only access to the`KubeVirt` resources by following the steps below:
21+
22+
1. Bind the `kubevirt.io:view` ClusterRole to the `datadog-agent` service account:
23+
24+
```yaml
25+
apiVersion: rbac.authorization.k8s.io/v1
26+
kind: ClusterRoleBinding
27+
metadata:
28+
name: datadog-agent-kubevirt
29+
roleRef:
30+
apiGroup: rbac.authorization.k8s.io
31+
kind: ClusterRole
32+
name: kubevirt.io:view
33+
subjects:
34+
- kind: ServiceAccount
35+
name: datadog-agent
36+
namespace: default
37+
```
38+
39+
2. Annotate the pods template of your `virt-controller` deployment by patching the `KubeVirt` resource as follows:
40+
41+
```yaml
42+
apiVersion: kubevirt.io/v1
43+
kind: KubeVirt
44+
metadata:
45+
name: kubevirt
46+
namespace: kubevirt
47+
spec:
48+
certificateRotateStrategy: {}
49+
configuration: {}
50+
customizeComponents:
51+
patches:
52+
- resourceType: Deployment
53+
resourceName: virt-controller
54+
patch: '{"spec": {"template":{"metadata":{"annotations":{ "ad.datadoghq.com/virt-controller.check_names": "[\"kubevirt_controller\"]", "ad.datadoghq.com/virt-controller.init_configs": "[{}]", "ad.datadoghq.com/virt-controller.instances": "[{ \"kubevirt_controller_metrics_endpoint\": \"https://%%host%%:%%port%%/metrics\",\"kubevirt_controller_healthz_endpoint\": \"https://%%host%%:%%port%%/healthz\", \"kube_namespace\":\"%%kube_namespace%%\", \"kube_pod_name\":\"%%kube_pod_name%%\", \"tls_verify\": \"false\"}]"}}}}}'
55+
type: strategic
56+
```
57+
58+
Replace `<DD_CLUSTER_NAME>` with the name you want for your cluster.
59+
60+
### Validation
61+
62+
[Run the Cluster Agent's `clusterchecks` subcommand][5] inside your Cluster Agent container and look for the `kubevirt_controller` check under the Checks section.
63+
64+
## Data Collected
65+
66+
### Metrics
67+
68+
See [metadata.csv][6] for a list of metrics provided by this integration.
69+
70+
### Events
71+
72+
The KubeVirt Controller integration does not include any events.
73+
74+
### Service Checks
75+
76+
The KubeVirt Controller integration does not include any service checks.
77+
78+
## Troubleshooting
79+
80+
Need help? Contact [Datadog support][7].
81+
82+
[1]: https://docs.datadoghq.com/integrations/kubevirt_controller
83+
[2]: https://app.datadoghq.com/account/settings/agent/latest
84+
[3]: https://docs.datadoghq.com/agent/kubernetes/integrations/
85+
[4]: https://docs.datadoghq.com/containers/cluster_agent/clusterchecks/?tab=datadogoperator
86+
[5]: https://docs.datadoghq.com/containers/troubleshooting/cluster-and-endpoint-checks/#dispatching-logic-in-the-cluster-agent
87+
[6]: https://github.com/DataDog/integrations-core/blob/master/kubevirt_controller/metadata.csv
88+
[7]: https://docs.datadoghq.com/help/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: KubeVirt Controller
2+
files:
3+
- name: kubevirt_controller.yaml
4+
options:
5+
- template: init_config
6+
options:
7+
- template: init_config/openmetrics
8+
- template: instances
9+
options:
10+
- name: kubevirt_controller_metrics_endpoint
11+
description: |
12+
URL to the KubeVirt Controller Service /metrics endpoint.
13+
value:
14+
display_default: null
15+
example: https://10.244.0.38:443/metrics
16+
type: string
17+
- name: kubevirt_controller_healthz_endpoint
18+
description: |
19+
URL to check the KubeVirt Controller /healthz endpoint.
20+
value:
21+
display_default: null
22+
example: https://10.244.0.38:443/healthz
23+
type: string
24+
- name: kube_namespace
25+
description: |
26+
The namespace where the KubeVirt Controller is running. (Provided by autodiscovery template variables)
27+
value:
28+
display_default: null
29+
example: kubevirt
30+
type: string
31+
- name: kube_pod_name
32+
description: |
33+
The name of the KubeVirt Controller pod. (Provided by autodiscovery template variables)
34+
value:
35+
display_default: null
36+
example: virt-controller-id-1234
37+
type: string
38+
- template: instances/openmetrics
39+
overrides:
40+
openmetrics_endpoint.required: false
41+
openmetrics_endpoint.hidden: true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Initial Release
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
__version__ = '0.0.1'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
from .__about__ import __version__
5+
from .check import KubeVirtControllerCheck
6+
7+
__all__ = ['__version__', 'KubeVirtControllerCheck']
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
from datadog_checks.base import OpenMetricsBaseCheckV2, is_affirmative
6+
from datadog_checks.base.checks.openmetrics.v2.transform import get_native_dynamic_transformer
7+
8+
from .metrics import METRICS_MAP
9+
10+
11+
class KubeVirtControllerCheck(OpenMetricsBaseCheckV2):
12+
__NAMESPACE__ = "kubevirt_controller"
13+
DEFAULT_METRIC_LIMIT = 0
14+
15+
def __init__(self, name, init_config, instances):
16+
super(KubeVirtControllerCheck, self).__init__(name, init_config, instances)
17+
self.check_initializations.appendleft(self._parse_config)
18+
self.check_initializations.append(self._init_base_tags)
19+
self.check_initializations.append(self._configure_additional_transformers)
20+
21+
def check(self, _):
22+
if self.kubevirt_controller_healthz_endpoint:
23+
self._report_health_check(self.kubevirt_controller_healthz_endpoint)
24+
else:
25+
self.log.warning(
26+
"Skipping health check. Please provide a `kubevirt_controller_healthz_endpoint` to ensure the health of the KubeVirt Controller." # noqa: E501
27+
)
28+
29+
super().check(_)
30+
31+
def _init_base_tags(self):
32+
self.base_tags = [
33+
"pod_name:{}".format(self.pod_name),
34+
"kube_namespace:{}".format(self.kube_namespace),
35+
]
36+
37+
def _report_health_check(self, health_endpoint):
38+
try:
39+
self.log.debug("Checking health status at %s", health_endpoint)
40+
response = self.http.get(health_endpoint, verify=self.tls_verify)
41+
response.raise_for_status()
42+
self.gauge("can_connect", 1, tags=[f"endpoint:{health_endpoint}", *self.base_tags])
43+
except Exception as e:
44+
self.log.error(
45+
"Cannot connect to KubeVirt Controller HTTP endpoint '%s': %s.\n",
46+
health_endpoint,
47+
str(e),
48+
)
49+
self.gauge("can_connect", 0, tags=[f"endpoint:{health_endpoint}", *self.base_tags])
50+
raise
51+
52+
def _parse_config(self):
53+
self.kubevirt_controller_metrics_endpoint = self.instance.get("kubevirt_controller_metrics_endpoint")
54+
self.kubevirt_controller_healthz_endpoint = self.instance.get("kubevirt_controller_healthz_endpoint")
55+
self.kube_namespace = self.instance.get("kube_namespace")
56+
self.pod_name = self.instance.get("kube_pod_name")
57+
self.tls_verify = is_affirmative(self.instance.get("tls_verify"))
58+
59+
self.scraper_configs = []
60+
61+
instance = {
62+
"openmetrics_endpoint": self.kubevirt_controller_metrics_endpoint,
63+
"namespace": self.__NAMESPACE__,
64+
"enable_health_service_check": False,
65+
"tls_verify": self.tls_verify,
66+
}
67+
68+
self.scraper_configs.append(instance)
69+
70+
def _configure_additional_transformers(self):
71+
metric_transformer = self.scrapers[self.kubevirt_controller_metrics_endpoint].metric_transformer
72+
metric_transformer.add_custom_transformer(r".*", self.configure_transformer_kubevirt_metrics(), pattern=True)
73+
74+
def configure_transformer_kubevirt_metrics(self):
75+
"""
76+
Return a metrics transformer that adds tags to all the collected metrics.
77+
"""
78+
79+
def transform(_metric, sample_data, _runtime_data):
80+
for sample, tags, hostname in sample_data:
81+
metric_name = _metric.name
82+
metric_type = _metric.type
83+
84+
# ignore metrics we don't collect
85+
if metric_name not in METRICS_MAP:
86+
continue
87+
88+
# attach tags to the metric
89+
tags = tags + self.base_tags
90+
91+
# apply the METRICS_MAP mapping for the metric name
92+
new_metric_name = METRICS_MAP[metric_name]
93+
if isinstance(new_metric_name, dict) and "name" in new_metric_name:
94+
new_metric_name = new_metric_name["name"]
95+
96+
# call the correct metric submission method based on the metric type
97+
if metric_type == "counter":
98+
self.count(new_metric_name + ".count", sample.value, tags=tags, hostname=hostname)
99+
elif metric_type == "gauge":
100+
self.gauge(new_metric_name, sample.value, tags=tags, hostname=hostname)
101+
else:
102+
metric_transformer = self.scrapers[self.kubevirt_controller_metrics_endpoint].metric_transformer
103+
104+
native_transformer = get_native_dynamic_transformer(
105+
self, new_metric_name, None, metric_transformer.global_options
106+
)
107+
108+
def add_tag_to_sample(sample, pod_tags):
109+
[sample, tags, hostname] = sample
110+
return [sample, tags + pod_tags, hostname]
111+
112+
modified_sample_data = (add_tag_to_sample(x, self.base_tags) for x in sample_data)
113+
native_transformer(_metric, modified_sample_data, _runtime_data)
114+
115+
return transform
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
# This file is autogenerated.
6+
# To change this file you should edit assets/configuration/spec.yaml and then run the following commands:
7+
# ddev -x validate config -s <INTEGRATION_NAME>
8+
# ddev -x validate models -s <INTEGRATION_NAME>
9+
10+
from .instance import InstanceConfig
11+
from .shared import SharedConfig
12+
13+
14+
class ConfigMixin:
15+
_config_model_instance: InstanceConfig
16+
_config_model_shared: SharedConfig
17+
18+
@property
19+
def config(self) -> InstanceConfig:
20+
return self._config_model_instance
21+
22+
@property
23+
def shared_config(self) -> SharedConfig:
24+
return self._config_model_shared

0 commit comments

Comments
 (0)