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

Coredns integration with K8s-charm #35

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a275fc6
coredns integration implementation
louiseschmidtgen Feb 26, 2024
d49a76b
format
louiseschmidtgen Feb 26, 2024
bba0036
lint
louiseschmidtgen Feb 26, 2024
d7f9dda
fixtures draft
louiseschmidtgen Feb 26, 2024
580bea1
add tests and integration with coredns
louiseschmidtgen Feb 27, 2024
e16b6e8
cleanup
louiseschmidtgen Feb 27, 2024
bbd57c2
use opstest for cross model relation
louiseschmidtgen Feb 27, 2024
528abf0
use future endpoint for setting dns bits
louiseschmidtgen Feb 27, 2024
8d41a88
Adams suggestions
louiseschmidtgen Feb 27, 2024
3d788d5
return statement
louiseschmidtgen Feb 28, 2024
1e17afc
update dns config
louiseschmidtgen Mar 5, 2024
16de018
use adams pytest fixtures
louiseschmidtgen Mar 5, 2024
181845f
add pytest operator with add-k8s in requirements
louiseschmidtgen Mar 5, 2024
37ce582
k8s creds
louiseschmidtgen Mar 5, 2024
b4c96a4
get kubeconfig
louiseschmidtgen Mar 6, 2024
95073b1
fix test
louiseschmidtgen Mar 6, 2024
2314eec
tweak test
louiseschmidtgen Mar 6, 2024
b3e1063
add log
louiseschmidtgen Mar 6, 2024
c61854c
try sth
louiseschmidtgen Mar 6, 2024
e7d589f
change back to fixture
louiseschmidtgen Mar 6, 2024
4746424
fixture test
louiseschmidtgen Mar 6, 2024
2be2114
use mark fixture
louiseschmidtgen Mar 6, 2024
06a9dc9
improvements from pair programming
addyess Mar 6, 2024
c6f95f8
fix fixtures
louiseschmidtgen Mar 7, 2024
82e809a
cleanup
louiseschmidtgen Mar 7, 2024
bf40711
correct cleanup order
louiseschmidtgen Mar 7, 2024
ed3674e
update test
louiseschmidtgen Mar 8, 2024
236cf5e
rm empty test
louiseschmidtgen Mar 12, 2024
4e8d2ea
linting
louiseschmidtgen Mar 12, 2024
910fdf9
lint
louiseschmidtgen Mar 12, 2024
95e5d07
try kubeconfig from action
louiseschmidtgen Mar 23, 2024
11c9b70
undo kubeconfig
louiseschmidtgen Mar 25, 2024
9f1d8f7
Post rebase fixup
addyess Apr 25, 2024
46415da
Merge branch 'main' into KU-370/coredns-integration
addyess Apr 26, 2024
71882be
Merge branch 'main' into KU-370/coredns-integration
addyess Apr 26, 2024
16b97d5
Remove merge mistakes
addyess Apr 26, 2024
0d9eb7c
Merge branch 'main' into KU-370/coredns-integration
addyess Apr 29, 2024
4711de4
Merge branch 'main' into KU-370/coredns-integration
addyess Apr 29, 2024
c25a5d3
Merge branch 'main' into KU-370/coredns-integration
addyess May 2, 2024
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Sometimes you will want to debug certain situations, and having the models torn

Running the integration tests with extra arguments can be accomplished with
```shell
tox run -e integration-tests -- --positional --arguments
tox run -e integration -- --positional --arguments
```

#### COS Integration
Expand All @@ -122,7 +122,7 @@ The COS integration tests are optional as these are slow/heavy tests. Currently,

#### Useful arguments
`--keep-models`: Doesn't delete the model once the integration tests are finished
`--model`: Rerun the test with a given model name -- if it already exist, the integration tests will use it
`--model`: Rerun the test with a given model name -- if it already exist, the integration tests will use it. The model is not destroyed after the tests are finished.
`-k regex-pattern`: run a specific set of matching tests names ignore other passing tests
Remember that cloud costs could be incurred for every machine -- so be sure to clean up your models on clouds if you instruct pytest-operator to not clean up the models.

Expand Down
3 changes: 3 additions & 0 deletions charms/worker/k8s/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,6 @@ requires:
interface: etcd
external-cloud-provider:
interface: external_cloud_provider
dns-provider:
interface: kube-dns
limit: 1
11 changes: 11 additions & 0 deletions charms/worker/k8s/lib/charms/k8s/v0/k8sd_api_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,17 @@ def update_cluster_config(self, config: UpdateClusterConfigRequest):
body = config.dict(exclude_none=True, by_alias=True)
self._send_request(endpoint, "PUT", EmptyResponse, body)

def configure_dns(self, dns_domain: str, dns_ip: str):
"""Configure the DNS for the k8s cluster.

Args:
dns_domain (str): The domain name for the DNS.
dns_ip (str): The IP address for the DNS.
"""
endpoint = "/1.0/k8sd/cluster/config"
body = {"dns.dns-ip": dns_ip, "dns.cluster-domain": dns_domain, "dns.enabled": False}
self._send_request(endpoint, "POST", EmptyResponse, body)

louiseschmidtgen marked this conversation as resolved.
Show resolved Hide resolved
def get_cluster_status(self) -> GetClusterStatusResponse:
"""Retrieve cluster status.

Expand Down
1 change: 1 addition & 0 deletions charms/worker/k8s/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ charm-lib-contextual-status @ git+https://github.com/charmed-kubernetes/charm-li
charm-lib-interface-external-cloud-provider @ git+https://github.com/charmed-kubernetes/charm-lib-interface-external-cloud-provider@e1c5fc69e98100a7d43c0ad5a7969bba1ecbcd40
charm-lib-node-base @ git+https://github.com/charmed-kubernetes/layer-kubernetes-node-base@9b212854e768f13c26cc907bed51444e97e51b50#subdirectory=ops
charm-lib-reconciler @ git+https://github.com/charmed-kubernetes/charm-lib-reconciler@f818cc30d1a22be43ffdfecf7fbd9c3fd2967502
charm-lib-interface-kube-dns @ git+https://github.com/charmed-kubernetes/charm-lib-interface-kube-dns@3b94af979dbb3ecd926faa30b37dc366e90385e5
cosl==0.0.8
ops==2.12.0
pydantic==1.10.15
Expand Down
21 changes: 19 additions & 2 deletions charms/worker/k8s/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from charms.contextual_status import WaitingStatus, on_error
from charms.grafana_agent.v0.cos_agent import COSAgentProvider
from charms.interface_external_cloud_provider import ExternalCloudProvider
from charms.interface_kube_dns import KubeDnsRequires
from charms.k8s.v0.k8sd_api_manager import (
BootstrapConfig,
ControlPlaneNodeJoinConfig,
Expand Down Expand Up @@ -105,6 +106,7 @@ def __init__(self, *args):
self.reconciler = Reconciler(self, self._reconcile)
self.distributor = TokenDistributor(self, self.get_node_name(), self.api_manager)
self.collector = TokenCollector(self, self.get_node_name())
self.kube_dns = KubeDnsRequires(self, endpoint="dns-provider")
self.labeler = LabelMaker(
self, kubeconfig_path=self._internal_kubeconfig, kubectl=KUBECTL_PATH
)
Expand Down Expand Up @@ -381,11 +383,10 @@ def _enable_functionalities(self):
"""Enable necessary components for the Kubernetes cluster."""
status.add(ops.MaintenanceStatus("Updating K8s features"))
log.info("Enabling K8s features")
dns_config = DNSConfig(enabled=True)
dns_config = self._get_dns_config()
network_config = NetworkConfig(enabled=True)
user_cluster_config = UserFacingClusterConfig(dns=dns_config, network=network_config)
update_request = UpdateClusterConfigRequest(config=user_cluster_config)

self.api_manager.update_cluster_config(update_request)

@on_error(
Expand Down Expand Up @@ -418,6 +419,22 @@ def _ensure_cluster_config(self):

self.api_manager.update_cluster_config(update_request)

def _get_dns_config(self) -> DNSConfig:
"""Get DNS config either for the enabled built-in dns or an integrated charm.

Returns:
DNSConfig: A DNSConfig object with the cluster domain,
service IP and whether the default dns is enabled or not.
"""
if self.kube_dns.domain is not None and self.kube_dns.address is not None:
return DNSConfig(
enabled=False,
cluster_domain=self.kube_dns.domain,
service_ip=self.kube_dns.address,
)

return DNSConfig(enabled=True)

def _get_scrape_jobs(self):
"""Retrieve the Prometheus Scrape Jobs.

Expand Down
82 changes: 82 additions & 0 deletions charms/worker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[tool.bandit]
exclude_dirs = ["/venv/", "tests"]
skips = ["B404","B603"]
[tool.bandit.assert_used]
skips = ["*/*test.py", "*/test_*.py", "*tests/*.py"]

# Testing tools configuration
[tool.coverage.run]
branch = true

# Formatting tools configuration
[tool.black]
line-length = 99
target-version = ["py38"]

[tool.coverage.report]
show_missing = true

# Linting tools configuration
[tool.flake8]
max-line-length = 99
max-doc-length = 99
max-complexity = 10
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
select = ["E", "W", "F", "C", "N", "R", "D", "H"]
# Ignore W503, E501 because using black creates errors with this
# Ignore D107 Missing docstring in __init__
# Ignore N805 first argument should be named self. Pydantic validators do not comply.
ignore = ["W503", "E501", "D107", "N805"]

# D100, D101, D102, D103: Ignore missing docstrings in tests
per-file-ignores = ["tests/*:D100,D101,D102,D103,D104,D205,D212,D415"]
docstring-convention = "google"

[tool.isort]
line_length = 99
profile = "black"

[tool.mypy]
ignore_missing_imports = true
explicit_package_bases = true
namespace_packages = true

[tool.pylint]
# Ignore too-few-public-methods due to pydantic models
# Ignore no-self-argument due to pydantic validators
disable = "wrong-import-order,redefined-outer-name,too-few-public-methods,no-self-argument,fixme,too-many-instance-attributes"
# Ignore Pydantic check: https://github.com/pydantic/pydantic/issues/1961
extension-pkg-whitelist = "pydantic" # wokeignore:rule=whitelist

[tool.pytest.ini_options]
minversion = "6.0"
log_cli_level = "INFO"

# Linting tools configuration
[tool.ruff]
line-length = 99
select = ["E", "W", "F", "C", "N", "D", "I001"]
extend-ignore = [
"D203",
"D204",
"D213",
"D215",
"D400",
"D404",
"D406",
"D407",
"D408",
"D409",
"D413",
]
ignore = ["E501", "D107"]
extend-exclude = ["__pycache__", "*.egg_info"]
per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]}

[tool.ruff.mccabe]
max-complexity = 10

[tool.codespell]
skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage"
[tool.pyright]
extraPaths = ["./lib"]
67 changes: 67 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,73 @@ async def grafana_agent(kubernetes_cluster: Model):
await kubernetes_cluster.remove_application("grafana-agent")


@pytest_asyncio.fixture(scope="module")
async def cluster_kubeconfig(ops_test: OpsTest, kubernetes_cluster: Model):
"""Fixture to pull the kubeconfig out of the kubernetes cluster."""
k8s = kubernetes_cluster.applications["k8s"].units[0]
action = await k8s.run_action("get-kubeconfig")
action = await action.wait()
kubeconfig_path = ops_test.tmp_path / "kubeconfig"
kubeconfig_path.write_text(action.results["kubeconfig"])
yield kubeconfig_path


@pytest_asyncio.fixture(scope="module")
async def coredns_model(ops_test: OpsTest, cluster_kubeconfig: Path):
"""
This fixture deploys Coredns on the specified
Kubernetes (k8s) model for testing purposes.
"""
coredns_alias = "coredns-model"

config = type.__call__(Configuration)
k8s_config.load_config(client_configuration=config, config_file=str(cluster_kubeconfig))

log.info("Adding k8s cloud")
k8s_cloud = await ops_test.add_k8s(skip_storage=True, kubeconfig=config)
k8s_model = await ops_test.track_model(
coredns_alias, cloud_name=k8s_cloud, keep=ops_test.ModelKeep.NEVER
)
log.info("Deploying Coredns ")
await k8s_model.deploy("coredns", trust=True)
await k8s_model.wait_for_idle(apps=["coredns"], status="active")
yield k8s_model

# the cluster is consuming this model: remove saas first
await ops_test.forget_model(coredns_alias, timeout=40)


@pytest_asyncio.fixture(scope="module")
async def integrate_coredns(coredns_model: Model, kubernetes_cluster: Model):
"""This function offers Coredns in the specified Kubernetes (k8s) model."""
log.info("Offering Coredns...")
await coredns_model.create_offer("coredns:dns-provider")
await coredns_model.block_until(lambda: "coredns" in coredns_model.application_offers)
log.info("Coredns offered...")

log.info("Consuming Coredns...")
model_owner = untag("user-", coredns_model.info.owner_tag)

await coredns_model.wait_for_idle(status="active")
await kubernetes_cluster.wait_for_idle(status="active")

offer_url = f"{model_owner}/{coredns_model.name}.coredns"
saas = await kubernetes_cluster.consume(offer_url)
log.info("Coredns consumed...")

log.info("Relating Coredns...")
await kubernetes_cluster.integrate("k8s:dns-provider", "coredns")
assert "coredns" in kubernetes_cluster.remote_applications

yield coredns_model

# Now let's clean up
await kubernetes_cluster.applications["k8s"].destroy_relation("k8s:dns-provider", "coredns")
await kubernetes_cluster.wait_for_idle(status="active")
await kubernetes_cluster.remove_saas(saas)
await coredns_model.remove_offer(f"{coredns_model.name}.{saas}", force=True)


@pytest_asyncio.fixture(scope="module")
async def cos_model(
ops_test: OpsTest, kubernetes_cluster, _grafana_agent # pylint: disable=W0613
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/cos_substrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ def execute_command(self, container, command: List[str]):
return None

def get_kubeconfig(self, container) -> str:
"""Get kubeconfig from a container.
"""
Get kubeconfig from a container.

Args:
container: Container instance.
Expand Down
1 change: 1 addition & 0 deletions tests/integration/grafana.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(

def _get_with_auth(self, url: str) -> str:
"""Send GET request with basic authentication.
Raises AssertionError: If the response status code is not 200.

Args:
url (str): The URL to send the request to.
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/prometheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(

def _get_url(self, url):
"""Send GET request to the provided URL.
Raises AssertionError: If the response status code is not 200.

Args:
url (str): The URL to send the request to.
Expand Down Expand Up @@ -66,6 +67,7 @@ async def health(self) -> str:

async def check_metrics(self, query: str):
"""Query Prometheus for metrics.
Raises AssertionError: If the query fails or if data is not yet available.

Args:
query (str): The Prometheus query to execute.
Expand Down
23 changes: 23 additions & 0 deletions tests/integration/test_k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,26 @@
]
for query in queries:
await prometheus.check_metrics(query)


@pytest.mark.usefixtures("integrate_coredns")
async def test_dns(kubernetes_cluster: model.Model):
"""
This function performs a DNS test on the specified Kubernetes (k8s) unit in the cluster model.
The test is performed by running a pod in the k8s unit and
checking if it can resolve the domain name
(See: https://charmhub.io/microk8s/docs/how-to-advanced-dns).
"""
log.info("Running DNS test...")
k8s = kubernetes_cluster.applications["k8s"]
exec_cmd = "k8s kubectl run --rm -it --image alpine \
--restart=Never test-dns -- nslookup canonical.com"
action = await k8s.units[0].run(exec_cmd)
result = await action.wait()
log.info("DNS test result: %s", result.results)
assert result.results["return-code"] == 0, "DNS Test failed."

output = result.results["stdout"]
assert "canonical.com" in output, "Canonical.com not found in DNS result."
Dismissed Show dismissed Hide dismissed
# TODO: test this output, https://charmhub.io/microk8s/docs/how-to-advanced-dns
log.info("DNS test passed.")
Loading