Skip to content
Open
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
4 changes: 0 additions & 4 deletions .github/assets/testing/edge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ core:
channel: 2026.1/edge
config:
snap-channel: 2026.1/edge
ovn-central-k8s:
channel: 26.03/edge
ovn-relay-k8s:
channel: 26.03/edge
placement-k8s:
channel: 2026.1/edge
sunbeam-clusterd:
Expand Down
4 changes: 0 additions & 4 deletions .github/assets/testing/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ core:
channel: OS_CHARM
watcher-k8s:
channel: OS_CHARM
ovn-central-k8s:
channel: OVN_CHARM
ovn-relay-k8s:
channel: OVN_CHARM
placement-k8s:
channel: OS_CHARM
sunbeam-clusterd:
Expand Down
57 changes: 16 additions & 41 deletions cloud/etc/deploy-microovn/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,14 @@ resource "juju_application" "microovn_arm64" {
endpoint_bindings = var.endpoint_bindings
}

moved {
from = juju_application.sunbeam-ovn-proxy[0]
to = juju_application.sunbeam-ovn-proxy
}

resource "juju_application" "sunbeam-ovn-proxy" {
name = "sunbeam-ovn-proxy"
model_uuid = data.juju_model.machine_model.uuid
# Only deploy when microovn is the SDN provider
count = var.ovn-relay-offer-url == null ? 1 : 0
# Deploy on same machine as token distributor
machines = length(var.token_distributor_machine_ids) == 0 ? null : toset(var.token_distributor_machine_ids)
units = length(var.token_distributor_machine_ids) == 0 ? 1 : null
Expand Down Expand Up @@ -167,20 +170,6 @@ resource "juju_integration" "microovn-certs" {
}
}

resource "juju_integration" "microovn-ovsdb-cms" {
count = (var.ovn-relay-offer-url != null) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid

application {
name = juju_application.microovn.name
endpoint = "ovsdb-external"
}

application {
offer_url = var.ovn-relay-offer-url
}
}

resource "juju_integration" "microovn-openstack-network-agents" {
model_uuid = data.juju_model.machine_model.uuid

Expand Down Expand Up @@ -272,25 +261,10 @@ resource "juju_integration" "microovn_arm64_certs" {
}
}

resource "juju_integration" "microovn_arm64_ovsdb_cms" {
count = (var.ovn-relay-offer-url != null && length(local.microovn_arm64_machine_ids) > 0) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid

application {
name = juju_application.microovn_arm64[0].name
endpoint = "ovsdb-external"
}

application {
offer_url = var.ovn-relay-offer-url
}
}

resource "juju_integration" "microovn_arm64_to_ovn_proxy" {
count = (
length(local.microovn_arm64_machine_ids) > 0
&& length(local.microovn_machine_ids) == 0
&& length(juju_application.sunbeam-ovn-proxy.*.name) > 0
) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid

Expand All @@ -300,18 +274,15 @@ resource "juju_integration" "microovn_arm64_to_ovn_proxy" {
}

application {
name = juju_application.sunbeam-ovn-proxy[0].name
name = juju_application.sunbeam-ovn-proxy.name
endpoint = "ovsdb"
}
}

resource "juju_integration" "microovn-to-ovn-proxy" {
count = (
length(juju_application.sunbeam-ovn-proxy.*.name) > 0
&& (
length(local.microovn_machine_ids) > 0
|| length(local.microovn_arm64_machine_ids) == 0
)
length(local.microovn_machine_ids) > 0
|| length(local.microovn_arm64_machine_ids) == 0
) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid

Expand All @@ -321,15 +292,19 @@ resource "juju_integration" "microovn-to-ovn-proxy" {
}

application {
name = juju_application.sunbeam-ovn-proxy[0].name
name = juju_application.sunbeam-ovn-proxy.name
endpoint = "ovsdb"
}
}

moved {
from = juju_offer.ovsdb-cms[0]
to = juju_offer.ovsdb-cms
}

resource "juju_offer" "ovsdb-cms" {
count = length(juju_application.sunbeam-ovn-proxy.*.name) > 0 ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid
application_name = juju_application.sunbeam-ovn-proxy[0].name
application_name = juju_application.sunbeam-ovn-proxy.name
endpoints = ["ovsdb-cms"]
}

Expand All @@ -342,5 +317,5 @@ output "microovn-arm64-application-name" {
}

output "ovsdb-cms-offer" {
value = try(juju_offer.ovsdb-cms[0].url, null)
value = juju_offer.ovsdb-cms.url
}
7 changes: 0 additions & 7 deletions cloud/etc/deploy-microovn/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,3 @@ variable "ca-offer-url" {
type = string
default = null
}

# Mandatory relation, no defaults
variable "ovn-relay-offer-url" {
description = "Offer URL for ovn relay service"
type = string
default = null
}
21 changes: 2 additions & 19 deletions cloud/etc/deploy-openstack-hypervisor/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,11 @@ resource "juju_integration" "hypervisor-certs" {
}

moved {
from = juju_integration.hypervisor-ovn
to = juju_integration.hypervisor-ovn[0]
}

resource "juju_integration" "hypervisor-ovn" {
# Should be deployed if ovn-relay-offer-url set
count = (var.ovn-relay-offer-url != null) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid

application {
name = juju_application.openstack-hypervisor.name
endpoint = "ovsdb-cms"
}

application {
offer_url = var.ovn-relay-offer-url
}
from = juju_integration.hypervisor-ovn-proxy[0]
to = juju_integration.hypervisor-ovn-proxy
}

resource "juju_integration" "hypervisor-ovn-proxy" {
# Shouldn't be deployed if ovn-relay-offer-url is set
count = (var.ovn-relay-offer-url == null) ? 1 : 0
model_uuid = data.juju_model.machine_model.uuid
application {
name = juju_application.openstack-hypervisor.name
Expand Down
7 changes: 0 additions & 7 deletions cloud/etc/deploy-openstack-hypervisor/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,6 @@ variable "ca-offer-url" {
default = null
}

# Mandatory relation, no defaults
variable "ovn-relay-offer-url" {
description = "Offer URL for ovn relay service"
type = string
default = null
}

variable "ceilometer-offer-url" {
description = "Offer URL for openstack ceilometer"
type = string
Expand Down
4 changes: 0 additions & 4 deletions manifests/2026.1/beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ core:
channel: 2026.1/beta
config:
snap-channel: 2026.1/beta
ovn-central-k8s:
channel: 26.03/beta
ovn-relay-k8s:
channel: 26.03/beta
placement-k8s:
channel: 2026.1/beta
sunbeam-clusterd:
Expand Down
4 changes: 0 additions & 4 deletions manifests/2026.1/candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ core:
channel: 2026.1/candidate
config:
snap-channel: 2026.1/candidate
ovn-central-k8s:
channel: 26.03/candidate
ovn-relay-k8s:
channel: 26.03/candidate
placement-k8s:
channel: 2026.1/candidate
sunbeam-clusterd:
Expand Down
4 changes: 0 additions & 4 deletions manifests/2026.1/edge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ core:
channel: 2026.1/edge
config:
snap-channel: 2026.1/edge
ovn-central-k8s:
channel: 26.03/edge
ovn-relay-k8s:
channel: 26.03/edge
placement-k8s:
channel: 2026.1/edge
sunbeam-clusterd:
Expand Down
4 changes: 0 additions & 4 deletions manifests/2026.1/stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ core:
channel: 2026.1/stable
config:
snap-channel: 2026.1/stable
ovn-central-k8s:
channel: 26.03/stable
ovn-relay-k8s:
channel: 26.03/stable
placement-k8s:
channel: 2026.1/stable
sunbeam-clusterd:
Expand Down
104 changes: 14 additions & 90 deletions sunbeam-python/sunbeam/core/ovn.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,27 @@
# SPDX-FileCopyrightText: 2025 - Canonical Ltd
# SPDX-License-Identifier: Apache-2.0

import enum
from collections.abc import Iterable

import pydantic

from sunbeam.clusterd.client import Client
from sunbeam.core.common import Role
from sunbeam.core.deployment import Deployment
from sunbeam.core.juju import JujuHelper
from sunbeam.core.questions import load_answers, write_answers

CLUSTERD_CONFIG_KEY = "OvnConfig"
SNAP_PROVIDER_CONFIG_KEY = "ovn.provider"


class OvnProvider(enum.StrEnum):
OVN_K8S = "ovn-k8s"
MICROOVN = "microovn"


class OvnConfig(pydantic.BaseModel):
provider: OvnProvider | None = None


DEFAULT_PROVIDER = OvnProvider.OVN_K8S
DEFAULT_ARCHITECTURE = "amd64"
ARM64_ARCHITECTURE = "arm64"


def load_provider_config(client: Client) -> OvnConfig:
"""Load the OVN provider configuration from the cluster deployment answers.

:param client: the Sunbeam client
:return: the OVN provider configuration
"""
answers = load_answers(client, CLUSTERD_CONFIG_KEY)
return OvnConfig.model_validate(answers)


def write_provider_config(client: Client, config: OvnConfig) -> None:
"""Write the OVN provider configuration to the cluster deployment answers.

:param client: the Sunbeam client
:param config: the OVN provider configuration
"""
write_answers(client, CLUSTERD_CONFIG_KEY, config.model_dump())


class OvnManager:
def __init__(self, client: Client):
self.client = client

def get_provider(self) -> OvnProvider:
"""Get the OVN provider from the configuration."""
config = load_provider_config(self.client)
if config.provider is None:
return DEFAULT_PROVIDER
return config.provider

def get_roles_for_microovn(self) -> set[Role]:
"""Get list of roles where microovn is necessary.

:return: set of roles
"""
provider = self.get_provider()
roles = {Role.NETWORK}
if provider == OvnProvider.MICROOVN:
roles |= {Role.COMPUTE, Role.CONTROL}
return roles
return {Role.CONTROL, Role.COMPUTE, Role.NETWORK}

def is_microovn_necessary(self, roles: Iterable[Role]) -> bool:
"""Check if microovn is necessary for the given roles.
Expand All @@ -79,19 +31,6 @@ def is_microovn_necessary(self, roles: Iterable[Role]) -> bool:
"""
return len(self.get_roles_for_microovn().intersection(roles)) > 0

def is_network_agent_dataplane_node(self, roles: Iterable[Role]) -> bool:
"""Check whether the node is a network agent dataplane node.

:param roles: iterable of roles
:return: True if the role is managed by openstack-network-agents,
False otherwise
"""
provider = self.get_provider()
dataplane_roles = {Role.NETWORK}
if provider == OvnProvider.MICROOVN:
dataplane_roles.add(Role.COMPUTE)
return len(dataplane_roles.intersection(roles)) > 0

def is_microovn_necessary_maas(
self, nb_network: int, nb_compute: int, nb_control: int
) -> bool:
Expand All @@ -102,30 +41,19 @@ def is_microovn_necessary_maas(
:param nb_control: number of control nodes
:return: True if microovn is necessary, False otherwise
"""
provider = self.get_provider()
if provider == OvnProvider.MICROOVN:
return (nb_network + nb_compute + nb_control) > 0
else:
return nb_network > 0
return (nb_network + nb_compute + nb_control) > 0

def _list_microovn_nodes(self) -> list[dict]:
"""Collect cluster nodes that should run MicroOVN."""
nodes = self.client.cluster.list_nodes_by_role("network")
if self.get_provider() == OvnProvider.MICROOVN:
nodes += self.client.cluster.list_nodes_by_role("compute")
nodes += self.client.cluster.list_nodes_by_role("control")
return nodes

def get_token_distributor_machines(
self, provider: OvnProvider | None = None
) -> list[str]:
"""Get machine IDs for MicroOVN helper applications."""
provider = provider or self.get_provider()
roles = [Role.NETWORK]
if provider == OvnProvider.MICROOVN:
roles = [Role.CONTROL, Role.COMPUTE, Role.NETWORK]
return (
self.client.cluster.list_nodes_by_role("control")
+ self.client.cluster.list_nodes_by_role("compute")
+ self.client.cluster.list_nodes_by_role("network")
)

for role in roles:
def get_token_distributor_machines(self) -> list[str]:
"""Get machine IDs for MicroOVN helper applications."""
for role in (Role.CONTROL, Role.COMPUTE, Role.NETWORK):
machine_ids: set[str] = set()
for node in self.client.cluster.list_nodes_by_role(role.name.lower()):
machineid = node.get("machineid")
Expand Down Expand Up @@ -176,11 +104,7 @@ def get_control_plane_tfvars(

:return: dict of Terraform variables
"""
provider = self.get_provider()
tfvars = {}
if provider == OvnProvider.MICROOVN:
model_name = jhelper.get_model_name_with_owner(
deployment.openstack_machines_model
)
tfvars["external-ovsdb-cms-offer-url"] = model_name + ".sunbeam-ovn-proxy"
return tfvars
model_name = jhelper.get_model_name_with_owner(
deployment.openstack_machines_model
)
return {"external-ovsdb-cms-offer-url": model_name + ".sunbeam-ovn-proxy"}
Loading
Loading