Example #1
0
class NodeGrains:
    fqdn: str = None
    host: str = None
    ipv4: List = attr.Factory(list)
    fqdns: List = attr.Factory(list)
    not_used: Dict = attr.Factory(dict)

    @classmethod
    def from_grains(cls, **kwargs):
        # Assumption: 'not_used' doesn't appear in grains
        not_used = {
            k: kwargs.pop(k)
            for k in list(kwargs) if k not in attr.fields_dict(cls)
        }
        return cls(**kwargs, not_used=not_used)

    @property
    def addrs(self):
        res = []
        for _attr in ('host', 'fqdn', 'fqdns', 'ipv4'):
            v = getattr(self, _attr)
            if v:
                if type(v) is list:
                    res.extend(v)
                else:  # str is expected
                    res.append(v)
        return list(set(res))
 class SomeClass(salt.SaltArgsMixin):
     fun_args: Tuple = attr.ib(converter=lambda v: () if v is None else v,
                               default=None)
     fun_kwargs: Dict = attr.ib(converter=lambda v: {} if v is None else v,
                                default=None)
     kw: Dict = attr.Factory(dict)
     secure: bool = False
Example #3
0
class SaltMasterParams(ResourceParams):
    lvm2_first: bool = attr_ib(
        cli_spec='salt_master/lvm2_first', default=False
    )
    updated_keys: List[str] = attr_ib(
        cli_spec='salt_master/onchanges', default=attr.Factory(list)
    )
    onchanges: str = attr_ib(
        cli_spec='salt_master/onchanges', default='restart'
    )
Example #4
0
class ConsulParams(UpgradeParams, VendorParamsMixin):
    server: bool = attr_ib(cli_spec='consul/server', default=True)
    bind_addr: IPv4Address = attr_ib('ipv4',
                                     cli_spec='consul/bind_addr',
                                     default='0.0.0.0')
    bootstrap_expect: Optional[int] = attr_ib(
        cli_spec='consul/bootstrap_expect',
        default=None,
        converter=attr.converters.optional(int),
        # TODO check positives only
        validator=attr.validators.optional(attr.validators.instance_of(int)))
    # TODO List[ip4, domain] ???
    retry_join: List[str] = attr_ib(cli_spec='consul/retry_join',
                                    default=attr.Factory(list))
    version: Union[str, Version] = attr_ib('version',
                                           cli_spec='consul/version',
                                           default=None,
                                           validator=attr.validators.optional(
                                               attr_gen.validator__version))
    service: bool = attr_ib(init=False, default=True)
Example #5
0
class ResourceSLS(ResourceTransition):  # XXX ??? inheritance
    """Base class for Salt state formulas that implement transitions."""
    _fileroot_prefix: ClassVar[Optional[Union[str, Path]]] = None
    _base_sls: ClassVar[Optional[Union[str, Path]]] = None
    sls: ClassVar[Optional[str]] = None

    client: SaltClientBase
    pillar_inline: Dict = attr.Factory(functools.partial(defaultdict, dict))

    _pillar: Optional[dict] = attr.ib(init=False, default=None)

    @property
    def fileroot_prefix(self):
        # XXX not the best place for that logic
        #     but __attrs_post_init__ might be not
        #     called from child classes in case of override
        res = Path(self._fileroot_prefix or '')
        if res.is_absolute():
            raise ValueError("fileroot_prefix should be relative: '{res}'")
        return res

    @property
    def base_sls(self):
        # XXX not the best place for that logic
        #     but __attrs_post_init__ might be not
        #     called from child classes in case of override
        res = Path(self._base_sls or '')
        if res.is_absolute():
            raise ValueError("base_sls should be relative: '{res}'")
        return res

    @property
    def resource_type(self) -> config.CortxResourceT:
        return self.state_t.resource_t.resource_t_id

    @property
    def resource_name(self) -> str:
        return self.resource_type.value

    @property
    def targets_list(self):
        return list(self.pillar)

    @property
    def pillar(self):
        # XXX uses the pillar only
        if self._pillar is None:
            rc_key = PillarKey(self.resource_name)

            pillar = PillarResolverNew(
                targets=self.targets, client=self.client
            ).get((rc_key,))

            self._pillar = {
                target: (
                    _pillar[rc_key]
                    if _pillar[rc_key] and _pillar[rc_key] is not values.MISSED
                    else {}
                ) for target, _pillar in pillar.items()
            }
        return self._pillar

    @property
    def is_vendored(self) -> bool:
        vendored = {
            pillar.get('vendored', False)
            for pillar in self.pillar.values()
        }
        if len(vendored) != 1:
            raise errors.ProvisionerRuntimeError(
                f"Mixed {self.resource_name} vendored setup"
                f" detected for targets '{self.targets}'"
            )
        return list(vendored)[0]

    def pillar_set(
        self, pillar: Dict, expand: bool = True,
        fpath=None, targets=None
    ):
        if targets is None:
            targets = self.targets
        else:
            # TODO verify that target is a single minion
            #      which is part (or same) as self.targets
            pass
        return self.client.pillar_set({
            self.resource_name: pillar
        }, expand=expand, fpath=fpath, targets=targets)

    def set_vendored(self, vendored: bool):
        self.pillar_set(dict(vendored=vendored))

    def rc_r_path(self, path: Union[str, Path]):
        # FIXME hard code
        return (
            self.fileroot_prefix / self.base_sls / 'files' / path
        )

    def fileroot_path(self, path: Union[str, Path]):
        return self.client.fileroot_path.path(self.rc_r_path(path))

    @property
    def fileroot(self):
        return FileRoot(
            self.client.fileroot_path,
            local_client=FileRoot.def_local_client_t(
                c_path=self.client.c_path
            )
        )

    def fileroot_read(
        self,
        r_path: Union[str, Path],
        text: bool = True,
        yaml: bool = False
    ):
        if yaml:
            return self.fileroot.read_yaml(self.rc_r_path(r_path))
        else:
            return self.fileroot.read(self.rc_r_path(r_path), text=text)

    def fileroot_write(
        self,
        r_path: Union[str, Path],
        data: Any,
        text: bool = True,
        yaml: bool = False
    ):
        if yaml:
            return self.fileroot.write_yaml(self.rc_r_path(r_path), data)
        else:
            return self.fileroot.write(
                self.rc_r_path(r_path), data, text=text
            )

    def fileroot_copy(
        self, source: Union[str, Path], r_dest: Union[str, Path],
    ):
        return self.fileroot.copy(source, self.rc_r_path(r_dest))

    def setup_roots(self):
        logger.info(
            f"Preparing '{self.resource_name}' pillar and file roots for"
            f" '{self.state.name}' on targets: {self.targets}"
        )

        if self.fileroot_prefix:
            self.pillar_inline['inline']['fileroot_prefix'] = (
                f"{self.fileroot_prefix}/"
            )

    def _run(self):
        res = []
        # XXX possibly a divergence with a design
        # some SLS may just shifts root without any states appliance
        if self.sls:
            fun_kwargs = {}
            # TODO DOC how to pass inline pillar
            if self.pillar_inline:
                fun_kwargs['pillar'] = self.pillar_inline

            slss = (
                self.sls if isinstance(self.sls, (List, Tuple)) else [self.sls]
            )

            if self.is_vendored:
                slss = [f'{VENDOR_SLS_PREFIX}.{sls}' for sls in slss]

            base_sls_path = self.fileroot_prefix / self.base_sls
            slss = ['.'.join((base_sls_path / sls).parts) for sls in slss]

            kwargs = dict(fun_kwargs=fun_kwargs, targets=self.targets)

            # if isinstance(self.targets, (list, tuple)):
            #     kwargs['targets'] = '|'.join(self.targets)
            #     kwargs['tgt_type'] = 'pcre'

            for sls in slss:
                _res = self.client.state_apply(sls, **kwargs)
                res.append(_res)

        return res

    def run(self):
        self.setup_roots()
        return self._run()
Example #6
0
class Container(Remote):
    name = attr.ib()
    image = attr.ib()
    hostname = attr.ib(default=None)
    container = attr.ib(init=False, default=None)
    specific = attr.ib(default=attr.Factory(dict))
    volumes = attr.ib(
        init=False,
        default=attr.Factory(
            lambda:
            {'/sys/fs/cgroup': {
                'bind': '/sys/fs/cgroup',
                'mode': 'ro'
            }}))

    client = docker.from_env()

    def __attrs_post_init__(self):
        if self.hostname is None:
            self.hostname = safe_hostname(self.name)

        self.volumes.update(self.specific.pop('volumes', {}))

    def run(self):
        if self.container is not None:
            raise RuntimeError("already running")  # some API error

        try:
            self.container = self.client.containers.run(
                self.image,
                name=self.name,
                # it's safer to not exceed 64 chars on Unix systems
                # for the hostname
                # (real limit might be get using `getconf HOST_NAME_MAX`)
                hostname=self.hostname,
                detach=True,
                tty=True,
                # network=network_name,
                volumes=self.volumes,
                # security_opt=['seccomp=unconfined'],
                tmpfs={
                    '/run': '',
                    '/run/lock': ''
                },
                ports={'22/tcp': None},
                **self.specific)
        except Exception as exc:
            self.destroy()
            if isinstance(exc, docker.errors.APIError):
                if 'is already in use' in str(exc):
                    # TODO ? try to remove is it's not running
                    raise RemoteAlreadyInUse(self.name)
            raise

    def destroy(self, ok_if_missed=True, force=True):
        self.container = None
        self.destroy_by_name(self.name, ok_if_missed=ok_if_missed, force=force)

    @staticmethod
    def destroy_by_name(container_name, ok_if_missed=True, force=True):
        try:
            container = Container.client.containers.get(container_name)
        except Exception:  # TODO less generic exception
            # container with the name might not be even created yet
            if ok_if_missed:
                logger.info(
                    'Container with name {} is missed'.format(container_name))
            else:
                raise
        else:
            container.remove(force=force)
Example #7
0
class Node:
    minion_id: str
    host: str
    user: str = 'root'
    port: int = 22

    grains: Optional[NodeGrains] = None
    # ordered by priority
    _ping_addrs: List = attr.Factory(list)

    @classmethod
    def from_spec(cls, spec: str) -> 'Node':
        kwargs = {}

        parts = spec.split(':')
        kwargs['minion_id'] = parts[0]
        hostspec = parts[1]

        try:
            kwargs['port'] = parts[2]
        except IndexError:
            pass

        parts = hostspec.split('@')
        try:
            kwargs['user'] = parts[0]
            kwargs['host'] = parts[1]
        except IndexError:
            del kwargs['user']
            kwargs['host'] = parts[0]

        return cls(**kwargs)

    def __str__(self):  # noqa: D105
        """Information of Node in Cortx cluster."""
        return ('{}:{}@{}:{}'.format(self.minion_id, self.user, self.host,
                                     self.port))

    @property
    def addrs(self):
        return list(set([self.host] + self.grains.addrs))

    @property
    def ping_addrs(self):
        return self._ping_addrs

    @ping_addrs.setter
    def ping_addrs(self, addrs: Iterable):
        # TODO IMPROVE EOS-8473 more effective way to order
        #      w.g. use dict (it remembers the order) and set intersection
        priorities = [self.grains.fqdn] + self.grains.fqdns + [
            self.host, self.grains.host
        ] + self.grains.ipv4

        self._ping_addrs[:] = []
        for addr in priorities:
            if addr in addrs and (addr not in self._ping_addrs):
                self._ping_addrs.append(addr)

        for addr in addrs:
            if addr not in self._ping_addrs:
                self._ping_addrs.append(addr)