19 Commits

Author SHA1 Message Date
OVERLORD
7e986834f6 Update README.md 2025-05-21 15:13:41 +03:00
OVERLORD7F
291e9cfe75 Complete menus overhaul 2025-05-21 14:12:37 +03:00
OVERLORD7F
d7477da9b6 - Beggining of implementing new menus and text using "Rich" library
btw main.py is ~ 50 lines now 0_o
2025-05-16 17:53:57 +03:00
OVERLORD7F
da756a8ba6 - Changed "disk edit mode" into function and moved to new module
- Changed config sub menu into function and moved to config_data_import module
- New function config_show
- Fixed create disk option in disk edit mode
- A few formatting changes in menus
2025-05-16 12:17:51 +03:00
OVERLORD7F
b6fb2b14e6 - Introducing power-on-check function.
All disk-related operations are available ONLY if VM is powered off.
2025-05-16 11:09:27 +03:00
OVERLORD
6b56b75189 Merge pull request #7 from Seting-dev/main
edit data pools api
2025-05-15 17:21:29 +03:00
OVERLORD
331b8f6935 Update data_pools_api.py
WOW SO MUCH COMMITED
2025-05-15 17:20:20 +03:00
Seting-dev
6c1759549c edit data pools api 2025-05-15 17:17:33 +03:00
OVERLORD7F
81554db41a removed vm_info_short 2025-05-15 15:44:39 +03:00
OVERLORD7F
54328f4e4a LEETS GOOOOOOOO
main is less then 100 lines !!!!
Code cleanup, all functions moved from main.
Small changes in main menu

vm_info_short moved to domain_api
2025-05-15 15:41:33 +03:00
OVERLORD7F
dccb5975e6 Merge branch 'main' of https://github.com/OVERLORD7F/SpaceVM_VM_Utility 2025-05-15 11:51:06 +03:00
OVERLORD7F
28e9bb8ea4 changes in importing config 2025-05-15 11:46:28 +03:00
Seting-dev
9525c4e2db Update main.py
test
2025-05-15 10:58:07 +03:00
Seting-dev
e7d2e0e829 check 2025-05-15 10:53:03 +03:00
Seting-dev
2f7a0826bf test1
test2
test3
2025-05-15 10:48:51 +03:00
Seting-dev
a885c549cc Merge branch 'main' of https://github.com/OVERLORD7F/SpaceVM_VM_Utility 2025-05-15 10:21:48 +03:00
OVERLORD
22f3d3fe64 Fixed issue #2 , Expanded config_edit function 2025-05-14 10:53:34 +03:00
Seting-dev
b2e361f032 Хуйня 2025-05-13 17:41:42 +03:00
OVERLORD
013d352bde Update README.md 2025-05-13 14:02:42 +03:00
7 changed files with 346 additions and 167 deletions

View File

@@ -3,22 +3,43 @@ Utility to manage Virtual Machines in SpaceVM.
Written in python, uses [SpaceVM API](https://spacevm.ru/docs/6.5/api/) to collect and manage existing Virtual Machines in your SpaceVM cluster.
_For now, this utility is focused on managing virtual disks_
>[!NOTE]
>_For now, this utility is focused on managing virtual disks_<br>
>_Works with SpaceVM 6.5.5+_
_Works with SpaceVM 6.5.5+_
# Requirements
- Fully setup SpaceVM cluster with VMs
- SpaceVM Utility and SpaceVM cluster should be in LAN
- Obtain your [API Key](https://spacevm.ru/docs/latest/base/operator_guide/security/users/#_14)
>[!WARNING]
> Utility is only tested on Windows 10
- For Windows 10 - [New Microsoft Terminal](https://github.com/microsoft/terminal) is highly recommended (correct colors, menus, etc)
# Config File
# Utility usage
Clone repository or use compiled .exe from [Releases Tab](https://github.com/OVERLORD7F/SpaceVM_VM_Utility/releases)
Fill in the config file as stated below.
## Config File
Config file contains all necessary data for utility and has to be placed in the same directory as Utility itself.
You can create config and specify all necessary data within the Utility.
<ins>The following parameters are required:</ins>
```
1. Controller IP Address (Master)
2. API Integration Key
3. Data Pool UUID (which will be used for operations)
4. Virtual Machine UUID (List of selected VMs for operations)
5. Virtual Machine UUID
6. ...
```
You can populate config within Utility Main Menu.
For manual input see format below:
```
Controller IP Address
API Integration Key
Data Pool UUID
VM UUID #1
VM UUID #2
VM UUID #3
...
1.2.3.4
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1...
67497424-e54b-46f1-b023-4d6d65eac104
304fd7be-06a6-4f4e-9bb9-41bf532ec4fb
f840320f-cecc-4a31-a799-fd84e7baf089
```

View File

@@ -1,26 +1,37 @@
#from main import base_url , api_key , requests
import requests
import os
from rich.prompt import Prompt
from rich.console import Console
from rich.panel import Panel
from rich.align import Align
def cluster_info(base_url , api_key): #output short clusters overview
url= f"http://{base_url}/api/clusters"
response = requests.get(url , headers={'Authorization' : api_key})
def cluster_info(base_url, api_key): # output short clusters overview
url = f"http://{base_url}/api/clusters"
response = requests.get(url, headers={'Authorization': api_key})
console = Console()
if response.status_code == 200:
cluster_info = response.json()
results_cluster_info = cluster_info['results']
print("\nShort clusters overview:")
print(f"\nClusters total: {cluster_info['count']}")
print("-" * 51)
os.system('cls' if os.name == 'nt' else 'clear')
console.rule("[bold cyan]Short Clusters Overview")
console.print(f"[bold]Clusters total:[/] {cluster_info['count']}\n")
panels = []
for x in results_cluster_info:
print(f"\nCluster Name: {x['verbose_name']} ({x['status']})")
print(f"Nodes: {x['nodes_count']}")
print(f"Total CPU: {x['cpu_count']} Cores || CPU Usage: {round(x['cpu_used_percent_user'] , 2)}%")#output is rounded by 2
print(f"Total RAM: {int(x['memory_count']/1024)}GB || RAM Usage: {round(x['mem_used_percent_user'] , 2)}%") #RAM pretty output = mb-to-gb + set 'int' to remove .0
print("-" * 51)
panel_content = (
f"[bold]Nodes:[/] {x['nodes_count']}\n"
f"[bold]Total CPU:[/] {x['cpu_count']} Cores / [bold]CPU Usage:[/] {round(x['cpu_used_percent_user'], 2)}%\n"
f"[bold]Total RAM:[/] {int(x['memory_count']/1024)} GB / [bold]RAM Usage:[/] {round(x['mem_used_percent_user'], 2)}%"
)
panel = Panel(
Align.left(panel_content),
title=f"[bold gold3]{x['verbose_name']}[/] [red]({x['status']})[/]",
border_style="magenta",
expand=False
)
panels.append(panel)
console.print(*panels, sep="\n")
else:
print(f"Failed to retrieve data {response.status_code}")
console.print(f"[red]Failed to retrieve data {response.status_code}[/]")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
os.system('cls' if os.name == 'nt' else 'clear')

77
config_data_import.py Normal file
View File

@@ -0,0 +1,77 @@
import os
from rich import print
from rich.panel import Panel
from rich.console import Console , Align
from rich.prompt import Prompt
console = Console()
def config_menu(config_relative_path):
cls()
config_menu_options="[gold bold][1] [grey53 italic]Show current configuration\n[/grey53 italic] \
\n[gold bold][2] [grey53 italic]Change configuraion[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - return to Main Menu"
config_menu_options=Align.center(config_menu_options, vertical="middle")
console = Console()
console.print(Panel(config_menu_options, title="[gold bold]SpaceVM Utility - Utility Configuration" , border_style="magenta" , width=150 , padding = 2))
sub_choice=str(input("\n>>> "))
if sub_choice == "1":
config_show(config_relative_path)
if sub_choice == "2":
config_edit(config_relative_path)
def config_show(config_relative_path):
cls()
console.rule(title = "Current configuration" , align="center" , style="yellow")
with open(config_relative_path, "r") as f:
print(f.read())
console.rule(style="yellow")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
def import_vm_uuid(config_relative_path):
vm_uuids = []
with open(config_relative_path, "r") as f:
for i in range(3): # ignoring 2 first lines (IP, API-KEY)
next(f)
for line in f:
line = line.strip('\n')
if line: # checks if line is empty (EOF). ESSENTIAL, DO NOT REMOVE
vm_uuids.append(line)
return vm_uuids
def import_threelines(config_relative_path):
threelines = [None] * 3
with open(config_relative_path, "r") as f:
all_lines = f.readlines()
if len(all_lines) < 3:
raise ValueError("Check config. Receiving less than 3 lines!")
threelines[0] = all_lines[0].strip('\n')
threelines[1] = "jwt " + all_lines[1].strip('\n') #actual format for api_key. That was realy obvious DACOM >:C
threelines[2] = all_lines[2].strip('\n')
return threelines
def config_edit(config_relative_path):
read_input=input("Create new config file? (Y / N): ")
menu_choice=str(read_input)
if menu_choice == "Y" or menu_choice == "y":
base_url = input("Type SpaceVM Controller IP: ")
api_key = input("Type your API Key: ")
data_pool_uuid = input("Type Data Pool UUID you wish to use: ")
lines = [base_url, api_key, data_pool_uuid]
with open(config_relative_path, "w+") as file:
for line in lines:
file.write(line + '\n')
print("Type VM UUIDs one by one (input ENTER to stop)")
with open(config_relative_path, "a") as file: #appends new content at the end without modifying the existing data
vm_input="test"
while (vm_input != ""):
vm_input = input(">> ")
file.write(vm_input + '\n')
console.print("[green bold]VM UUIDs has been written in config :pencil:")
console.print("[green bold]Configuration completed ! :white_check_mark:")
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
cls()
def cls():
os.system('cls' if os.name=='nt' else 'clear')

36
data_pools_api.py Normal file
View File

@@ -0,0 +1,36 @@
import requests
import os
from rich.prompt import Prompt
from rich.console import Console
from rich.panel import Panel
from rich.align import Align
def data_pools(base_url, api_key): # output data pool info
url = f"http://{base_url}//api/data-pools/"
response = requests.get(url, headers={'Authorization': api_key})
console = Console()
if response.status_code == 200:
data_pools = response.json()
results_data_pools_info = data_pools['results']
os.system('cls' if os.name == 'nt' else 'clear')
console.rule("[bold cyan]Data Pools Overview")
console.print(f"[bold]Data pools total:[/] {data_pools['count']}\n")
panels = []
for x in results_data_pools_info:
panel_content = (
f"[bold]Type:[/] {x['type']}\n"
f"[bold]Used:[/] {round((x['free_space']/1024), 1)} Gb / {round((x['size'] / 1024), 1)} Gb\n"
f"[bold]UUID:[/] [italic]{x['id']}"
)
panel = Panel(
Align.left(panel_content),
title=f"[bold gold3]{x['verbose_name']}[/] [red]({x['status']})[/]",
border_style="magenta",
expand=False
)
panels.append(panel)
console.print(*panels, sep="\n")
else:
console.print(f"[red]Failed to retrieve data {response.status_code}[/]")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
os.system('cls' if os.name == 'nt' else 'clear')

67
disk_edit_mode.py Normal file
View File

@@ -0,0 +1,67 @@
import os
import requests
from domain_api import *
from rich.prompt import Prompt
from rich.console import Console , Align
def disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids):
os.system('cls' if os.name=='nt' else 'clear')
diks_edit_menu_options="[gold bold][1] [grey53 italic]Delete vDisk by UUID\n[/grey53 italic] \
\n[gold bold][2] [grey53 italic]Delete ALL vDisks on selected Virtual Machine[/grey53 italic]\n \
\n[gold bold][3] [grey53 italic]Create Disk[/grey53 italic]\n \
\n[gold bold][4] [grey53 italic]Prepare VMs for Courses™[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - return to Main Menu"
diks_edit_menu_options = Align.center(diks_edit_menu_options, vertical="middle")
console = Console()
console.print(Panel(diks_edit_menu_options, title="[bold red]Disk Edit Mode" , border_style="magenta" , width=150 , padding = 2))
sub_choice=str(input("\n>>> "))
if sub_choice == "1":
read_input=input("Input vDisk uuid to delete: ")
vdisk_uuid=str(read_input)
delete_disk(base_url , api_key , vdisk_uuid)
if sub_choice == "2":
print(vm_uuids)
select_uuids=int(input("Select VM to delete disks from. \n Type VM uuid index number (from list above) to select: ")) - 1
vm_check_power(base_url , api_key , vm_uuids[select_uuids]) #power on check
domain_all_content = get_domain_all_content(base_url , api_key , vm_uuids[select_uuids])
disk_uuids = get_disk_uuids(base_url , api_key , domain_all_content)
for x in disk_uuids:
delete_disk(base_url , api_key , x)
console.print("[bold red]All attached vDisks has been deleted!")
if sub_choice == "3":
vdisk_size=str(input("Enter disk size (GB): "))
print(vm_uuids)
select_uuids=int(input("Select VM to attach new disk. \n Type VM uuid index number (from list above) to select: ")) - 1
print(f"{vm_uuids[select_uuids]} - {data_pool_uuid} - {vdisk_size} ")
create_and_attach_disk(base_url , api_key , vm_uuids[select_uuids] , data_pool_uuid , vdisk_size , "falloc")
if sub_choice == "4":
os.system('cls' if os.name=='nt' else 'clear')
console.rule(title="[bold magenta]Preparing VMs for Courses" , align="center" , style="grey53" , characters = "=")
for y in vm_uuids: #power-on check
domain_uuid = y.strip('\n')
vm_check_power(base_url , api_key , domain_uuid)
for x in vm_uuids: # only for removing disks
domain_uuid = x.strip('\n')
domain_info = get_domain_info(base_url , api_key , domain_uuid)
domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid)
vm_info(base_url , api_key , domain_uuid)
if domain_info:
disk_uuids = get_disk_uuids(base_url , api_key , domain_all_content)
for y in disk_uuids:
delete_disk(base_url , api_key , y)
console.print("[bold red]All attached vDisks has been deleted!")
for z in vm_uuids: # only for creating disks
domain_uuid = z.strip('\n')
console.print(f"\n[bold underline yellow]Creating and attaching disk to VM {domain_uuid}")
domain_info = get_domain_info(base_url , api_key , domain_uuid)
domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid)
if domain_info:
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 10, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
console.print("[bold green]\nDone. Happy virtualization :thumbs_up::thumbs_up:")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.. :right_arrow_curving_down:")
os.system('cls' if os.name=='nt' else 'clear')

View File

@@ -1,11 +1,13 @@
# functions for working with domain-api
import requests
import secrets #for generating unique names
#from main import power_state
import os
from rich.console import Console , Align
from rich.columns import Columns
from rich.panel import Panel
from rich.prompt import Prompt
console = Console() #necessary for pretty menus & output
power_state = ["Unknown" , "Off" , "Suspend" , "On"] #3 - on; 2 - suspend; 1 - off; 0 - unknown
@@ -51,6 +53,7 @@ def get_disk_uuids(base_url , api_key , domain_all_content):
print("ERROR: unexpected data format")
return []
def delete_disk(base_url , api_key , vdisk_uuid):
url = f"http://{base_url}/api/vdisks/{vdisk_uuid}/remove/"
headers={
@@ -70,48 +73,74 @@ def delete_disk(base_url , api_key , vdisk_uuid):
else:
print(f"ERROR deleting disk {vdisk_uuid} :\n {response.status_code} - {response.text}")
return False
def get_disk_info(domain_all_content):
console = Console()
# check for "vdisks" field in recieved json response
if 'vdisks' not in domain_all_content:
print("No 'vdisks' field in recieved data")
return
# get vdisk list
disks = domain_all_content['vdisks']
# check for disks
if not disks:
print("No 'disks' field in recieved data. \nProbably VM does not have any attached disks?")
console.print("[bold yellow]No 'disks' field in recieved data. \nProbably VM does not have any attached disks?")
return
disk_info_renderables = []
# Print info for each disk
for disk in disks:
# check for requiered fileds
# check for required fields
if 'id' in disk and 'verbose_name' in disk and 'size' in disk:
print(f"Name: {disk['verbose_name']}")
print(f"UUID: {disk['id']}")
print(f"Size: {disk['size']} GB")
print("-" * 51)
output_string = (
f"[bold]Name:[/] {disk['verbose_name']}\n"
f"[bold]UUID:[/] [italic]{disk['id']}[/italic]\n"
f"[bold]Size:[/] {disk['size']} GB")
disk_info_renderables.append(Panel(output_string, expand=False, border_style="magenta"))
else:
print("ERROR: failed to retrieve vdisk data.")
console.print(Columns(disk_info_renderables))
def vm_info(base_url , api_key , vm_uuids):
domain_info = get_domain_info(base_url , api_key , vm_uuids)
domain_all_content = get_domain_all_content(base_url , api_key , vm_uuids)
def vm_info(base_url, api_key, vm_uuids):
domain_info = get_domain_info(base_url, api_key, vm_uuids)
domain_all_content = get_domain_all_content(base_url, api_key, vm_uuids)
if domain_info:
print("\n" , "=" * 14 , "Virtual Machine Info" , "=" * 15)
print(f"\t VM: {domain_info['verbose_name']}")
print(f"\t Power State: {power_state[domain_info['user_power_state']]}") #translating status code to "pretty name"
print(f"\t vDisks: {domain_info['vdisks_count']}")
print("-" * 19 , "vDisks Info" , "-" * 19)
console = Console()
vm_info_lines = f"[bold]Power State:[/] [bold red]{power_state[domain_info['user_power_state']]}[/bold red] \n[bold]vDisks:[/] {domain_info['vdisks_count']}"
vm_info_renderable = Panel(vm_info_lines, title=f"[bold magenta]{domain_info['verbose_name']}" , expand=False , border_style="yellow")
vm_info_renderable=Align.center(vm_info_renderable, vertical="middle")
print("\n")
console.rule(style="yellow")
console.print(vm_info_renderable)
console.rule(title = "[bold yellow]vDisks Info" , style="grey53" , align="center")
get_disk_info(domain_all_content)
console.rule(style="yellow")
def vm_info_short(base_url, api_key):
url = f"http://{base_url}/api/domains/"
response = requests.get(url, headers={'Authorization': api_key})
if response.status_code == 200:
vm_info_short = response.json()
results_vm_info_short = vm_info_short['results']
os.system('cls' if os.name=='nt' else 'clear')
console.print(Align.center(Panel(f"[bold magenta]Short VM overview | Total: {vm_info_short['count']}", expand=True , border_style="yellow") , vertical="middle"))
console.rule(style="grey53")
output_renderables = []
for x in results_vm_info_short:
output_string = f"VM: [bold]{x['verbose_name']}" + f"\nUUID: [italic]{x['id']}"
output_renderable = Panel(output_string, expand=False, border_style="magenta")
output_renderables.append(output_renderable) #adds current renderable
console.print(Columns(output_renderables)) #print renderables by columns
else:
print(f"Failed to retrieve data {response.status_code}")
console.rule(style="grey53")
Prompt.ask("[green_yellow bold]ENTER - return to Main Menu.... :right_arrow_curving_down:")
os.system('cls' if os.name=='nt' else 'clear')
def create_and_attach_disk(base_url , api_key , vm_id, data_pool_uuid, vdisk_size, preallocation):
domain_name=get_domain_info(base_url , api_key , vm_id)
disk_name=domain_name["verbose_name"]+"_"+secrets.token_hex(5) #generates unique hex id. this method can generate ~million unique ids
@@ -129,8 +158,21 @@ def create_and_attach_disk(base_url , api_key , vm_id, data_pool_uuid, vdisk_siz
}
response = requests.post(url , headers=headers, json=payload)
if response.status_code == 200:
print(f"\nvDisk {disk_name} - {vdisk_size}GB has been created")
print(f"vDisk {disk_name} ({vdisk_size}GB) has been created and attached")
return True
else:
print(f"ERROR creating vDisk :\n {response.status_code} - {response.text}")
return False
return False
#checks for power on.
def vm_check_power(base_url , api_key , vm_uuids):
domain_info = get_domain_info(base_url , api_key , vm_uuids)
if domain_info:
#3 - on; 2 - suspend; 1 - off; 0 - unknown
if domain_info['user_power_state'] == 3 or domain_info['user_power_state'] == 2 : #if ON or SUSPEND
raise Exception(f"VM - {vm_uuids} IS POWERED ON! \n Turn it off and relaunch Utility.")
if domain_info['user_power_state'] == 0:
raise Exception(f"VM - {vm_uuids} is UNAVAILABLE! \n Have fun figuring that out D:")
if domain_info['user_power_state'] == 1:
print(f"VM - {vm_uuids} Power check passed!")

153
main.py
View File

@@ -1,130 +1,55 @@
import sys
import os
from config_data_import import *
from cluster_api import *
from domain_api import *
from data_pools_api import *
from disk_edit_mode import *
from rich import print
from rich.panel import Panel
from rich.console import Console , Align
power_state = ["Unknown" , "Off" , "Suspend" , "On"] #3 - on; 2 - suspend; 1 - off; 0 - unknown
#config.txt in the same directory with main.py
base_dir = os.getcwd() # Use the current directory as fallback
config_relative_path = os.path.join(base_dir, 'config.txt')
#config_relative_path = "Y:\\py\\SpaceVM_VM_Utility\\config.txt"
def config_edit():
read_input=input("Create new config file? (Y / N): ")
menu_choice=str(read_input)
if menu_choice == "Y" or menu_choice == "y":
base_url = input("Type SpaceVM Controller IP: ")
api_key = input("Type your API Key: ")
data_pool_uuid = input("Type Data pool uuid you wish to use: ")
lines = [base_url, api_key, data_pool_uuid]
with open(config_relative_path, "w+") as file:
for line in lines:
file.write(line + '\n')
print("Type VM-UUID (input ENTER to stop)")
with open(config_relative_path, "a") as file: #appends new content at the end without modifying the existing data
vm_input="test"
while (vm_input != ""):
vm_input = input(">> ")
file.write(vm_input + '\n')
if os.path.exists(config_relative_path) and os.path.getsize(config_relative_path) > 0: #check if file exists and not empty
#importing API-KEY / IP / DATA POOL UUID from config
with open(config_relative_path, "r") as f: # using '\' (instead of '\\') throws syntax warning
all_lines = f.readlines()
base_url = all_lines[0].strip('\n')
api_key = "jwt " + all_lines[1].strip('\n') #actual format for api_key. That was realy obvious DACOM >:C
data_pool_uuid = all_lines[2].strip('\n')
config_relative_path = os.path.join(os.getcwd() , 'config.txt') #config.txt in the same directory with main.py
if os.path.exists(config_relative_path) and os.path.getsize(config_relative_path) > 0: #check if config exists and not empty
pass #do nothing
else:
print("Config file was not found or empty.. ")
config_edit()
#importing VM-UUIDs
vm_uuids = []
with open(config_relative_path, "r") as f:
for i in range(3): # ignoring 2 first lines (IP, API-KEY)
next(f)
for line in f:
line = line.strip('\n')
if line: # checks if line is empty (EOF). ESSENTIAL, DO NOT REMOVE
vm_uuids.append(line)
console.print("[yellow bold italic]Config file was not found or empty.. ")
config_edit(config_relative_path)
#importing API-KEY / IP / DATA POOL UUID / VM-UUIDs from config
#base_url=threelines[0] api_key=threelines[1] data_pool_uuid=threelines[2]
base_url, api_key, data_pool_uuid = import_threelines(config_relative_path)
vm_uuids = import_vm_uuid(config_relative_path)
#so-called INT MAIN
menu_choice=0
menu_options="[gold bold][1] [grey53 italic]Manage utility config\n[/grey53 italic] \
\n[gold bold][2] [grey53 italic]Enter disk edit mode[/grey53 italic]\n \
\n[gold bold][3] [grey53 italic]Show breif cluster overview[/grey53 italic]\n \
\n[gold bold][4] [grey53 italic]Show VM info \n (for selected VMs in config)[/grey53 italic]\n \
\n[gold bold][5] [grey53 italic]Show data pools[/grey53 italic]\n \
\n[gold bold][6] [grey53 italic]Show VMs Name / UUID[/grey53 italic]\n \
\n\n[green_yellow bold]ENTER - exit Utility"
menu_options=Align.center(menu_options, vertical="middle")
menu_subtitle = "[blue bold][link=https://github.com/OVERLORD7F/SpaceVM_VM_Utility]:wrench: Project_GitHub[/link] [yellow]| [magenta bold][link=https://spacevm.ru/docs/]:books: SpaceVM_Docs[/link] [yellow]| [red bold][link=https://comptek.ru]:briefcase: Comptek[/link]"
console = Console()
os.system('cls' if os.name=='nt' else 'clear')
while(menu_choice != ""): #main menu loop
read_input=input("\nUitility Main Menu: \n1) Edit config \n2) Enter disk edit mode \n3) Show breif cluster overview \n4) Show VM info \n>>> ")
menu_choice=str(read_input)
console.print(Panel(menu_options,
title="[bold magenta]SpaceVM Utility - Main Menu" , subtitle = menu_subtitle, subtitle_align="right" , style="yellow" , width=150 , padding = 2))
menu_choice=str(input("\n>>> "))
if menu_choice == "1":
config_edit()
config_menu(config_relative_path)
if menu_choice == "2":
print("\033[H\033[2J", end="") # clears cmd screen, but saves scrollback buffer
print("Select option: \n 1) Delete vDisk by UUID \n 2) Delete ALL vDisks on selected Virtual Machine \n 3) Create Disk \n 4) Prepare VMs for Courses™")
read_input=input(">> ")
menu_choice=int(read_input)
if menu_choice == 1:
read_input=input("Input vDisk uuid to delete: ")
vdisk_uuid=str(read_input)
delete_disk(base_url , api_key , vdisk_uuid)
if menu_choice == 2:
print(vm_uuids)
select_uuids=int(input("Select VM to delete disks from. \n Type VM uuid index number (from list above) to select: ")) - 1
print(f"actual selected uuid = {select_uuids}")
print(vm_uuids[select_uuids])
domain_all_content = get_domain_all_content(base_url , api_key , vm_uuids[select_uuids])
disk_uuids = get_disk_uuids(base_url , api_key , domain_all_content)
for x in disk_uuids:
delete_disk(base_url , api_key , x)
print("All attached vDisks has been deleted!")
if menu_choice == 3:
vdisk_size=str(input("Enter disk size (GB): "))
print(vm_uuids)
select_uuids=int(input("Select VM to attach new disk. \n Type VM uuid index number (from list above) to select: ")) - 1
print(f"actual selected uuid = {select_uuids}")
print(vm_uuids[select_uuids])
create_and_attach_disk(vm_uuids[select_uuids] , data_pool_uuid, vdisk_size, "falloc")
if menu_choice == 4:
print("#" * 5 , "Preparing VMs for Courses" , "#" * 5)
for x in vm_uuids: # only for removing disks
domain_uuid = x.strip('\n')
domain_info = get_domain_info(base_url , api_key , domain_uuid)
domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid)
if domain_info:
print("=" * 14 , "Virtual Machine Info" , "=" * 15)
print(f"\t VM: {domain_info['verbose_name']}")
print(f"\t Power State: {power_state[domain_info['user_power_state']]}") #translating status code to "pretty name"
print(f"\t vDisks: {domain_info['vdisks_count']}")
print("-" * 19 , "vDisks Info" , "-" * 19)
get_disk_info(domain_all_content)
disk_uuids = get_disk_uuids(base_url , api_key , domain_all_content)
for y in disk_uuids:
delete_disk(base_url , api_key , y)
print("All attached vDisks has been deleted!")
for z in vm_uuids: # only for creating disks
domain_uuid = z.strip('\n')
domain_info = get_domain_info(base_url , api_key , domain_uuid)
domain_all_content = get_domain_all_content(base_url , api_key , domain_uuid)
if domain_info:
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 10, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
create_and_attach_disk(base_url , api_key , domain_uuid , data_pool_uuid, 20, "falloc")
disk_edit_mode(base_url , api_key , data_pool_uuid , vm_uuids)
if menu_choice == "3":
cluster_info(base_url , api_key)
if menu_choice == "4":
print("\033[H\033[2J", end="")
os.system('cls' if os.name=='nt' else 'clear')
for x in vm_uuids:
vm_info(base_url , api_key , x)
print("Exiting Utility..")
sys.exit()
Prompt.ask("[green_yellow bold]Press ENTER to proceed.. :right_arrow_curving_down:")
if menu_choice == "5":
data_pools(base_url , api_key)
if menu_choice == "6":
vm_info_short(base_url , api_key)
os.system('cls' if os.name=='nt' else 'clear') #clears screen before looping back to main menu
console.print("[red bold]Exiting Utility ")