ok

Mini Shell

Direktori : /var/opt/nydus/ops/primordial/
Upload File :
Current File : //var/opt/nydus/ops/primordial/zkconfig.py

# -*- coding: utf-8 -*-

import re
import socket
from collections import deque
from typing import Any

from kazoo.client import KazooClient
from kazoo.exceptions import ConnectionLoss, NoNodeError, ZookeeperError
from kazoo.handlers.threading import KazooTimeoutError

from primordial.config import Config
from primordial.utils import retry_this


class ZKConfig(Config):
    """Config class that loads configuration options from a ZooKeeper cluster"""
    ROOT = '/config'  # type: str
    # The ? in the first capture is so the regex matching is non-greedy.
    # Passwords with a "=" in them can otherwise result in weird parsing
    CONFIG_LINE = re.compile(r'\s*(\S+?)\s*=\s*(\S+)\s*')

    def __init__(self, hosts: str) -> None:
        self._client = KazooClient(hosts=hosts, read_only=True)
        self._config = {}  # type: ignore
        self.load_config()

    def __enter__(self):
        """
        This turns this class/object into a context manager for connections to the zookeeper cluster.
        This starts up the connection and returns the client as the resource being managed.
        """
        # If start throws an error it will get propagated up the stack
        # The start method can throw a KazooTimeoutError error
        self._client.start()
        return self._client

    def __exit__(self, _exc_type, _exc_val, _exc_tb):
        """
        This method takes care of releasing the client connection to the zookeeper cluster.
        """
        try:
            self._client.stop()
            self._client.close()
        except Exception:  # pylint: disable=W0703
            # Squelch any exception we may encounter as part of connection closing
            pass

    @retry_this(on_ex_classes=(ConnectionLoss, KazooTimeoutError,
                               socket.error, NoNodeError, ZookeeperError))
    def load_config(self):
        """
        Load HFS config data including config at the various namespaces and flatten it out into
        a dict representation.
        """
        with self as zk_client:
            for path in self.enumerate(zk_client):
                self._config.update(self.get_config_at_path(zk_client, path))

    def enumerate(self, zk_client):
        """
        Generate all child paths, starting at a particular path. The starting path is also
        included in this enumeration.
        """
        paths_to_traverse = deque([self.ROOT])

        # Do a breadth first traversal of nodes
        while paths_to_traverse:
            path = paths_to_traverse.popleft()
            yield path
            for child in zk_client.get_children(path):
                child_path = '{}/{}'.format(path, child)
                paths_to_traverse.append(child_path)

    def get_config_at_path(self, zk_client, path):
        """
        Get the data (which is interpreted as config uploaded to that path/namespace) at a particular
        zookeeper path. Parse out the data looking for lines that look like key=value.
        Generate a dict out of this normalizing the key appropriately. Finally return back this dict.
        """
        config = {}
        # The path is going to have /config as the prefix, so when we split on '/' the first 2 items
        # are '' and 'config'. Hence, we drop the first 2 items in the code below.
        # Example 1: if path was '/config' then path_sections would be []
        # Example 2: if path was '/config/hhfs/nydus' then path_sections would be ['hhfs', 'nydus']
        path_sections = path.split('/')[2:]
        zk_value, _zk_stat = zk_client.get(path)
        stored_config = zk_value.decode("utf-8")

        for line in stored_config.split('\n'):
            match = self.CONFIG_LINE.match(line)
            if match:
                k, v = match.groups()
                config_key = '.'.join(path_sections + [k])
                config[config_key] = v

        return config

    def get(self, key: str, default: Any = None) -> Any:
        """
        Get the config option as a string

        :param key: config option name
        :param default: default value if no value exists in the config
        :return: option value
        """
        return self._config.get(key, default)

Zerion Mini Shell 1.0