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
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' )
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)
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()
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)
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)