Generalize kubernetes control plane resource
magnum_capi_helm driver supports only kubeadm and there is no mechanism to use the driver with other k8s distributions like canonical k8s [1]. To generalize the kubernets control plane, the resource class KubeadmControlPlane is modified as K8sControlPlane. Two new configuration parameters are added: * api_resources - provides ability to modify the api version and plural names for cluster api resources. This is especially necessary for changing k8s control plane resource api versions and plural names. Also it will be helpful if the management cluster supports different api version than what is hardcoded in the driver for any cluster api resource. Note plural is only supported for k8s control plane as we dont see any value add to make it configurable for other cluster api resources. * k8s_control_plane_resource_conditions To change the control plane resource condition check to determine the resource status as ready. Canonical k8s does not use etcd as k8s backend and hence the EtcdClusterHealthy condition does not exist and the resource status is CREATE_FAILED. [1] https://documentation.ubuntu.com/canonical-kubernetes/release-1.32/capi/ [2] https://github.com/canonical/cluster-api-k8s/tree/main Change-Id: Iea342f8917f0b797fb3dc5810433d52841af9b55 Signed-off-by: Hemanth Nakkina <hemanth.nakkina@canonical.com>
This commit is contained in:
@@ -156,7 +156,7 @@ class CAPIMonitor(monitors.MonitorBase):
|
||||
resource_name = driver_utils.get_k8s_resource_name(
|
||||
self.cluster, "control-plane"
|
||||
)
|
||||
resource_kcp = self._k8s_client.get_kubeadm_control_plane(
|
||||
resource_kcp = self._k8s_client.get_k8s_control_plane(
|
||||
resource_name, namespace
|
||||
)
|
||||
if not resource_kcp:
|
||||
|
||||
@@ -130,6 +130,40 @@ capi_helm_opts = [
|
||||
"generated application credentials."
|
||||
),
|
||||
),
|
||||
cfg.StrOpt(
|
||||
"api_resources",
|
||||
default={},
|
||||
help=(
|
||||
"""
|
||||
Dictionary of cluster api resources to modify
|
||||
api_version and plural names in string format.
|
||||
|
||||
"Example:"
|
||||
'{
|
||||
"K8sControlPlane": {
|
||||
"api_version": "controlplane.cluster.x-k8s.io/v1beta1",
|
||||
"plural_name": "kubeadmcontrolplanes"
|
||||
},
|
||||
"OpenstackCluster": {
|
||||
"api_version": "infrastructure.cluster.x-k8s.io/v1beta1",
|
||||
},
|
||||
}'
|
||||
"""
|
||||
),
|
||||
),
|
||||
cfg.ListOpt(
|
||||
"k8s_control_plane_resource_conditions",
|
||||
default=[
|
||||
"MachinesReady",
|
||||
"Ready",
|
||||
"EtcdClusterHealthy",
|
||||
"ControlPlaneComponentsHealthy",
|
||||
],
|
||||
help=(
|
||||
"List of conditions to check for kubernetes control plane "
|
||||
"resource to consider as ready."
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -82,7 +82,7 @@ class Driver(driver.Driver):
|
||||
def _update_control_plane_nodegroup_status(self, cluster, nodegroup):
|
||||
# The status of the master nodegroup is determined by the Cluster API
|
||||
# control plane object
|
||||
kcp = self._k8s_client.get_kubeadm_control_plane(
|
||||
kcp = self._k8s_client.get_k8s_control_plane(
|
||||
driver_utils.get_k8s_resource_name(cluster, "control-plane"),
|
||||
driver_utils.cluster_namespace(cluster),
|
||||
)
|
||||
@@ -108,12 +108,7 @@ class Driver(driver.Driver):
|
||||
}
|
||||
kcp_ready = all(
|
||||
cond in kcp_true_conditions
|
||||
for cond in (
|
||||
"MachinesReady",
|
||||
"Ready",
|
||||
"EtcdClusterHealthy",
|
||||
"ControlPlaneComponentsHealthy",
|
||||
)
|
||||
for cond in CONF.capi_helm.k8s_control_plane_resource_conditions
|
||||
)
|
||||
target_replicas = kcp_spec.get("replicas")
|
||||
current_replicas = kcp_status.get("replicas")
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
import base64
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
@@ -171,8 +172,8 @@ class Client(requests.Session):
|
||||
def get_capi_openstackcluster(self, name, namespace):
|
||||
return OpenstackCluster(self).fetch(name, namespace)
|
||||
|
||||
def get_kubeadm_control_plane(self, name, namespace):
|
||||
return KubeadmControlPlane(self).fetch(name, namespace)
|
||||
def get_k8s_control_plane(self, name, namespace):
|
||||
return K8sControlPlane(self).fetch(name, namespace)
|
||||
|
||||
def get_machine_deployment(self, name, namespace):
|
||||
return MachineDeployment(self).fetch(name, namespace)
|
||||
@@ -201,6 +202,7 @@ class Resource:
|
||||
self, "plural_name", self.kind.lower() + "s"
|
||||
)
|
||||
self.namespaced = getattr(self, "namespaced", True)
|
||||
self.api_resources = json.loads(CONF.capi_helm.api_resources)
|
||||
|
||||
def prepare_path(self, name=None, namespace=None):
|
||||
# Begin with either /api or /apis depending whether the api version
|
||||
@@ -287,29 +289,62 @@ class Secret(Resource):
|
||||
|
||||
|
||||
class Cluster(Resource):
|
||||
api_version = "cluster.x-k8s.io/v1beta1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("Cluster", {})
|
||||
.get("api_version", "cluster.x-k8s.io/v1beta1")
|
||||
)
|
||||
|
||||
|
||||
class OpenstackCluster(Resource):
|
||||
api_version = "infrastructure.cluster.x-k8s.io/v1beta1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("OpenstackCluster", {})
|
||||
.get("api_version", "infrastructure.cluster.x-k8s.io/v1beta1")
|
||||
)
|
||||
|
||||
|
||||
class MachineDeployment(Resource):
|
||||
api_version = "cluster.x-k8s.io/v1beta1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("MachineDeployment", {})
|
||||
.get("api_version", "cluster.x-k8s.io/v1beta1")
|
||||
)
|
||||
|
||||
|
||||
class KubeadmControlPlane(Resource):
|
||||
api_version = "controlplane.cluster.x-k8s.io/v1beta1"
|
||||
class K8sControlPlane(Resource):
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("K8sControlPlane", {})
|
||||
.get("api_version", "controlplane.cluster.x-k8s.io/v1beta1")
|
||||
)
|
||||
plural_name = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("K8sControlPlane", {})
|
||||
.get("plural_name", "kubeadmcontrolplanes")
|
||||
)
|
||||
|
||||
|
||||
class Machine(Resource):
|
||||
api_version = "cluster.x-k8s.io/v1beta1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("Machine", {})
|
||||
.get("api_version", "cluster.x-k8s.io/v1beta1")
|
||||
)
|
||||
|
||||
|
||||
class Manifests(Resource):
|
||||
api_version = "addons.stackhpc.com/v1alpha1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("Manifests", {})
|
||||
.get("api_version", "addons.stackhpc.com/v1alpha1")
|
||||
)
|
||||
plural_name = "manifests"
|
||||
|
||||
|
||||
class HelmRelease(Resource):
|
||||
api_version = "addons.stackhpc.com/v1alpha1"
|
||||
api_version = (
|
||||
json.loads(CONF.capi_helm.api_resources)
|
||||
.get("HelmRelease", {})
|
||||
.get("api_version", "addons.stackhpc.com/v1alpha1")
|
||||
)
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestCAPIMonitor(base.DbTestCase):
|
||||
"ready": True,
|
||||
}
|
||||
}
|
||||
self.mock_k8s.get_kubeadm_control_plane.return_value = copy.deepcopy(
|
||||
self.mock_k8s.get_k8s_control_plane.return_value = copy.deepcopy(
|
||||
ready_state
|
||||
)
|
||||
self.mock_k8s.get_machine_deployment.return_value = copy.deepcopy(
|
||||
@@ -193,7 +193,7 @@ class TestCAPIMonitor(base.DbTestCase):
|
||||
]
|
||||
}
|
||||
}
|
||||
self.mock_k8s.get_kubeadm_control_plane.return_value = cp_state
|
||||
self.mock_k8s.get_k8s_control_plane.return_value = cp_state
|
||||
self.monitor.poll_health_status()
|
||||
|
||||
self.assertEqual(
|
||||
@@ -264,7 +264,7 @@ class TestCAPIMonitor(base.DbTestCase):
|
||||
def test_all_missing(self):
|
||||
self.mock_k8s.get_capi_cluster.return_value = None
|
||||
self.mock_k8s.get_capi_openstackcluster.return_value = None
|
||||
self.mock_k8s.get_kubeadm_control_plane.return_value = None
|
||||
self.mock_k8s.get_k8s_control_plane.return_value = None
|
||||
self.mock_k8s.get_machine_deployment.return_value = None
|
||||
self.monitor.poll_health_status()
|
||||
|
||||
|
||||
@@ -413,13 +413,13 @@ class ClusterAPIDriverTest(base.DbTestCase):
|
||||
mock_load.return_value = mock_client
|
||||
nodegroup = mock.MagicMock()
|
||||
nodegroup.name = "masters"
|
||||
mock_client.get_kubeadm_control_plane.return_value = None
|
||||
mock_client.get_k8s_control_plane.return_value = None
|
||||
|
||||
self.driver._update_control_plane_nodegroup_status(
|
||||
self.cluster_obj, nodegroup
|
||||
)
|
||||
|
||||
mock_client.get_kubeadm_control_plane.assert_called_once_with(
|
||||
mock_client.get_k8s_control_plane.assert_called_once_with(
|
||||
"cluster-example-a-111111111111-control-plane",
|
||||
"magnum-fakeproject",
|
||||
)
|
||||
@@ -455,13 +455,13 @@ class ClusterAPIDriverTest(base.DbTestCase):
|
||||
"readyReplicas": 3,
|
||||
},
|
||||
}
|
||||
mock_client.get_kubeadm_control_plane.return_value = kcp
|
||||
mock_client.get_k8s_control_plane.return_value = kcp
|
||||
|
||||
self.driver._update_control_plane_nodegroup_status(
|
||||
self.cluster_obj, nodegroup
|
||||
)
|
||||
|
||||
mock_client.get_kubeadm_control_plane.assert_called_once_with(
|
||||
mock_client.get_k8s_control_plane.assert_called_once_with(
|
||||
"cluster-example-a-111111111111-control-plane",
|
||||
"magnum-fakeproject",
|
||||
)
|
||||
@@ -497,13 +497,13 @@ class ClusterAPIDriverTest(base.DbTestCase):
|
||||
"readyReplicas": 2,
|
||||
},
|
||||
}
|
||||
mock_client.get_kubeadm_control_plane.return_value = kcp
|
||||
mock_client.get_k8s_control_plane.return_value = kcp
|
||||
|
||||
self.driver._update_control_plane_nodegroup_status(
|
||||
self.cluster_obj, nodegroup
|
||||
)
|
||||
|
||||
mock_client.get_kubeadm_control_plane.assert_called_once_with(
|
||||
mock_client.get_k8s_control_plane.assert_called_once_with(
|
||||
"cluster-example-a-111111111111-control-plane",
|
||||
"magnum-fakeproject",
|
||||
)
|
||||
@@ -539,13 +539,13 @@ class ClusterAPIDriverTest(base.DbTestCase):
|
||||
"readyReplicas": 3,
|
||||
},
|
||||
}
|
||||
mock_client.get_kubeadm_control_plane.return_value = kcp
|
||||
mock_client.get_k8s_control_plane.return_value = kcp
|
||||
|
||||
self.driver._update_control_plane_nodegroup_status(
|
||||
self.cluster_obj, nodegroup
|
||||
)
|
||||
|
||||
mock_client.get_kubeadm_control_plane.assert_called_once_with(
|
||||
mock_client.get_k8s_control_plane.assert_called_once_with(
|
||||
"cluster-example-a-111111111111-control-plane",
|
||||
"magnum-fakeproject",
|
||||
)
|
||||
|
||||
@@ -301,14 +301,14 @@ class TestKubernetesClient(base.TestCase):
|
||||
)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_get_kubeadm_control_plane_found(self, mock_request):
|
||||
def test_get_k8s_control_plane_found(self, mock_request):
|
||||
client = kubernetes.Client(TEST_KUBECONFIG)
|
||||
mock_response = mock.MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = "mock_json"
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
cluster = client.get_kubeadm_control_plane("name", "ns1")
|
||||
cluster = client.get_k8s_control_plane("name", "ns1")
|
||||
|
||||
mock_request.assert_called_once_with(
|
||||
"GET",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds new configuration parameters to update api_version and plural names
|
||||
of k8s resources related to Cluster API. To be specific, the resources
|
||||
are Cluster, OpenstackCluster, MachineDeployment, K8sControlPlane,
|
||||
Machine, Manifests, HelmRelease.
|
||||
|
||||
Adds new configuration parameter to specify list of conditions to check
|
||||
in kubernetes control plane resource to consider resource as ready.
|
||||
Reference in New Issue
Block a user