-
Notifications
You must be signed in to change notification settings - Fork 3
/
storage_manifests.py
144 lines (115 loc) · 4.85 KB
/
storage_manifests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# Copyright 2022 Canonical Ltd.
# See LICENSE file for licensing details.
"""Implementation of cinder-csi specific details of the kubernetes manifests."""
import logging
import pickle
from hashlib import md5
from typing import Dict, Optional
from lightkube.codecs import AnyResource, from_dict
from ops.manifests import Addition, ConfigRegistry, ManifestLabel, Manifests, Patch
log = logging.getLogger(__file__)
NAMESPACE = "kube-system"
SECRET_NAME = "csi-cinder-cloud-config"
STORAGE_CLASS_NAME = "csi-cinder-{type}"
class CreateSecret(Addition):
"""Create secret for the deployment.
a secret named cloud-config in the kube-system namespace
cloud.conf -- base64 encoded contents of cloud.conf
endpoint-ca.cert -- base64 encoded ca cert for the auth-url
"""
CONFIG_TO_SECRET = {"cloud-conf": "cloud.conf", "endpoint-ca-cert": "endpoint-ca.cert"}
def __call__(self) -> Optional[AnyResource]:
"""Craft the secrets object for the deployment."""
secret_config = {}
for k, new_k in self.CONFIG_TO_SECRET.items():
if value := self.manifests.config.get(k):
secret_config[new_k] = value.decode()
log.info("Encode secret data for storage.")
return from_dict(
dict(
apiVersion="v1",
kind="Secret",
type="Opaque",
metadata=dict(name=SECRET_NAME, namespace=NAMESPACE),
data=secret_config,
)
)
class CreateStorageClass(Addition):
"""Create cinder storage class."""
def __init__(self, manifests: "Manifests", sc_type: str):
super().__init__(manifests)
self.type = sc_type
def __call__(self) -> Optional[AnyResource]:
"""Craft the storage class object."""
storage_name = STORAGE_CLASS_NAME.format(type=self.type)
log.info(f"Creating storage class {storage_name}")
sc = from_dict(
dict(
apiVersion="storage.k8s.io/v1",
kind="StorageClass",
metadata=dict(name=storage_name),
provisioner="cinder.csi.openstack.org",
reclaimPolicy="Delete",
volumeBindingMode="WaitForFirstConsumer",
)
)
if az := self.manifests.config.get("availability-zone"):
sc.parameters.availability = az
return sc
class UpdateSecrets(Patch):
"""Update the secret name in Deployments and DaemonSets."""
def __call__(self, obj):
"""Update the secret volume spec in daemonsets and deployments."""
if not any(
[
(obj.kind == "DaemonSet" and obj.metadata.name == "csi-cinder-nodeplugin"),
(obj.kind == "Deployment" and obj.metadata.name == "csi-cinder-controllerplugin"),
]
):
return
for volume in obj.spec.template.spec.volumes:
if volume.secret:
volume.secret.secretName = SECRET_NAME
log.info(f"Setting secret for {obj.kind}/{obj.metadata.name}")
class StorageManifests(Manifests):
"""Deployment Specific details for the cinder-csi-driver."""
def __init__(self, charm, charm_config, kube_control, integrator):
super().__init__(
"cinder-csi-driver",
charm.model,
"upstream/cloud_storage",
[
CreateSecret(self),
ManifestLabel(self),
ConfigRegistry(self),
CreateStorageClass(self, "default"), # creates csi-cinder-default
UpdateSecrets(self), # update secrets
],
)
self.integrator = integrator
self.charm_config = charm_config
self.kube_control = kube_control
@property
def config(self) -> Dict:
"""Returns current config available from charm config and joined relations."""
config: Dict = {}
if self.kube_control.is_ready:
config["image-registry"] = self.kube_control.get_registry_location()
if self.integrator.is_ready:
config["cloud-conf"] = self.integrator.cloud_conf_b64
config["endpoint-ca-cert"] = self.integrator.endpoint_tls_ca
config.update(**self.charm_config.available_data)
for key, value in dict(**config).items():
if value == "" or value is None:
del config[key]
config["release"] = config.pop("storage-release", None)
return config
def hash(self) -> int:
"""Calculate a hash of the current configuration."""
return int(md5(pickle.dumps(self.config)).hexdigest(), 16)
def evaluate(self) -> Optional[str]:
"""Determine if manifest_config can be applied to manifests."""
for prop in ["cloud-conf"]:
if not self.config.get(prop):
return f"Storage manifests waiting for definition of {prop}"
return None