Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5f6fd15
feat: arm64 arch
yanksyoon Nov 28, 2023
6702035
docs: arm64 arch
yanksyoon Nov 28, 2023
c20e1fa
chore: remove arm arch support
yanksyoon Nov 30, 2023
1350874
feat: yq arm bin support
yanksyoon Nov 30, 2023
10b84ec
test: add charm state w/ arch unit tests
yanksyoon Nov 30, 2023
b751694
Merge branch 'main' into feat/arm64
yanksyoon Nov 30, 2023
8679566
fix: add arch to runner creation
yanksyoon Nov 30, 2023
40ddebb
tests: fix
yanksyoon Nov 30, 2023
313f90a
Merge branch 'main' into feat/arm64
yanksyoon Nov 30, 2023
3d9c8b7
chore: refactor runner args
yanksyoon Dec 3, 2023
617fc1e
Merge branch 'main' of https://github.com/canonical/github-runner-ope…
yanksyoon Dec 3, 2023
8a91191
chore: resolve merge conflicts
yanksyoon Dec 3, 2023
aa65bf6
docs: add arm64 docs
yanksyoon Dec 3, 2023
815673e
docs: remove extra newline
yanksyoon Dec 3, 2023
e6fd776
chore: update dostring
yanksyoon Dec 3, 2023
99f7ae4
docs: update deployment command
yanksyoon Dec 3, 2023
9286ab0
docs: remove reference to untested infra
yanksyoon Dec 4, 2023
dbf4106
docs: grammar fix
yanksyoon Dec 4, 2023
0fda576
docs: use latest arm supported instance
yanksyoon Dec 4, 2023
16fd125
chore: use set
yanksyoon Dec 4, 2023
81ad4b4
chore: test different archs for runner bin download
yanksyoon Dec 4, 2023
7a17cdb
docs: remove reference to lxd problems
yanksyoon Dec 4, 2023
41a5bfa
docs: arm64 nested v explanations
yanksyoon Dec 4, 2023
f78315b
chore: update set syntax
yanksyoon Dec 4, 2023
db8490c
Merge branch 'main' into feat/arm64
yanksyoon Dec 4, 2023
119ad18
Update test_runner_manager.py
yanksyoon Dec 4, 2023
a4105e3
docs: update explanation to arm64 nested v support
yanksyoon Dec 5, 2023
ac10133
docs: fix typo
yanksyoon Dec 5, 2023
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
25 changes: 25 additions & 0 deletions docs/explanation/arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ARM64

### Nested virtualiztion support

GitHub runner uses [LXD](https://github.com/canonical/lxd) to create a virtual machine to run the
GitHub runner's binary. Some versions of the ARM64 architecture do not support nested
virtualizations.

Furthermore LXD by default uses QEMU with KVM acceleration options and such behavior cannot
overridden. When run on a machine without KVM support,
the following error will occur:
```
Error: Failed instance creation: Failed creating instance record: Instance type "virtual-machine"
is not supported on this server: KVM support is missing (no /dev/kvm)
```

The kernel for nested virtualizations have not yet landed upstream.

The current progress of ARM64 nested virtualization support requires a few underlying technologies
to be further developed.
- [Hardware: supported](https://developer.arm.com/documentation/102142/0100/Nested-virtualization)
- Kernel (KVM): upstream not yet ready
- Userspace programs (e.g. qemu): unsupported.

Therefore, it is currently necessary that the charm is deployed on a bare metal instance.
30 changes: 30 additions & 0 deletions docs/how-to/deploy-on-arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# How to deploy on ARM64

The charm supports deployment on ARM64 machines. However, it should be noted that the ARM64
deployment currently only supports ARM64 bare-metal machines due to the limitations of
[nested virtualization on ARM64](https://developer.arm.com/documentation/102142/0100/Nested-virtualization).

The following uses AWS's [m7g.metal](https://aws.amazon.com/blogs/aws/now-available-bare-metal-arm-based-ec2-instances/)
instance to deploy the GitHub Runner on ARM64 architecture.

### Prerequisites
1. Juju with ARM64 bare metal instance availability.
- On AWS: `juju bootstrap aws <desired-controller-name>`
2. GitHub [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
3. Repository to register the GitHub runners.

### Deployment steps

Run the following command:
```shell
juju deploy github-runner \
--constraints="instance-type=a1.metal arch=arm64" \
--config token=<PERSONAL-ACCESS-TOKEN> --config path=<OWNER/REPO>
```

The units may take several minutes to settle. Furthermore, due to charm restart (kernel update),
the Unit may become lost for a few minutes. This is an expected behavior and the unit should
automatically re-register itself onto the Juju controller after a successful reboot.

Goto the repository > Settings (tab) > Actions (left menu dropdown) > Runners and verify that the
runner has successfully registered and is online.
6 changes: 6 additions & 0 deletions docs/reference/arm64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# ARM64

### AWS bare metal instances

Use any of the [ARM64 metal instances](https://aws.amazon.com/ec2/instance-types/) to provide juju
with ARM64 bare metal instances. Some of the examples include: a1.metal, m7g.metal.
51 changes: 47 additions & 4 deletions src-docs/charm_state.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ State of the Charm.

**Global Variables**
---------------
- **ARCHITECTURES_ARM64**
- **ARCHITECTURES_X86**
- **COS_AGENT_INTEGRATION_NAME**


---

## <kbd>class</kbd> `ARCH`
Supported system architectures.





---

## <kbd>class</kbd> `CharmConfigInvalidError`
Expand All @@ -21,7 +32,7 @@ Raised when charm config is invalid.

- <b>`msg`</b>: Explanation of the error.

<a href="../src/charm_state.py#L27"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm_state.py#L40"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand Down Expand Up @@ -66,7 +77,7 @@ Return the aproxy address.

---

<a href="../src/charm_state.py#L85"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm_state.py#L98"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `check_fields`

Expand All @@ -78,7 +89,7 @@ Validate the proxy configuration.

---

<a href="../src/charm_state.py#L51"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm_state.py#L64"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `from_charm`

Expand Down Expand Up @@ -111,13 +122,14 @@ The charm state.

- <b>`is_metrics_logging_available`</b>: Whether the charm is able to issue metrics.
- <b>`proxy_config`</b>: Whether aproxy should be used.
- <b>`arch`</b>: The underlying compute architecture, i.e. x64, amd64
Comment thread
yanksyoon marked this conversation as resolved.




---

<a href="../src/charm_state.py#L109"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm_state.py#L159"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `from_charm`

Expand All @@ -139,3 +151,34 @@ Initialize the state from charm.
Current state of the charm.


---

## <kbd>class</kbd> `UnsupportedArchitectureError`
Raised when given machine charm architecture is unsupported.



**Attributes:**

- <b>`arch`</b>: The current machine architecture.

<a href="../src/charm_state.py#L117"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

```python
__init__(arch: str) → None
```

Initialize a new instance of the CharmConfigInvalidError exception.



**Args:**

- <b>`arch`</b>: The current machine architecture.





37 changes: 28 additions & 9 deletions src-docs/runner.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,37 @@ The `Runner` class stores the information on the runners and manages the lifecyc

The `RunnerManager` class from `runner_manager.py` creates and manages a collection of `Runner` instances.

**Global Variables**
---------------
- **YQ_BIN_URL_AMD64**
- **YQ_BIN_URL_ARM64**


---

## <kbd>class</kbd> `CreateRunnerConfig`
The configuration values for creating a single runner instance.



**Args:**

- <b>`image`</b>: Name of the image to launch the LXD instance with.
- <b>`resources`</b>: Resource setting for the LXD instance.
- <b>`binary_path`</b>: Path to the runner binary.
- <b>`registration_token`</b>: Token for registering the runner on GitHub.
- <b>`arch`</b>: Current machine architecture.





---

## <kbd>class</kbd> `Runner`
Single instance of GitHub self-hosted runner.

<a href="../src/runner.py#L78"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner.py#L101"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand Down Expand Up @@ -44,17 +67,12 @@ Construct the runner instance.

---

<a href="../src/runner.py#L108"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner.py#L131"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `create`

```python
create(
image: str,
resources: VirtualMachineResources,
binary_path: Path,
registration_token: str
)
create(config: CreateRunnerConfig)
```

Create the runner instance on LXD and register it on GitHub.
Expand All @@ -67,6 +85,7 @@ Create the runner instance on LXD and register it on GitHub.
- <b>`resources`</b>: Resource setting for the LXD instance.
- <b>`binary_path`</b>: Path to the runner binary.
- <b>`registration_token`</b>: Token for registering the runner on GitHub.
- <b>`arch`</b>: Current machine architecture.



Expand All @@ -76,7 +95,7 @@ Create the runner instance on LXD and register it on GitHub.

---

<a href="../src/runner.py#L151"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner.py#L171"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `remove`

Expand Down
14 changes: 5 additions & 9 deletions src-docs/runner_manager.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Check if runner binary exists.

---

<a href="../src/runner_manager.py#L541"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner_manager.py#L543"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `flush`

Expand All @@ -96,7 +96,7 @@ Remove existing runners.

---

<a href="../src/runner_manager.py#L273"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner_manager.py#L269"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `get_github_info`

Expand All @@ -118,10 +118,7 @@ Get information on the runners from GitHub.
### <kbd>function</kbd> `get_latest_runner_bin_url`

```python
get_latest_runner_bin_url(
os_name: str = 'linux',
arch_name: str = 'x64'
) → RunnerApplication
get_latest_runner_bin_url(os_name: str = 'linux') → RunnerApplication
```

Get the URL for the latest runner binary.
Expand All @@ -133,7 +130,6 @@ The runner binary URL changes when a new version is available.
**Args:**

- <b>`os_name`</b>: Name of operating system.
- <b>`arch_name`</b>: Name of architecture.



Expand All @@ -142,7 +138,7 @@ The runner binary URL changes when a new version is available.

---

<a href="../src/runner_manager.py#L472"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/runner_manager.py#L474"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `reconcile`

Expand All @@ -166,7 +162,7 @@ Bring runners in line with target.

---

<a href="../src/utilities.py#L206"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/utilities.py#L202"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `update_runner_bin`

Expand Down
58 changes: 58 additions & 0 deletions src/charm_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import dataclasses
import logging
import platform
from enum import Enum
from typing import Optional

from ops import CharmBase
Expand All @@ -14,6 +16,18 @@

logger = logging.getLogger(__name__)

ARCHITECTURES_ARM64 = {"aarch64", "arm64"}

ARCHITECTURES_X86 = {"x86_64"}


class ARCH(str, Enum):
"""Supported system architectures."""

ARM64 = "arm64"
X64 = "x64"


COS_AGENT_INTEGRATION_NAME = "cos-agent"


Expand Down Expand Up @@ -94,17 +108,54 @@ def check_fields(cls, values: dict) -> dict:
return values


class UnsupportedArchitectureError(Exception):
"""Raised when given machine charm architecture is unsupported.

Attributes:
arch: The current machine architecture.
"""

def __init__(self, arch: str) -> None:
"""Initialize a new instance of the CharmConfigInvalidError exception.

Args:
arch: The current machine architecture.
"""
self.arch = arch


def _get_supported_arch() -> ARCH:
"""Get current machine architecture.

Raises:
UnsupportedArchitectureError: if the current architecture is unsupported.

Returns:
Arch: Current machine architecture.
"""
arch = platform.machine()
match arch:
case arch if arch in ARCHITECTURES_ARM64:
return ARCH.ARM64
case arch if arch in ARCHITECTURES_X86:
return ARCH.X64
case _:
raise UnsupportedArchitectureError(arch=arch)


@dataclasses.dataclass(frozen=True)
class State:
"""The charm state.

Attributes:
is_metrics_logging_available: Whether the charm is able to issue metrics.
proxy_config: Whether aproxy should be used.
arch: The underlying compute architecture, i.e. x86_64, amd64, arm64/aarch64.
"""

is_metrics_logging_available: bool
proxy_config: ProxyConfig
arch: ARCH

@classmethod
def from_charm(cls, charm: CharmBase) -> "State":
Expand All @@ -122,7 +173,14 @@ def from_charm(cls, charm: CharmBase) -> "State":
logger.error("Invalid proxy config: %s", exc)
raise CharmConfigInvalidError("Invalid proxy configuration") from exc

try:
arch = _get_supported_arch()
except UnsupportedArchitectureError as exc:
logger.error("Unsupported architecture: %s", exc.arch)
raise CharmConfigInvalidError(f"Unsupported architecture {exc.arch}") from exc

return cls(
is_metrics_logging_available=bool(charm.model.relations[COS_AGENT_INTEGRATION_NAME]),
proxy_config=proxy_config,
arch=arch,
)
Loading