def print_cluster(servers: Dict[str, List[OpenStackVMInfo]], vh: ValueHolder = None): __run_ico = Symbols.PLAY.color(Colors.GREEN) __pause_ico = Symbols.PAUSE.color(Colors.BRIGHT_YELLOW) __stop_ico = Symbols.STOP.color(Colors.RED) if vh is None: vh = ValueHolder(2, [40, 20]) to = TableOutput( TableColumn("Cluster Name", vh.get(WidthConst.max_cluster_name)), TableColumn("", 5), TableColumn("Nodes state", 20, inv_ch=Colors.GREEN.wrap_len() * 3), TableColumn("VmType", vh.get(WidthConst.max_vm_type_len)), TableColumn("Lifetime", 10) ) to.print_header() for cluster_name, _servers in servers.items(): server = _servers[0] num_running: int = len([s for s in _servers if s.state == ServerPowerState.running]) num_paused: int = len([s for s in _servers if s.state == ServerPowerState.paused]) num_stopped: int = len(_servers) - num_running - num_paused to.print_row( cluster_name, f"{len(_servers):>3}{Symbols.PC}:", f"{__run_ico}{num_running:<3} {__pause_ico}{num_paused:<3} {__stop_ico}{num_stopped:<3}", server.flavor.name, get_lifetime(server.created) )
def print_cluster(servers: Dict[str, List[OpenStackVMInfo]], vh: ValueHolder = None, ostack: OpenStack = None): __run_ico = Symbols.PLAY.green() __pause_ico = Symbols.PAUSE.yellow() __stop_ico = Symbols.STOP.red() __state = { ServerPowerState.running: __run_ico, ServerPowerState.paused: __pause_ico } if vh is None: vh = ValueHolder(3, [50, 30, 15]) to = TableOutput( TableColumn("", 1, inv_ch=Colors.GREEN.wrap_len()), TableColumn("Host name", vh.get(WidthConst.max_fqdn_len)), TableColumn("Host IP", 16), TableColumn("SSH Key", vh.get(WidthConst.max_key_len)), TableColumn("Network name", length=vh.get(WidthConst.max_net_len)) ) to.print_header() for cluster_name, servers in servers.items(): servers = sorted(servers, key=lambda x: x.fqdn) for server in servers: to.print_row( __state[server.state] if server.state in __state else __stop_ico, server.fqdn, server.ip_address if server.ip_address else "0.0.0.0", server.key_name, server.net_name )
def host_selector(ostack: OpenStack, name: str, node_index: Optional[int] = None, own: bool = False) -> OpenStackVMInfo: if name == "None": name = "" if name and node_index == -1: _name, _, _node_index = name.rpartition("-") try: node_index = int(_node_index) name = _name except (ValueError, TypeError): pass if "." in name: name, _ = name.split(".") search_result: Dict[str, List[OpenStackVMInfo]] = ostack.get_server_by_cluster(name, sort=True, only_owned=own) to = TableOutput( TableColumn("Cluster name", 40), print_row_number=True ) if len(search_result.keys()) > 1: to.print_header() for cluster_name in search_result.keys(): to.print_row(cluster_name) selection: int = Console.ask("Choose cluster from the list", int) try: name = list(search_result.keys())[selection:][0] except IndexError: raise ValueError("Wrong selection, please select item within an provided range") elif search_result: name = list(search_result.keys())[0] else: raise ValueError(f"No matching cluster matching pattern'{name}' found") nodes: List[OpenStackVMInfo] = search_result[name] if node_index == -1: if len(nodes) > 1: to = TableOutput( TableColumn("IP", 18), TableColumn("Host name", 40), print_row_number=True ) to.print_header() for node in nodes: to.print_row(node.ip_address, node.fqdn) node_index: int = Console.ask("Choose host from the list", int) if node_index > len(nodes): raise ValueError("Wrong selection, please select item within an provided range") else: node_index = 0 else: node_index -= 1 # the node name starts for 1, while list from 0 try: return nodes[node_index] except IndexError: raise ValueError("Unknown host name, please check the name")
def __init__(conf: Configuration, search_pattern: str, own: bool): ostack = OpenStack(conf) image_id_ref: Dict[str, DiskImageInfo] = {img.id: img for img in ostack.images} images: List[tuple[DiskImageInfo, str, DiskImageInfo]] = [ ] # Snap Image, Base Image Name, Base Image user_id = conf.user_id _search_pattern = search_pattern.lower() if search_pattern else None max_name_len: TableMaxValue[int] = TableMaxValue(0) max_base_name: TableMaxValue[int] = TableMaxValue(0) for image in ostack.images: if own and image.user_id != user_id: continue if not image.image_type: continue if search_pattern and _search_pattern not in image.name.lower(): continue base_image: DiskImageInfo = image_id_ref[ image. base_image_ref] if image.base_image_ref in image_id_ref else None base_os_image: OSImageInfo = ostack.get_os_image( base_image) if base_image else None base_image_name: str = base_os_image.name if base_os_image else\ base_image.name if base_image else "unknown" max_name_len.process(len(image.name)) max_base_name.process(len(base_image_name)) images.append(( image, base_image_name, base_image, )) table = TableOutput(TableColumn("Name", max_name_len.value), TableColumn("Status", 10), TableColumn("Base Image Name", max_base_name.value), TableColumn("Snap Size | Base Sise | Total Size", 36), TableColumn("Visibility", 10)) table.print_header() for image, base_name, base_image in images: snap_size = TableSizeColumn(image.size) base_image_size = TableSizeColumn( base_image.size) if base_image else snap_size table.print_row( image.name, image.status, base_name, f"{(snap_size-base_image_size).value:>10} | {base_image_size.value:>10} | {snap_size.value:>10}", image.visibility)
def __init__(conf: Configuration, details: bool, show_clusters: bool, graph: bool): stack = OpenStack(conf) limits = stack.quotas to = TableOutput( TableColumn("Metric", length=limits.max_metric_len, pos=TableColumnPosition.right, sep=":", inv_ch=Colors.RED.wrap_len()), TableColumn("Used", length=7, pos=TableColumnPosition.right, sep="|", inv_ch=Colors.RED.wrap_len()), TableColumn( "Avl.", length=7, pos=TableColumnPosition.right, sep="|", ), TableColumn("Total", length=7, pos=TableColumnPosition.right), TableColumn("", length=30)) print(f"Region {conf.region} stats\n") to.print_header(solid=True) for metric in limits: prc = get_percents(metric.used, metric.max_count) c_end = Colors.RESET if prc > 80 else "" c_start = Colors.RED if prc > 90 else Colors.YELLOW if prc > 80 else "" if metric.max_count < 0: c_start = "" c_end = "" to.print_row( f"{c_start}{metric.name}{c_end}", f"{c_start}{metric.used}{c_end}", "-" if metric.available < 0 else metric.available, "-" if metric.max_count < 0 else metric.max_count, f"{get_progressbar(prc, c_start)} {c_start}{prc:.1f}%{c_end}") print() print("* Used/Avl. - raw metric") print() if graph: _show_graph(stack, limits) elif details: print("Calculating per-user statistic....") _show_details(conf, stack, limits, show_clusters)
def show_normal(conf: Configuration, ostack: OpenStack, search_pattern: str, own: bool): images = ostack.os_images table = TableOutput(TableColumn("Name", 20), TableColumn("Alias", 20), TableColumn("Size", 9), TableColumn("Description", 60)) table.print_header() for image in images: if search_pattern and search_pattern.lower() not in image.name.lower(): continue table.print_row(image.name, image.alias, TableSizeColumn(image.size).value, image.description)
def _keys_list(conf: Configuration, ostack: OpenStack, show_row_nums: bool = False) -> List[VMKeypairItemValue]: server_keypairs: Dict[int, VMKeypairItemValue] = { hash(key): key for key in ostack.get_keypairs() } conf_keys = conf.get_keys() if not conf_keys: Console.print_warning("No keys found, add new ones") return [] max_key_len = len(max(conf.key_names)) to = TableOutput(TableColumn("Key Name", max_key_len + len(KEY_ICON), inv_ch=len(KEY_ICON) - 2, pos=TableColumnPosition.left), TableColumn("Priv.Key", 3, inv_ch=len(CHECK_ICON) - 2, pos=TableColumnPosition.center), TableColumn("Pub.Key", 3, inv_ch=len(CHECK_ICON) - 2, pos=TableColumnPosition.center), TableColumn("Rem.Sync", 3, inv_ch=len(CHECK_ICON), pos=TableColumnPosition.center), TableColumn("Fingerprint", 48, pos=TableColumnPosition.left), print_row_number=show_row_nums) to.print_header() for kp in conf_keys: to.print_row( f"{KEY_ICON}{kp.name}", CHECK_ICON if kp.private_key else UNCHECK_ICON, CHECK_ICON if kp.public_key else UNCHECK_ICON, CHECK_ICON if hash(kp) in server_keypairs else UNCHECK_ICON, server_keypairs[hash(kp)].fingerprint if hash(kp) in server_keypairs else "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00") return conf_keys
def print_networks(ostack: OpenStack, select: bool = False) -> OSNetworkItem or None: tbl = TableOutput(TableColumn("N.", 3), TableColumn("Zone", 10), TableColumn("Name", 20), TableColumn("CIDR", 15), TableColumn("Domain name", 30), TableColumn("DNS", 15), TableColumn("Active", 6), style=TableStyle.line_highlight) nets: List[OSNetworkItem] = sorted(ostack.networks, key=lambda x: x.name) tbl.print_header() counter: int = 0 for net in nets: if len(net.dns_nameservers) > 1: dns_servers = net.dns_nameservers[:-1] for dns_ip in dns_servers: tbl.print_row("", "", "", "", "", "", dns_ip, "") tbl.print_row( str(counter), ",".join(net.orig_network.availability_zones), net.name, net.cidr, net.domain_name, ",".join(net.dns_nameservers[-1:]) if len(net.dns_nameservers) > 1 else ",".join(net.dns_nameservers), net.orig_network.status) counter += 1 if select: a = input("Please select item:") try: return nets[int(a)] except ValueError: pass return None
def show_all(conf: Configuration, ostack: OpenStack, search_pattern: str, own: bool): images = ostack.images table = TableOutput(TableColumn("Id", 36), TableColumn("Name", 60), TableColumn("Size", 9), TableColumn("Status", 10), TableColumn("Description", 20)) table.print_header() for image in images: if image.image_type: continue if search_pattern and search_pattern.lower() not in image.name.lower(): continue table.print_row(image.id, image.name, TableSizeColumn(image.size).value, image.status, image.description if image.description else "-")
def __init__(conf: Configuration, image_name: str, sort_by_name: bool, all: bool): sort_keys = { True: lambda x: x.name, False: lambda x: (x.vcpus, x.ram, x.disk, x.ephemeral_disk) } ostack = OpenStack(conf) if image_name: images = list(ostack.get_image_by_alias(image_name)) if images and len(images) > 1: Console.print_error( f"Image '{image_name}' matches more than one image") return elif not images: Console.print_error(f"No image with name '{image_name}' found") return flavors = sorted(ostack.get_flavors(images[0]), key=sort_keys[sort_by_name]) else: flavors = sorted(ostack.flavors, key=sort_keys[sort_by_name]) table = TableOutput(TableColumn("Name", 20), TableColumn("vCPU", 5), TableColumn("RAM", 9), TableColumn("D+E Size", 15), TableColumn("Disk", 15), TableColumn("Ephemeral Disk", 15), TableColumn("Id", 36)) table.print_header() for flavor in flavors: if not all and flavor.ephemeral_disk == 0: continue table.print_row(flavor.name, flavor.vcpus, TableSizeColumn(flavor.ram).value, TableSizeColumn(flavor.sum_disk_size).value, TableSizeColumn(flavor.disk).value, TableSizeColumn(flavor.ephemeral_disk).value, flavor.id)
def __init__(conf: Configuration, name: str, node_number: int, user_name: str, use_password: bool, use_key: str, own: bool, port: int, internal: bool): ostack = OpenStack(conf) if name == "None": name = "" if use_key == "None": use_key = None if name and node_number == -1: _name, _, _node_number = name.rpartition("-") try: node_number = int(_node_number) name = _name except (ValueError, TypeError): pass if "." in name: name, _ = name.split(".") search_result: Dict[str, List[OpenStackVMInfo]] = ostack.get_server_by_cluster(name, sort=True, only_owned=own) to = TableOutput( TableColumn("Cluster name", 40), print_row_number=True ) if len(search_result.keys()) > 1: to.print_header() for cluster_name in search_result.keys(): to.print_row(cluster_name) selection: int = Console.ask("Choose cluster from the list", int) try: name = list(search_result.keys())[selection:][0] except IndexError: raise ValueError("Wrong selection, please select item within an provided range") elif search_result: name = list(search_result.keys())[0] else: raise ValueError(f"No matching cluster matching pattern'{name}' found") nodes: List[OpenStackVMInfo] = search_result[name] if node_number == -1: if len(nodes) > 1: to = TableOutput( TableColumn("IP", 18), TableColumn("Host name", 40), print_row_number=True ) to.print_header() for node in nodes: to.print_row(node.ip_address, node.fqdn) node_number: int = Console.ask("Choose host from the list", int) if node_number > len(nodes): raise ValueError("Wrong selection, please select item within an provided range") else: node_number = 0 else: node_number -= 1 # the node name starts for 1, while list from 0 try: node: OpenStackVMInfo = nodes[node_number] except IndexError: raise ValueError("Unknown host name, please check the name") print(f"Establishing connection to {node.fqdn}({node.ip_address}) as '{user_name}' user...") if use_password: _open_console(internal, node.ip_address, port=port, user_name=user_name, password=True) else: if not os.path.exists(conf.local_key_dir): os.makedirs(conf.local_key_dir, exist_ok=True) if not use_key and node.key_name and node.key_name in conf.key_names and conf.get_key(node.key_name).private_key: #PKCS8 format -> openssl pkcs8_frmt_B = "-----BEGIN PRIVATE KEY-----" pkcs8_frmt_E ="-----END PRIVATE KEY-----" openssl_frmt_B = "-----BEGIN RSA PRIVATE KEY-----" openssl_frmt_E = "-----END RSA PRIVATE KEY-----" key = conf.get_key(node.key_name).private_key if pkcs8_frmt_B in key: key = key.replace(pkcs8_frmt_B, openssl_frmt_B) if pkcs8_frmt_E in key: key = key.replace(pkcs8_frmt_E, openssl_frmt_E) use_key = os.path.join(conf.local_key_dir, node.key_name) + ".key" with open(use_key, "w+", encoding="UTF-8") as f: f.write(key) try: os.chmod(use_key, 0o600) except OSError: pass else: raise ValueError("No custom key provided nor private key found in the key storage. Please add private key to" " storage or use custom one with 'use-key' argument") _open_console(internal, node.ip_address, user_name=user_name, port=port, key_file=use_key)
def __call__(self, *args, **kwargs): assert isinstance(self._conf, Configuration) conf: Configuration = self._conf if not conf.os_address: conf.os_address = self.ask_text_question( "OpenStack identity api address: ") if not conf.os_login: conf.os_login = self.ask_text_question("OpenStack username: "******"OpenStack password: "******""" Login sequence: ------------------ - unscoped loing - set project id - scoped login - fetch region - relogin to fetch API endpoints """ if not conf.project.id: print("Fetching available projects....") if not osvm.login(_type=AuthRequestType.UNSCOPED): if osvm.has_errors: so.check_issues() self.reset() raise RuntimeError("Unable to continue") projects = osvm.projects to = TableOutput(TableColumn("Id", 33), TableColumn("Name", 20), TableColumn("Enabled", 6), print_row_number=True) to.print_header() for prj in projects: to.print_row(prj.id, prj.name, prj.enabled) n: int = Console.ask("Select the project number to be used", _type=int) conf.project = VMProject(id=projects[n].id, name=projects[n].name, domain=projects[n].domain_id) osvm.logout() print(f"Checking login for the project '{conf.project.name}'...") if not osvm.login(): if osvm.has_errors: so.check_issues() self.reset() raise RuntimeError("Unable to continue") if not conf.region: print( f"Fetching available regions for the project '{conf.project.name}'..." ) to = TableOutput(TableColumn("Id", 33), TableColumn("Descriuption"), print_row_number=True) to.print_header() regions = osvm.regions for region in regions: to.print_row(region.id, region.description) n: int = Console.ask("Select the region number to be used", _type=int) conf.region = regions[n].id osvm.logout() if not osvm.login(): conf.reset() raise RuntimeError("Unable to continue") if not conf.default_network: print( "Please select default network for the VM (could be changed via 'conf network' command):" ) _net = print_networks(ostack=osvm, select=True) if not _net: raise RuntimeError("Network is not selected") conf.default_network = _net if not conf.default_vm_password: _p = self.ask_text_question("Default VM password: "******"qwerty" conf.default_vm_password = _p _default_keypair_name = "default" keys = conf.get_keys() srv_keys = osvm.get_keypairs(no_cache=True) _is_srv_key = False _is_cfg_key = False for srv_key in srv_keys: if srv_key.name == _default_keypair_name: _is_srv_key = True break for cfg_key in keys: if cfg_key.name == _default_keypair_name: _is_cfg_key = True break if _is_cfg_key and not _is_srv_key: print(f"Purging '{_default_keypair_name}' key from configuration") conf.delete_key(_default_keypair_name) _is_cfg_key = False if not _is_cfg_key and not _is_srv_key: print(f"Creating new '{_default_keypair_name}' keypair..") _create_key( conf, osvm, VMNewKeyPairItemBuilder().set_name(_default_keypair_name)) print( f"Key '{_default_keypair_name}' could be exported using command 'conf keys export {_default_keypair_name}'" ) if not _is_cfg_key and _is_srv_key: print( f"Public key '{_default_keypair_name}' would be re-synced locally, please add private key or re-generate new default key" )
def __init__(conf: Configuration, name: str, count: int, flavor: str, image: str, key: str, password: str): def __work_unit(x: OpenStackVMInfo) -> bool: while True: if x.status == ServerState.active: return True if x.status not in [ ServerState.building, ServerState.build, ServerState.active ]: return False sleep(2) # ToDo: short term cache data by reservation_id x = ostack.get_server_by_id(x) # ====================================================================== ostack = OpenStack(conf) vh = ValueHolder(2) def __filter(vm: OpenStackVMInfo) -> bool: r = not str(vm.name).startswith(name) if not r: vh.set_if_bigger(0, len(vm.cluster_name)) vh.set_if_bigger(1, len(vm.flavor.name)) return r servers = ostack.get_server_by_cluster(name, True, filter_func=__filter) if len(servers) > 1: Console.print_warning( f"Cluster with name '{Colors.BRIGHT_WHITE.wrap(name)}' already exists, instance would be not be created" ) print_cluster(servers, vh) return elif len(servers) == 1: Console.print_info( f"The cluster already exists, will add requested amount of hosts to the cluster" ) with Console.status_context( f"Obtaining cluster information from existing {name}..."): cluster_name, hosts = next(iter(servers.items())) cluster_name: str = cluster_name hosts: List[OpenStackVMInfo] = hosts host: OpenStackVMInfo = hosts[0] # re-calculate host names last_host_name = hosts[-1:][0].name _, _, num = last_host_name.rpartition("-") _start_num: int = 1 if num and num.isnumeric(): _start_num: int = int(num) + 1 name: List[str] = [ f"{cluster_name}-{num}" for num in range(_start_num, _start_num + count) ] image: OSImageInfo = ostack.get_os_image(host.image) img_flavor: OSFlavor = host.flavor _default_key = ostack.get_keypairs()[0] if ostack.get_keypairs( ) else None _key = ostack.get_keypair(host.key_name, _default_key) _pass = conf.default_vm_password if not password else password print(f" |Image flavor to use: {img_flavor.name}") print(f" |Image to use : {image.alias}") print(f" |Key to use : {_key.name}") print(f" |Hosts to add : {', '.join(name)}") else: with Console.status_context("Resolving cluster configuration"): image: List[OSImageInfo] = list(ostack.get_image_by_alias(image)) if not image: raise RuntimeError("Cannot resolve image name for the request") image: OSImageInfo = image[0] img_flavor = ostack.get_flavor(image, flavor) _default_key = ostack.get_keypairs()[0] if ostack.get_keypairs( ) else None _key = _default_key if not key else ostack.get_keypair( key, _default_key) _pass = conf.default_vm_password if not password else password # == create nodes so = StatusOutput(__work_unit, pool_size=2, additional_errors=ostack.last_errors) with Console.status_context("Asking for node creation"): servers = ostack.create_instances(cluster_names=name, image=image, flavor=img_flavor, password=_pass, count=count, ssh_key=_key) if not servers: so.check_issues() return so.start("Creating nodes ", objects=servers) # == Configure nodes def __work_unit_waiter(x: OpenStackVMInfo) -> bool: tries: int = 0 while tries < 200: log = ostack.get_server_console_log(x.id) for l in log: if "finished" in l or "login:"******"Configure nodes", servers) console = ostack.get_server_console_log(servers[0], grep_by="cloud-init") to = TableOutput(TableColumn("Name", 15), TableColumn("Value", 30)) to.print_header(custom_header="SUMMARY") for line in console: if "@users@" in line: users = line.split("@users@:")[1].strip().split(" ") to.print_row("Accounts", ",".join(users)) to.print_row("Key", _key.name if _key else "Not used") to.print_row("Password", _pass)