Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subordinate charm config PoC #160

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion charms/worker/k8s/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ config:
type: string
description: |
Labels can be used to organize and to select subsets of nodes in the
cluster. Declare node labels in key=value format, separated by spaces.
cluster. Declare node labels in key=value format, separated by spaces.
register-with-taints:
type: string
default: ""
Expand Down Expand Up @@ -178,6 +178,9 @@ provides:
interface: containerd
ceph-k8s-info:
interface: kubernetes-info
feature:
interface: k8s-snap-feature
scope: container

requires:
etcd:
Expand Down
23 changes: 22 additions & 1 deletion charms/worker/k8s/lib/charms/k8s/v0/k8sd_api_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class utilises different connection factories (UnixSocketConnectionFactory
from typing import Any, Dict, Generator, List, Optional, Type, TypeVar

import yaml
from pydantic import AnyHttpUrl, BaseModel, Field, SecretStr, validator
from pydantic import AnyHttpUrl, BaseModel, Field, SecretStr, validator, root_validator

# The unique Charmhub library identifier, never change it
LIBID = "6a5f235306864667a50437c08ba7e83f"
Expand Down Expand Up @@ -520,6 +520,27 @@ class KubeConfigMetadata(BaseModel):

kubeconfig: str

class FeatureRelationData(BaseModel):
"""Model representing data for a feature relation.

Attributes:
name (str): The feature name.
version (str): The library version that was used to create the feature object.
attributes (str): The serialized Pydantic model representing the feature configuration.
"""
name: str
version: str
attributes: str

@root_validator(pre=True)
def check_data(cls, values):
if 'name' not in values:
raise ValueError("The 'name' field must be provided")
if 'version' not in values:
raise ValueError("The 'version' field must be provided")
if 'attributes' not in values:
raise ValueError("The 'attributes' field must be provided")
return values

class GetKubeConfigResponse(BaseRequestModel):
"""Response model for getting the kubeconfig from the cluster.
Expand Down
43 changes: 43 additions & 0 deletions charms/worker/k8s/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import socket
import subprocess
import uuid
import json
from functools import cached_property
from pathlib import Path
from time import sleep
Expand All @@ -38,6 +39,7 @@
from charms.interface_external_cloud_provider import ExternalCloudProvider
from charms.k8s.v0.k8sd_api_manager import (
BootstrapConfig,
LoadBalancerConfig,
ControlPlaneNodeJoinConfig,
CreateClusterRequest,
DNSConfig,
Expand All @@ -51,6 +53,7 @@
UpdateClusterConfigRequest,
UserFacingClusterConfig,
UserFacingDatastoreConfig,
FeatureRelationData,
)
from charms.kubernetes_libs.v0.etcd import EtcdReactiveRequires
from charms.node_base import LabelMaker
Expand All @@ -60,6 +63,7 @@
from snap import version as snap_version
from token_distributor import ClusterTokenType, TokenCollector, TokenDistributor, TokenStrategy
from typing_extensions import Literal
from pydantic import ValidationError

# Log messages can be retrieved using juju debug-log
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -149,10 +153,48 @@ def __init__(self, *args):
)

self.framework.observe(self.on.update_status, self._on_update_status)

if self.is_control_plane:
self.etcd = EtcdReactiveRequires(self)
self.framework.observe(self.on.get_kubeconfig_action, self._get_external_kubeconfig)

def _resolve_feature_relations(self, event: ops.EventBase):
log.info("Resolving feature relations as snap configuration")

eaudetcobello marked this conversation as resolved.
Show resolved Hide resolved
for relation in self.model.relations["feature"]:
for unit in relation.units:
raw_relation_data = relation.data[unit]

if not raw_relation_data:
log.warning("No relation data found for %s relation", relation.name)
continue

try:
parsed_relation_data = FeatureRelationData.parse_obj(raw_relation_data)
except ValidationError as e:
log.warning("The relation data for %s relation is not valid: %s", relation.name, e)
continue

feature_name = parsed_relation_data.name
feature_attributes = parsed_relation_data.attributes

feature_config_classes: Dict[str, type] = {
"load-balancer": LoadBalancerConfig,
"local-storage": LocalStorageConfig,
}
if not (config_class := feature_config_classes.get(feature_name)):
status.add(ops.BlockedStatus(f"Unsupported feature {feature_name}"))
continue

feature_config = config_class.parse_raw(feature_attributes)
log.info("Updating feature [%s] with config [%s]", feature_name, feature_config)

cluster_config = UserFacingClusterConfig(**{feature_name: feature_config})
update_request = UpdateClusterConfigRequest(config=cluster_config)
self.api_manager.update_cluster_config(update_request)

log.info("Feature [%s] updated", feature_name)

def _k8s_info(self, event: ops.EventBase):
"""Send cluster information on the kubernetes-info relation.

Expand Down Expand Up @@ -700,6 +742,7 @@ def _reconcile(self, event: ops.EventBase):
self._apply_cos_requirements()
self._revoke_cluster_tokens(event)
self._ensure_cluster_config()
self._resolve_feature_relations(event)
self._join_cluster()
self._config_containerd_registries()
self._configure_cos_integration()
Expand Down
Loading