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

Support builds-on/runs-on arm #94

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
66 changes: 0 additions & 66 deletions .github/workflows/build-charm.yaml

This file was deleted.

6 changes: 4 additions & 2 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
pull_request:

jobs:

extra-args:
runs-on: ubuntu-latest
outputs:
Expand Down Expand Up @@ -41,12 +40,15 @@ jobs:
matrix:
arch:
- {id: amd64, builder-label: ubuntu-22.04, tester-arch: x64}
- {id: arm64, builder-label: ARM64, tester-arch: ARM64}
suite: ["k8s", "etcd", "ceph"]
with:
identifier: ${{ matrix.arch.id }}-${{ matrix.suite }}
builder-runner-label: ${{ matrix.arch.builder-label }}
charmcraft-channel: ${{ needs.charmcraft-channel.outputs.channel }}
extra-arguments: ${{needs.extra-args.outputs.args}} -k test_${{ matrix.suite }}
extra-arguments: >-
${{needs.extra-args.outputs.args}} -k test_${{ matrix.suite }}
${{ matrix.arch.id == 'arm64' && ' --lxd-containers' || '' }}
Comment on lines +49 to +51
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some study on a failure on ARM now that we're testing with lxd VMs.

lxd is required to use /dev/kvm, but on ARM64 -- /dev/kvm may not exist. I even logged into the machine and tried to add sudo modprobe kvm but it fails to load the kernel mode.

without /dev/kvm -- lxd doesn't start the machines and the tests fail

Therefore -- this change allows for lxd-containers on ARM -- but i expect we'd need arm64 to support /dev/kvm or don't use lxd as a cloud for arm64

juju-channel: 3/stable
load-test-enabled: false
provider: lxd
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/promote-charms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
charm-directory: ${{ fromJson(needs.select-charms.outputs.charms) }}
arch:
- amd64
- arm64
uses: canonical/operator-workflows/.github/workflows/promote_charm.yaml@main
with:
base-architecture: ${{ matrix.arch }}
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/publish-charms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ jobs:
- { path: ./charms/worker/, tagPrefix: k8s-worker }
arch:
- amd64
- arm64
secrets: inherit
with:
channel: ${{needs.configure-channel.outputs.track}}/${{needs.configure-channel.outputs.risk}}
working-directory: ${{ matrix.charm.path }}
charmcraft-channel: ${{ needs.charmcraft-channel.outputs.channel }}
tag-prefix: ${{ matrix.charm.tagPrefix }}
identifier: ${{ matrix.arch}}-k8s
identifier: ${{matrix.arch}}-k8s
10 changes: 4 additions & 6 deletions charms/CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ The `k8s` and `k8s-worker` charms are noticeably tucked into one-another.
```
└── worker
├── charmcraft.yaml
├── requirements.txt
└── k8s
├── charmcraft.yaml
├── lib
Expand All @@ -25,15 +24,14 @@ The unique parts of the charm are what are in each charm's top-level directory:

```
charmcraft.yaml
config.yaml
actions.yaml
metadata.yaml
requirements.yaml
.jujuignore
icon.svg
README.md
```

In order to exclude the `k8s` exclusive components from the `k8s-worker` charm, charmcraft will read the `worker/.jujuignore` file to determine what to leave out of the final charm.

### What's not
### What's shared

The shared portions of each charm are within `worker/k8s` (except for the above mentioned exclusions). This includes shared libraries from `worker/k8s/lib`, shared source from `worker/k8s/src`, shared python dependencies from `worker/k8s/requirements.txt`

Expand Down
15 changes: 15 additions & 0 deletions charms/worker/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ bases:
- name: ubuntu
channel: "24.04"
architectures: [amd64]
- build-on:
- name: ubuntu
channel: "20.04"
architectures: [arm64]
run-on:
- name: ubuntu
channel: "20.04"
architectures: [arm64]
- name: ubuntu
channel: "22.04"
architectures: [arm64]
- name: ubuntu
channel: "24.04"
architectures: [arm64]
config:
options:
labels:
Expand All @@ -59,6 +73,7 @@ parts:
plugin: charm
build-packages: [git]
charm-entrypoint: k8s/src/charm.py
charm-requirements: [k8s/requirements.txt]
Copy link
Contributor Author

@addyess addyess May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worker charm can specify it's requirements.txt so that the python reqs can be installed with no-binary

promote:
# move paths out of ./k8s to ./ since
# charmcraft assumes ./lib to be there
Expand Down
14 changes: 14 additions & 0 deletions charms/worker/k8s/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ bases:
- name: ubuntu
channel: "24.04"
architectures: [amd64]
- build-on:
- name: ubuntu
channel: "20.04"
architectures: [arm64]
run-on:
- name: ubuntu
channel: "20.04"
architectures: [arm64]
- name: ubuntu
channel: "22.04"
architectures: [arm64]
- name: ubuntu
channel: "24.04"
architectures: [arm64]
config:
options:
annotations:
Expand Down
2 changes: 2 additions & 0 deletions charms/worker/k8s/templates/snap_installation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ amd64:
- name: k8s
install-type: store
channel: edge
classic: true
arm64:
- name: k8s
install-type: store
channel: edge
classic: true
2 changes: 0 additions & 2 deletions charms/worker/requirements.txt

This file was deleted.

81 changes: 70 additions & 11 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import contextlib
import json
import logging
import re
import shlex
from dataclasses import dataclass, field
from itertools import chain
Expand Down Expand Up @@ -82,14 +83,33 @@ class Charm:

Attrs:
ops_test: Instance of the pytest-operator plugin
arch: Cloud Architecture
path: Path to the charm file
metadata: Charm's metadata
app_name: Preferred name of the juju application
"""

ops_test: OpsTest
arch: str
path: Path
_charmfile: Optional[Path] = None
_URL_RE = re.compile(r"ch:(?P<arch>\w+)/(?P<series>\w+)/(?P<charm>.+)")

@staticmethod
def craft_url(charm: str, series: str, arch: str) -> str:
"""Craft a charm URL.

Args:
charm: Charm name
series: Cloud series
arch: Cloud architecture

Returns:
string: URL to the charm
"""
if m := Charm._URL_RE.match(charm):
charm = m.group("charm")
return f"ch:{arch}/{series}/{charm}"

@property
def metadata(self) -> dict:
Expand Down Expand Up @@ -122,7 +142,8 @@ async def resolve(self, charm_files: List[str]) -> Path:
Path().glob(charm_name), # Look in top-level path
self.path.glob(charm_name), # Look in charm-level path
)
self._charmfile, *_ = filter(lambda s: s.name.startswith(header), potentials)
arch_choices = filter(lambda s: self.arch in str(s), potentials)
self._charmfile, *_ = filter(lambda s: s.name.startswith(header), arch_choices)
log.info("For %s found charmfile %s", self.app_name, self._charmfile)
except ValueError:
log.warning("No pre-built charm is available, let's build it")
Expand All @@ -142,19 +163,25 @@ class Bundle:
ops_test: Instance of the pytest-operator plugin
path: Path to the bundle file
content: Loaded content from the path
arch: Cloud Architecture
render: Path to a rendered bundle
applications: Mapping of applications in the bundle.
"""

ops_test: OpsTest
path: Path
arch: str
_content: Mapping = field(default_factory=dict)

@property
def content(self) -> Mapping:
"""Yaml content of the bundle loaded into a dict"""
if not self._content:
self._content = yaml.safe_load(self.path.read_bytes())
loaded = yaml.safe_load(self.path.read_bytes())
series = loaded.get("series", "focal")
for app in loaded["applications"].values():
app["charm"] = Charm.craft_url(app["charm"], series=series, arch=self.arch)
self._content = loaded
return self._content

@property
Expand All @@ -165,6 +192,7 @@ def applications(self) -> Mapping[str, dict]:
@property
def render(self) -> Path:
"""Path to written bundle config to be deployed."""
self.add_constraints({"arch": self.arch})
target = self.ops_test.tmp_path / "bundles" / self.path.name
target.parent.mkdir(exist_ok=True, parents=True)
yaml.safe_dump(self.content, target.open("w"))
Expand Down Expand Up @@ -202,6 +230,25 @@ def add_constraints(self, constraints: Dict[str, str]):
app["constraints"] = " ".join(f"{k}={v}" for k, v in existing.items())


async def cloud_arch(ops_test: OpsTest) -> str:
"""Return current architecture of the selected controller

Args:
ops_test (OpsTest): ops_test plugin

Returns:
string describing current architecture of the underlying cloud
"""
assert ops_test.model, "Model must be present"
controller = await ops_test.model.get_controller()
controller_model = await controller.get_model("controller")
arch = set(
machine.safe_data["hardware-characteristics"]["arch"]
for machine in controller_model.machines.values()
)
return arch.pop()


async def cloud_type(ops_test: OpsTest) -> Tuple[str, bool]:
"""Return current cloud type of the selected controller

Expand Down Expand Up @@ -304,14 +351,26 @@ async def deploy_model(
log.fatal("Failed to determine model: model_name=%s", model_name)


def bundle_file(request) -> Path:
"""Fixture to get bundle file.

Args:
request: pytest request object

Returns:
path to test's bundle file
"""
_file = "test-bundle.yaml"
bundle_marker = request.node.get_closest_marker("bundle_file")
if bundle_marker:
_file = bundle_marker.args[0]
return Path(__file__).parent / "data" / _file


@pytest_asyncio.fixture(scope="module")
async def kubernetes_cluster(request: pytest.FixtureRequest, ops_test: OpsTest):
"""Deploy local kubernetes charms."""
bundle_file = "test-bundle.yaml"
bundle_marker = request.node.get_closest_marker("bundle_file")
if bundle_marker:
bundle_file = bundle_marker.args[0]
bundle_path = Path(__file__).parent / "data" / bundle_file
bundle_path = bundle_file(request)
model = "main"

with ops_test.model_context(model) as the_model:
Expand All @@ -321,13 +380,14 @@ async def kubernetes_cluster(request: pytest.FixtureRequest, ops_test: OpsTest):
return

log.info("Deploying cluster using %s bundle.", bundle_file)
arch = await cloud_arch(ops_test)

charm_path = ("worker/k8s", "worker")
charms = [Charm(ops_test, Path("charms") / p) for p in charm_path]
charms = [Charm(ops_test, arch, Path("charms") / p) for p in charm_path]
charm_files = await asyncio.gather(
*[charm.resolve(request.config.option.charm_files) for charm in charms]
)
bundle = Bundle(ops_test, bundle_path)
bundle = Bundle(ops_test, bundle_path, arch)
_type, _vms = await cloud_type(ops_test)
if _type == "lxd" and not _vms:
log.info("Drop lxd machine constraints")
Expand All @@ -337,7 +397,6 @@ async def kubernetes_cluster(request: pytest.FixtureRequest, ops_test: OpsTest):
bundle.add_constraints({"virt-type": "virtual-machine"})
if request.config.option.apply_proxy:
await cloud_proxied(ops_test)

for path, charm in zip(charm_files, charms):
bundle.switch(charm.app_name, path)
async with deploy_model(request, ops_test, model, bundle) as the_model:
Expand All @@ -353,7 +412,7 @@ async def grafana_agent(kubernetes_cluster: Model):
machine_series = juju.utils.get_version_series(data["base"].split("@")[1])

await kubernetes_cluster.deploy(
f"ch:{machine_arch}/{machine_series}/grafana-agent",
Charm.craft_url("grafana-agent", machine_series, machine_arch),
channel="stable",
series=machine_series,
)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ deps =
types-PyYAML
types-requests
-r{toxinidir}/test_requirements.txt
-r{toxinidir}/charms/worker/requirements.txt
-r{toxinidir}/charms/worker/k8s/requirements.txt
commands =
pydocstyle {[vars]src_path}
codespell {toxinidir} --skip {toxinidir}/.git --skip {toxinidir}/.tox \
Expand Down
Loading