MicroOVN Charm

mj ponsonby Publisher

Platform:

Ubuntu
24.04
Channel Revision Published Runs on
latest/stable 123 15 Jun 2026
Ubuntu 24.04
latest/stable 115 15 Jun 2026
Ubuntu 24.04
latest/stable 114 15 Jun 2026
Ubuntu 24.04
latest/stable 113 15 Jun 2026
Ubuntu 24.04
latest/stable 111 15 Jun 2026
Ubuntu 24.04
latest/candidate 160 15 Jun 2026
Ubuntu 24.04
latest/candidate 159 15 Jun 2026
Ubuntu 24.04
latest/candidate 158 15 Jun 2026
Ubuntu 24.04
latest/candidate 157 15 Jun 2026
Ubuntu 24.04
latest/candidate 156 15 Jun 2026
Ubuntu 24.04
latest/edge 160 11 May 2026
Ubuntu 24.04
latest/edge 159 11 May 2026
Ubuntu 24.04
latest/edge 158 11 May 2026
Ubuntu 24.04
latest/edge 157 11 May 2026
Ubuntu 24.04
latest/edge 156 11 May 2026
Ubuntu 24.04
24.03/stable 152 15 Jun 2026
Ubuntu 24.04
24.03/stable 151 15 Jun 2026
Ubuntu 24.04
24.03/stable 148 15 Jun 2026
Ubuntu 24.04
24.03/stable 146 15 Jun 2026
Ubuntu 24.04
24.03/stable 40 05 Mar 2026
Ubuntu 24.04
24.03/candidate 154 15 Jun 2026
Ubuntu 24.04
24.03/candidate 148 22 Apr 2026
Ubuntu 24.04
24.03/candidate 146 22 Apr 2026
Ubuntu 24.04
24.03/candidate 151 22 Apr 2026
Ubuntu 24.04
24.03/candidate 152 22 Apr 2026
Ubuntu 24.04
24.03/edge 154 22 Apr 2026
Ubuntu 24.04
24.03/edge 151 22 Apr 2026
Ubuntu 24.04
24.03/edge 152 22 Apr 2026
Ubuntu 24.04
24.03/edge 148 22 Apr 2026
Ubuntu 24.04
24.03/edge 146 22 Apr 2026
Ubuntu 24.04
25.03/stable 153 15 Jun 2026
Ubuntu 24.04
25.03/stable 150 15 Jun 2026
Ubuntu 24.04
25.03/stable 149 15 Jun 2026
Ubuntu 24.04
25.03/stable 147 15 Jun 2026
Ubuntu 24.04
25.03/candidate 155 15 Jun 2026
Ubuntu 24.04
25.03/candidate 149 22 Apr 2026
Ubuntu 24.04
25.03/candidate 147 22 Apr 2026
Ubuntu 24.04
25.03/candidate 153 22 Apr 2026
Ubuntu 24.04
25.03/candidate 150 22 Apr 2026
Ubuntu 24.04
25.03/edge 155 22 Apr 2026
Ubuntu 24.04
25.03/edge 153 22 Apr 2026
Ubuntu 24.04
25.03/edge 150 22 Apr 2026
Ubuntu 24.04
25.03/edge 149 22 Apr 2026
Ubuntu 24.04
25.03/edge 147 22 Apr 2026
Ubuntu 24.04
juju deploy microovn --channel 24.03/stable

"""
This is a charm library for the ovsdb interface. This contains two classes to
ease development of charms using this interface, one for provides and one for
requires.

The provides part of this takes the ovsdb connection strings from the microovn
environment file.

The requires part communicates with the relation data and gets these strings to
be easily returned and used for interaction with the ovsdb databases.
"""

# The unique Charmhub library identifier, never change it
LIBID = "599e7729d8cf403db3f6afb6d7c64c92"

# Increment this major API version when introducing breaking changes
LIBAPI = 0

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 1

ENV_FILE = "/var/snap/microovn/common/data/env/ovn.env"
CONNECT_ENV_NAME = "OVN_{0}_CONNECT"
CONNECT_STR_KEY = "db_{0}_connection_str"

from dataclasses import dataclass
import logging

from ops import CharmBase, StoredState, EventBase
from ops.framework import Object
from ops.model import (
    Application,
    ModelError,
    Relation,
    SecretNotFoundError,
    Unit,
)

logger = logging.getLogger(__name__)

@dataclass
class OVSDBConnectionString:
    """Class for storing the northbound and southbound connection strings"""
    nb: str
    sb: str


class OVSDBRequires(Object):
    _stored = StoredState()

    def __init__(
        self,
        charm: CharmBase,
        relation_name: str,
    ):
        super().__init__(charm, relation_name)
        self.charm = charm
        self.relation_name = relation_name

    """
    get_connection_strings takes the northbound and southbound connection strings
    from the relation data.

    it returns None on failure, and it returns a instance of
    OVSDBConnectionString containing both northbound and southbound strings
    """
    def get_connection_strings(self) -> OVSDBConnectionString:
        if not (relation := self.charm.model.get_relation(self.relation_name)):
            return None

        ovsdb_app_data = relation.data[relation.app]
        nb_connect = ovsdb_app_data.get(CONNECT_STR_KEY.format("nb"))
        sb_connect = ovsdb_app_data.get(CONNECT_STR_KEY.format("sb"))
        if nb_connect and sb_connect:
            return OVSDBConnectionString(nb=nb_connect, sb=sb_connect)
        else:
            return None

class OVSDBProvides(Object):
    _stored = StoredState()

    def __init__(
        self,
        charm: CharmBase,
        relation_name: str,
    ):
        super().__init__(charm, relation_name)
        self.charm = charm
        self.relation_name = relation_name
        self.framework.observe(
            self.charm.on[relation_name].relation_changed,
            self._on_ovsdb_relation_changed,
        )
        self.framework.observe(
            self.charm.on[relation_name].relation_created,
            self._on_ovsdb_relation_changed,
        )

    def _on_ovsdb_relation_changed(self, _: EventBase):
        self.update_relation_data()

    def update_relation_data(self):
        if not (self.charm.unit.is_leader() and self.charm._stored.in_cluster):
            return

        if not (relation := self.charm.model.get_relation(self.relation_name)):
            return

        connect_str = self.get_connection_strings()
        if connect_str:
            relation.data[self.charm.app][CONNECT_STR_KEY.format("nb")] = \
                connect_str.nb
            relation.data[self.charm.app][CONNECT_STR_KEY.format("sb")] = \
                connect_str.sb
            logger.info("connection strings updated")

    """
    get_connection_strings takes the northbound and southbound connection strings
    from the relation data.

    it returns None on failure, and it returns a instance of
    OVSDBConnectionString containing both northbound and southbound strings
    """
    def get_connection_strings(self) -> OVSDBConnectionString:
        nb_connect = None
        sb_connect = None
        try:
            with open(ENV_FILE, "r") as f:
                for line in f:
                    line = line.strip()
                    if line.startswith(CONNECT_ENV_NAME.format("NB")):
                        nb_connect = line.split("=", 1)[1].strip('"')
                    if line.startswith(CONNECT_ENV_NAME.format("SB")):
                        sb_connect = line.split("=", 1)[1].strip('"')
        except FileNotFoundError:
            logger.error(
                "OVN env file not found, is this unit in the microovn cluster?")
            raise FileNotFoundError("{0} not found".format(ENV_FILE))

        if nb_connect and sb_connect:
            return OVSDBConnectionString(nb=nb_connect, sb=sb_connect)
        else:
            return None