# Copyright 2019 Nokia # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import six from crl.remotesession.remotesession import RemoteSession from crl.interactivesessions.shells.bashshell import BashShell from hostcli import HostCli from .envcreator import EnvCreator from .usermanager import UserManager from .metasingleton import MetaSingleton from .hosts import ( MgmtTarget, Master, NonMaster, Profiles, HostConfig) LOGGER = logging.getLogger(__name__) class ClusterError(Exception): pass @six.add_metaclass(MetaSingleton) class Cluster(object): """Singleton container for NCIR cluster hosts, their service profiles and access details. """ def __init__(self): self._mgmt_target = None self._hosts = None self._configprops_cache = None self._usermanager = UserManager(hostcli_factory=self.create_hostcli) self._envcreator = EnvCreator(remotesession_factory=self.create_remotesession, usermanager=self._usermanager) def clear_cache(self): """Clears configprops cache.""" self._configprops_cache = None def get_hosts(self): """Return Host containers of cluster. """ return [h for _, h in self._hosts.items()] def initialize(self, host, user, password): """Initialize Cluster with management VIP Arguments: host: Management VIP IP address user: Login username password: password for the user """ self._mgmt_target = MgmtTarget(host=host, user=user, password=password) self._hosts = {hc.name: self._create_host(hc) for hc in self._host_configs()} @staticmethod def set_profiles(master, worker): """Set *service_profile' names of *master* and *worker* nodes """ Profiles.set_profiles(master=master, worker=worker) def get_mgmt_shelldicts(self): """Return management VIP shelldicts for RemoteRunner. """ return [self._mgmt_target.asdict()] def get_host(self, hostname): """Get Host container for hostname.""" return self._hosts[hostname] def get_hosts_with_profiles(self, *service_profiles): """Get host names matching exactly *service_profiles*. """ return sorted(list(self._get_hosts_with_profs_gen(set(service_profiles)))) def get_hosts_containing(self, *service_profiles): """Get host names containing all *service_profiles*. """ return sorted(list(self._get_hosts_containing_gen(set(service_profiles)))) def create_remotesession(self): """Create initialized *RemoteSession* instance. """ r = RemoteSession() self.initialize_remotesession(r) return r def initialize_remotesession(self, remotesession): """Initialize :class:`crl.remotesession.remotesession.RemoteSession` instance with *shelldicts* and *name* of the hosts and sudo- via *set_runner_target*. Initialize *default* target with *get_mgmt_shelldicts* return value. The sudo- targets are terminals in target after executed roughly *sudo bash*. """ self._set_mgmt_targets(remotesession) for host in self.get_hosts(): remotesession.set_runner_target(shelldicts=host.shelldicts, name=host.name) remotesession.set_runner_target( shelldicts=self._get_sudoshelldicts(host.shelldicts), name='sudo-{}'.format(host.name)) remotesession.set_envcreator(self._envcreator) def _set_mgmt_targets(self, remotesession): remotesession.set_runner_target(self.get_mgmt_shelldicts()) remotesession.set_runner_target( shelldicts=self._get_sudoshelldicts(self.get_mgmt_shelldicts()), name='sudo-default') remotesession.set_target(host=self._mgmt_target.host, username=self._mgmt_target.user, password=self._mgmt_target.password, name='remotescript-default') @staticmethod def _get_sudoshelldicts(shelldicts): return shelldicts + [{'shellname': BashShell.__name__, 'cmd': 'sudo bash'}] def create_hostcli(self): """Create initialized *HostCli* instance. """ n = HostCli() self.initialize_hostcli(n) return n def initialize_hostcli(self, hostcli): """Initialize :class:`crl.hostcli.HostCli` instance (or :class:`crl.hostcli.OpenStack`) with :class:`crl.remotesession.remotesession.RemoteSession` instance initialized with :meth:`.initialize_remotesession`. """ hostcli.initialize(self.create_remotesession()) def create_user_with_roles(self, *roles): """Create according to roles list. Special roles: all_roles: all roles in the system no_roles: empty role list Return: UserRecord of created user """ return self._usermanager.create_user_with_roles(*roles) def delete_users(self): """Delete all users created by *Cluster*. """ self._usermanager.delete_users() def is_dpdk(self): """Return *True* if *dpdk* is used in provider network interfaces. More detail, *True* if and only if there is at least one host with network profile providing *ovs-dpdk* type interface. """ for _, h in self._hosts.items(): if h.is_dpdk: return True return False def get_hosts_with_dpdk(self): """Returns list of hosts where *dpdk* is in use. In more detail, return sorted list of host names in which there is at least one network profile containing *dpdk* type interface. """ return sorted([h.name for _, h in self._hosts.items() if h.is_dpdk]) def _get_hosts_with_profs_gen(self, service_profiles): def filt(host): mask = Profiles().profiles_mask return not (service_profiles ^ set(host.service_profiles)) & mask return self._filtered_hostnames(filt) def _get_hosts_containing_gen(self, service_profiles): def filt(host): return service_profiles.issubset(set(host.service_profiles)) return self._filtered_hostnames(filt) def _filtered_hostnames(self, filt): for h in self.get_hosts(): if filt(h): yield h.name @staticmethod def _create_host(host_config): LOGGER.debug('host_config: %s', host_config) master = Profiles().master return (Master(host_config) if master in host_config.service_profiles else NonMaster(host_config)) def _host_configs(self): LOGGER.debug('cloud_hosts: %s', self._cloud_hosts) for hostname, v in self._cloud_hosts.items(): yield HostConfig(name=hostname, network_domain=v['network_domain'], service_profiles=v['service_profiles'], networking=self._get_networking(hostname), mgmt_target=self._mgmt_target, is_dpdk=self._is_host_dpdk(v)) def _is_host_dpdk(self, host_prop): network_profiles = set(host_prop['network_profiles']) return bool(network_profiles.intersection(set(self._dpdk_profiles()))) def _dpdk_profiles(self): profiles = self._get_value_for_prop('cloud.network_profiles') for prof_n, prof_v in profiles.items(): ifaces = prof_v.get('provider_network_interfaces', {}) for _, iface_v in ifaces.items(): if iface_v['type'] == 'ovs-dpdk': yield prof_n @property def _cloud_hosts(self): return self._get_value_for_prop('cloud.hosts') def _get_networking(self, hostname): return self._get_value_for_prop('{}.networking'.format(hostname)) def _get_value_for_prop(self, prop): for pv in self._configprops: if pv['property'] == prop: return pv['value'] raise ClusterError('Property not found: {}'.format(prop)) @property def _configprops(self): if self._configprops_cache is None: self._setup_configprops_cache() return self._configprops_cache def _setup_configprops_cache(self): s = RemoteSession() s.set_runner_target(self.get_mgmt_shelldicts()) n = HostCli() n.initialize(s) self._configprops_cache = n.run('config-manager list properties')