RPC frontend for libvirt #1
33
client.py
Normal file → Executable file
33
client.py
Normal file → Executable file
|
@ -1,46 +1,49 @@
|
||||||
import os
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
|
||||||
|
|
||||||
class RPCClient():
|
|
||||||
|
class RPCClient:
|
||||||
"""
|
"""
|
||||||
RPCClient Class
|
RPCClient Class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def create_vm(self, **kwargs):
|
def create_vm(self, **kwargs):
|
||||||
|
|
||||||
url= "http://127.0.0.1:8080/create?"
|
url = "http://127.0.0.1:8080/create?"
|
||||||
if kwargs:
|
if kwargs:
|
||||||
q = ""
|
q = ""
|
||||||
for k in kwargs.keys():
|
for k in kwargs.keys():
|
||||||
q += "&{}={}".format(k,kwargs[k])
|
q += "&{}={}".format(k, kwargs[k])
|
||||||
|
|
||||||
url+=q
|
url += q
|
||||||
|
|
||||||
print(url)
|
print(url)
|
||||||
resp= requests.get(url)
|
resp = requests.get(url)
|
||||||
|
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
def start_vm(self, vm):
|
def start_vm(self, vm):
|
||||||
""" get a vm and try starting it
|
"""get a vm and try starting it
|
||||||
return the ID or appropriate message
|
return the ID or appropriate message
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def stop_vm(self, id):
|
def stop_vm(self, id):
|
||||||
#stops vm of a given id
|
# stops vm of a given id
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import random
|
import random
|
||||||
client= RPCClient()
|
|
||||||
print("Creating VM...")
|
|
||||||
name= "CentOS-8-Demo-{}-{}".format(random.sample("abcdefghijklmn", 1)[0], random.sample(range(10),1)[0])
|
|
||||||
iso_src= "/home/coolguy/Downloads/CentOS-8.2.2004-x86_64-boot.iso"
|
|
||||||
img_src= "/var/lib/libvirt/images/vol.img"
|
|
||||||
|
|
||||||
vm_info= client.create_vm(name=name, iso_source=iso_src, img_source=img_src)
|
client = RPCClient()
|
||||||
|
print("Creating VM...")
|
||||||
|
name = "CentOS-8-Demo-{}-{}".format(
|
||||||
|
random.sample("abcdefghijklmn", 1)[0], random.sample(range(10), 1)[0]
|
||||||
|
)
|
||||||
|
iso_src = "/home/coolguy/Downloads/CentOS-8.2.2004-x86_64-boot.iso"
|
||||||
|
img_src = "/var/lib/libvirt/images/vol.img"
|
||||||
|
|
||||||
|
vm_info = client.create_vm(name=name, iso_source=iso_src, img_source=img_src)
|
||||||
if vm_info["status"]:
|
if vm_info["status"]:
|
||||||
print("\n\nGuest '{}' has been created and booted".format(name))
|
print("\n\nGuest '{}' has been created and booted".format(name))
|
||||||
print("UUID for {}: {}".format(name, vm_info["uuid"]))
|
print("UUID for {}: {}".format(name, vm_info["uuid"]))
|
||||||
|
|
7
config.ini
Executable file
7
config.ini
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
[host]
|
||||||
|
# enter comma separated hosts
|
||||||
|
available_hosts = 'qemu:///system', "qemu:///session"
|
||||||
|
|
||||||
|
[server]
|
||||||
|
server_host = 127.0.0.1
|
||||||
|
server_port = 8080
|
9
consumer.sql
Executable file
9
consumer.sql
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
-- This table will store consumer/worker info/configurations
|
||||||
|
|
||||||
|
-- some info about workers
|
||||||
|
CREATE TABLE IF NOT EXISTS worker(
|
||||||
|
worker_id INT UNIQUE NOT NULL,
|
||||||
|
status VARCHAR NOT NULL,
|
||||||
|
--OTHER_DATA
|
||||||
|
|
||||||
|
)
|
199
database.py
Executable file
199
database.py
Executable file
|
@ -0,0 +1,199 @@
|
||||||
|
import sqlite3
|
||||||
|
from sqlite3 import Error
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
if not os.path.exists("database/"):
|
||||||
|
os.makedirs("database/")
|
||||||
|
|
||||||
|
# .db file
|
||||||
|
self.db = ""
|
||||||
|
|
||||||
|
# Insert into database
|
||||||
|
def insert_into_database(self, tableName, conn, data):
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
c = conn.execute("select * from {}".format(tableName))
|
||||||
|
fields = tuple([des[0] for des in c.description][:])
|
||||||
|
|
||||||
|
if "id" in fields:
|
||||||
|
fields = tuple(list(fields)[1:])
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO {}
|
||||||
|
{} VALUES {}
|
||||||
|
""".format(
|
||||||
|
tableName, fields, data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Update Database
|
||||||
|
def update_database(self, tableName, conn, fields, field_vals, ref, index):
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
if not isinstance(fields, tuple) and not isinstance(fields, list):
|
||||||
|
fields = list([fields])
|
||||||
|
field_vals = list([field_vals])
|
||||||
|
|
||||||
|
for field, field_val in zip(fields, field_vals):
|
||||||
|
# print(field,field_val)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
UPDATE {}
|
||||||
|
SET {}= ? WHERE {}= ?
|
||||||
|
""".format(
|
||||||
|
tableName, field, ref
|
||||||
|
),
|
||||||
|
(field_val, index),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# print("Error in updating data: {}".format(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Delete from Database
|
||||||
|
def delete_from_database(self, conn, tableName, condition):
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
# just to track if deletion was successful
|
||||||
|
count = len(
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM {} WHERE {}
|
||||||
|
""".format(
|
||||||
|
tableName, condition
|
||||||
|
)
|
||||||
|
).fetchall()
|
||||||
|
)
|
||||||
|
if not count:
|
||||||
|
return False
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM {} WHERE {}
|
||||||
|
""".format(
|
||||||
|
tableName, condition
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
# print("Error in deleting data: {}".format(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Search in the database
|
||||||
|
def search_from_database(self, tableName, conn, prop, value, order_by="id"):
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
# print("cur: {}".format(cur))
|
||||||
|
filtered_list = cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM {} WHERE {} LIKE ? ORDER BY {};
|
||||||
|
""".format(
|
||||||
|
tableName, prop, order_by
|
||||||
|
),
|
||||||
|
(str(value) + "%",),
|
||||||
|
).fetchall()
|
||||||
|
return filtered_list
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
# print("Error in searching: {}".format(e))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search_from_database_many(self, tableName, conn, condition):
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
filtered_list = cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM {} WHERE {}
|
||||||
|
""".format(
|
||||||
|
tableName, condition
|
||||||
|
)
|
||||||
|
).fetchall()
|
||||||
|
return filtered_list
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
# print("Error in deleting data: {}".format(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# connect database
|
||||||
|
def connect_database(self, db_file):
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect("database/" + db_file)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
# print("Error in database: {}".format(e))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# create table
|
||||||
|
def create_table(self, conn, table):
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(table)
|
||||||
|
except Error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def delete_table(self, conn, db_file, table_name):
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("DROP TABLE {}".format(table_name))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def extract_all_data(self, conn, db_file, tableName, order_by="id"):
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
cur = conn.execute(
|
||||||
|
"SELECT * FROM {} ORDER BY {}".format(tableName, order_by)
|
||||||
|
)
|
||||||
|
data = cur.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return data
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete_all_data(self, conn, db_file, tableName):
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
# conn.commit()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM {};
|
||||||
|
""".format(
|
||||||
|
tableName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
51
pc.py
Executable file
51
pc.py
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
from threading import Thread, Condition
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
|
queue = []
|
||||||
|
BUFF_SIZE = 10
|
||||||
|
condition = Condition()
|
||||||
|
|
||||||
|
|
||||||
|
class ProducerThread(Thread):
|
||||||
|
def run(self):
|
||||||
|
nums = range(5)
|
||||||
|
global queue
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
condition.acquire()
|
||||||
|
if len(queue) == BUFF_SIZE:
|
||||||
|
# Queue full, producer is waiting
|
||||||
|
condition.wait()
|
||||||
|
# Space in queue, Consumer notified the producer
|
||||||
|
|
||||||
|
num = random.choice(nums)
|
||||||
|
queue.append(num)
|
||||||
|
print("Produced ", num)
|
||||||
|
condition.notify()
|
||||||
|
condition.release()
|
||||||
|
time.sleep(random.random() * 2)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsumerThread(Thread):
|
||||||
|
def run(self):
|
||||||
|
global queue
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
condition.acquire()
|
||||||
|
if not queue:
|
||||||
|
# Nothing in queue, consumer is waiting
|
||||||
|
condition.wait()
|
||||||
|
# Producer added something to queue and notified the consumer
|
||||||
|
|
||||||
|
num = queue.pop(0)
|
||||||
|
print("Consumed ", num)
|
||||||
|
condition.notify()
|
||||||
|
condition.release()
|
||||||
|
time.sleep(random.random() * 2)
|
||||||
|
|
||||||
|
|
||||||
|
ProducerThread().start()
|
||||||
|
ConsumerThread().start()
|
10
queue.sql
Executable file
10
queue.sql
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
-- This table will store processes --
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS {}(
|
||||||
|
worker_id INT UNIQUE NOT NULL,
|
||||||
|
start DATETIME NOT NULL, -- timestamps
|
||||||
|
end DATETIME NOT NULL,
|
||||||
|
status VARCHAR NOT NULL,
|
||||||
|
--OTHER_VM_DATA
|
||||||
|
|
||||||
|
)
|
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
7
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17
templates/assets/css/Bootstrap-4---Table-Fixed-Header.css
Normal file
17
templates/assets/css/Bootstrap-4---Table-Fixed-Header.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #74ebd5;
|
||||||
|
background: -webkit-linear-gradient(to right, #74ebd5, #ACB6E5);
|
||||||
|
background: linear-gradient(to right, #74ebd5, #ACB6E5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableFixHead{
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableFixHead thead th{
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
2
templates/assets/js/jquery.min.js
vendored
Normal file
2
templates/assets/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
37
templates/config.tmpl.xml
Normal file
37
templates/config.tmpl.xml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<domain type="{{ args.type }}">
|
||||||
|
<name>{{ args.name }}</name>
|
||||||
|
<uuid>{{ args.uuid }}</uuid>
|
||||||
|
<memory unit="{{ args.memory_unit }}">{{ args.memory }}</memory>
|
||||||
|
<currentMemory unit="{{ args.memory_unit }}">{{ args.current_memory }}</currentMemory>
|
||||||
|
<vcpu placement="static">{{ args.vcpu }}</vcpu>
|
||||||
|
<os>
|
||||||
|
<type arch='{{ args.arch }}' machine='{{ args.machine }}'>hvm</type>
|
||||||
|
<!--boot dev='hd'/-->
|
||||||
|
<boot dev='cdrom'/>
|
||||||
|
</os>
|
||||||
|
<clock offset='{{ args.offset }}'/>
|
||||||
|
<on_poweroff>destroy</on_poweroff>
|
||||||
|
<on_reboot>restart</on_reboot>
|
||||||
|
<on_crash>destroy</on_crash>
|
||||||
|
<devices>
|
||||||
|
<emulator>{{ args.emulator }}</emulator>
|
||||||
|
<disk type="file" device="disk">
|
||||||
|
<driver name="qemu" type="raw"/>
|
||||||
|
<source file="{{ args.img_source }}"/>
|
||||||
|
<target dev="vda" bus="virtio"/>
|
||||||
|
</disk>
|
||||||
|
<disk type='file' device='cdrom'>
|
||||||
|
<source file='{{ args.iso_source }}'/>
|
||||||
|
<target dev='sda' bus='sata'/>
|
||||||
|
</disk>
|
||||||
|
<interface type="network">
|
||||||
|
<mac address="52:54:00:b6:8e:76"/>
|
||||||
|
<source network="default"/>
|
||||||
|
<model type="virtio"/>
|
||||||
|
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
|
||||||
|
</interface>
|
||||||
|
<!--graphics will be removed after testing as we probably won't need this-->
|
||||||
|
<graphics type='vnc' port='-1' listen='127.0.0.1'/>
|
||||||
|
</devices>
|
||||||
|
|
||||||
|
</domain>
|
81
templates/guest.html
Normal file
81
templates/guest.html
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>Guests Info</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Bootstrap-4---Table-Fixed-Header.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="col ">
|
||||||
|
<h3 class="text-center">Guest Detail</h4>
|
||||||
|
<hr>
|
||||||
|
<div class="tableFixHead">
|
||||||
|
<table class="table">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">S.No.</th>
|
||||||
|
<th scope="col">Guest Name</th>
|
||||||
|
<th scope="col">Host Name</th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Allocated<br>Memory(MiB)</th>
|
||||||
|
<th scope="col">Allocated<br>CPUs</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">UUID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="table-body"></tbody>
|
||||||
|
|
||||||
|
{% if guest_data %}
|
||||||
|
{% for data in guest_data %}
|
||||||
|
<tr id="${{ data.guestname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.guestname }}</td>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.id_ }}</td>
|
||||||
|
<td>{{ data.mem }}</td>
|
||||||
|
<td>{{ data.cpu }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uuid }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${{ data.guestname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.guestname }}</td>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.id_ }}</td>
|
||||||
|
<td>{{ data.mem }}</td>
|
||||||
|
<td>{{ data.cpu }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uuid }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${{ data.guestname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.guestname }}</td>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.id_ }}</td>
|
||||||
|
<td>{{ data.mem }}</td>
|
||||||
|
<td>{{ data.cpu }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uuid }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="assets/js/jquery.min.js"></script>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
182
templates/host.html
Normal file
182
templates/host.html
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>Hosts Info</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="{{ STATIC_PREFIX }}assets/css/Bootstrap-4---Table-Fixed-Header.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="col ">
|
||||||
|
<h3 class="text-center">Host Detail</h4>
|
||||||
|
<hr>
|
||||||
|
<div class="tableFixHead">
|
||||||
|
<table class="table">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">S.No.</th>
|
||||||
|
<th scope="col">Host Name</th>
|
||||||
|
<th scope="col">CPUs<br>(Total / Free)</th>
|
||||||
|
<th scope="col">Memory(MiB)<br>(Total / Free)</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">URI</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="table-body"></tbody>
|
||||||
|
|
||||||
|
<!--tr id="${data.hostname}">
|
||||||
|
<th scope="row">1</th>
|
||||||
|
<td>CentOS-8-Demo-a-1</td>
|
||||||
|
<td>4 / 3</td>
|
||||||
|
<td>3800 / 2800</td>
|
||||||
|
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
||||||
|
<td>qemu:///system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">2</th>
|
||||||
|
<td>CentOS-8-Demo-b-2</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 3800</td>
|
||||||
|
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
||||||
|
<td>qemu+ssh:///system/something/user</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">3</th>
|
||||||
|
<td>CentOS-8-Demo-g-0</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 2726</td>
|
||||||
|
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
||||||
|
<td>qemu:///system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">4</th>
|
||||||
|
<td>Win2k19-Demo-gz-30</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 2726</td>
|
||||||
|
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
||||||
|
<td>qemu+ssh://pool@check2.homedevops/system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">5</th>
|
||||||
|
<td>Linux-20-04-rc-302321</td>
|
||||||
|
<td>8 / 5</td>
|
||||||
|
<td>8800 / 6726</td>
|
||||||
|
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
||||||
|
<td>qemu:///system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">6</th>
|
||||||
|
<td>Win2k19-Demo-gz-30</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 2726</td>
|
||||||
|
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
||||||
|
<td>qemu+ssh://pool@reserve.homedevops/system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">7</th>
|
||||||
|
<td>Win2k19-Demo-gz-30</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 2726</td>
|
||||||
|
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
||||||
|
<td>qemu:///system</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="${data.hostname}">
|
||||||
|
<th scope="row">8</th>
|
||||||
|
<td>Win2k19-Demo-gz-30</td>
|
||||||
|
<td>4 / 2</td>
|
||||||
|
<td>3800 / 2726</td>
|
||||||
|
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
||||||
|
<td>qemu+ssh://pool@check3.homedevops/system</td>
|
||||||
|
</tr-->
|
||||||
|
|
||||||
|
{% if host_data %}
|
||||||
|
{% for data in host_data %}
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="${{ data.hostname }}">
|
||||||
|
<th scope="row">{{ data.sn }}</th>
|
||||||
|
<td>{{ data.hostname }}</td>
|
||||||
|
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
||||||
|
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
||||||
|
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
||||||
|
<td>{{ data.uri }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="assets/js/jquery.min.js"></script>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
804
templates/manage.html
Normal file
804
templates/manage.html
Normal file
|
@ -0,0 +1,804 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Manage Hosts</title>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto|Varela+Round">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
color: #566787;
|
||||||
|
background: #74ebd5;
|
||||||
|
background: -webkit-linear-gradient(to right, #74ebd5, #ACB6E5);
|
||||||
|
background: linear-gradient(to right, #74ebd5, #ACB6E5);
|
||||||
|
font-family: 'Varela Round', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
.table-wrapper {
|
||||||
|
min-width: 1000px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0,0,0,.05);
|
||||||
|
}
|
||||||
|
.table-title {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
background: #435d7d;
|
||||||
|
color: #fff;
|
||||||
|
padding: 16px 30px;
|
||||||
|
margin: -20px -25px 10px;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
.table-title h2 {
|
||||||
|
margin: 5px 0 0;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.table-title .btn-group {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.table-title .btn {
|
||||||
|
color: #fff;
|
||||||
|
float: right;
|
||||||
|
font-size: 13px;
|
||||||
|
border: none;
|
||||||
|
min-width: 50px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: none;
|
||||||
|
outline: none !important;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.table-title .btn i {
|
||||||
|
float: left;
|
||||||
|
font-size: 21px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.table-title .btn span {
|
||||||
|
float: left;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
table.table tr th, table.table tr td {
|
||||||
|
border-color: #e9e9e9;
|
||||||
|
padding: 12px 15px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
table.table tr th:first-child {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
table.table tr th:last-child {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
table.table-striped tbody tr:nth-of-type(odd) {
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
}
|
||||||
|
table.table-striped.table-hover tbody tr:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
table.table th i {
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
table.table td:last-child i {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 22px;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
table.table td a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #566787;
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
table.table td a:hover {
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
table.table td a.edit {
|
||||||
|
color: #FFC107;
|
||||||
|
}
|
||||||
|
table.table td a.delete {
|
||||||
|
color: #F44336;
|
||||||
|
}
|
||||||
|
table.table td i {
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
table.table .avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
float: right;
|
||||||
|
margin: 0 0 5px;
|
||||||
|
}
|
||||||
|
.pagination li a {
|
||||||
|
border: none;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 30px;
|
||||||
|
min-height: 30px;
|
||||||
|
color: #999;
|
||||||
|
margin: 0 2px;
|
||||||
|
line-height: 30px;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
.pagination li a:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.pagination li.active a, .pagination li.active a.page-link {
|
||||||
|
background: #03A9F4;
|
||||||
|
}
|
||||||
|
.pagination li.active a:hover {
|
||||||
|
background: #0397d6;
|
||||||
|
}
|
||||||
|
.pagination li.disabled i {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
.pagination li i {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-top: 6px
|
||||||
|
}
|
||||||
|
.hint-text {
|
||||||
|
float: left;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
/* Custom checkbox */
|
||||||
|
.custom-checkbox {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.custom-checkbox input[type="checkbox"] {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
margin: 5px 0 0 3px;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
.custom-checkbox label:before{
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
.custom-checkbox label:before {
|
||||||
|
content: '';
|
||||||
|
margin-right: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: text-top;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.custom-checkbox input[type="checkbox"]:checked + label:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 6px;
|
||||||
|
top: 3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 11px;
|
||||||
|
border: solid #000;
|
||||||
|
border-width: 0 3px 3px 0;
|
||||||
|
transform: inherit;
|
||||||
|
z-index: 3;
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
}
|
||||||
|
.custom-checkbox input[type="checkbox"]:checked + label:before {
|
||||||
|
border-color: #03A9F4;
|
||||||
|
background: #03A9F4;
|
||||||
|
}
|
||||||
|
.custom-checkbox input[type="checkbox"]:checked + label:after {
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
.custom-checkbox input[type="checkbox"]:disabled + label:before {
|
||||||
|
color: #b8b8b8;
|
||||||
|
cursor: auto;
|
||||||
|
box-shadow: none;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
/* Modal styles */
|
||||||
|
.modal .modal-dialog {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
.modal .modal-header, .modal .modal-body, .modal .modal-footer {
|
||||||
|
padding: 20px 30px;
|
||||||
|
}
|
||||||
|
.modal .modal-content {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.modal .modal-footer {
|
||||||
|
background: #ecf0f1;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
}
|
||||||
|
.modal .modal-title {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.modal .form-control {
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: #dddddd;
|
||||||
|
}
|
||||||
|
.modal textarea.form-control {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.modal .btn {
|
||||||
|
border-radius: 2px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.modal form label {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay{
|
||||||
|
position: absolute;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: black;
|
||||||
|
opacity: .5;
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
|
||||||
|
border: 10px solid #f3f3f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 10px solid #4e729e;
|
||||||
|
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
-webkit-animation: spin .4s linear infinite; /* Safari */
|
||||||
|
animation: spin .4s linear infinite;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safari */
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
//window.onload= function(){
|
||||||
|
// Activate tooltip
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
|
// Select/Deselect checkboxes
|
||||||
|
var checkbox = $('table tbody input[type="checkbox"]');
|
||||||
|
$("#selectAll").click(function(){
|
||||||
|
if(this.checked){
|
||||||
|
checkbox.each(function(){
|
||||||
|
this.checked = true;
|
||||||
|
});
|
||||||
|
} else{
|
||||||
|
checkbox.each(function(){
|
||||||
|
this.checked = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
checkbox.click(function(){
|
||||||
|
if(!this.checked){
|
||||||
|
$("#selectAll").prop("checked", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var actions = '<td>\
|
||||||
|
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>\
|
||||||
|
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>\
|
||||||
|
</td>'
|
||||||
|
var row = '<tr>';
|
||||||
|
|
||||||
|
$("#add-hostname").change((obj)=>{
|
||||||
|
console.log(obj.target.value)
|
||||||
|
|
||||||
|
$.post({
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify({"hostname": obj.target.value}),
|
||||||
|
url: "/query_host",
|
||||||
|
beforeSend: ()=>{$("#overlay").css("display", "flex");},
|
||||||
|
complete: ()=>{$("#overlay").css("display", "none");},
|
||||||
|
error:function () {
|
||||||
|
alert('An error occured while querying host. Please try again later.');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
|
||||||
|
if(response.done){
|
||||||
|
$("table").append(row)
|
||||||
|
|
||||||
|
input= $("#add-modal").find("input");
|
||||||
|
input.each(function(index){
|
||||||
|
var elem = this
|
||||||
|
if(elem.type=="checkbox"){
|
||||||
|
if(response.gpu){
|
||||||
|
$(elem)[0].checked = true
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$(elem)[0].checked= false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
elem.value= response.data[index]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#add")[0].disabled = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert(`Error: ${response.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input:checkbox").on("click", false);
|
||||||
|
|
||||||
|
$("#add-form").submit((e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
$("#add")[0].disabled = true;
|
||||||
|
|
||||||
|
if($("#add-form")[0].checkValidity()){
|
||||||
|
|
||||||
|
var requestData={
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
|
||||||
|
par = $("#add").parent().parent();
|
||||||
|
bdy= par.find(".modal-body");
|
||||||
|
input= bdy.find("input");
|
||||||
|
input.each(function(){
|
||||||
|
var elem = this
|
||||||
|
if(elem.type=="checkbox"){
|
||||||
|
if(elem.checked){
|
||||||
|
requestData.gpu= "Yes"
|
||||||
|
row += '<td class="text-success">Yes</td>'
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
requestData.gpu= "No"
|
||||||
|
row += '<td class="text-secondary">No</td>'
|
||||||
|
}
|
||||||
|
elem.checked= false;
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
requestData.data.push(elem.value)
|
||||||
|
row += `<td>${elem.value}</td>`
|
||||||
|
elem.value=""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestData.power= $(".sel-add").find(":selected").text()
|
||||||
|
row += `<td>${$(".sel-add").find(":selected").text()}</td>`
|
||||||
|
row += actions + '</tr>';
|
||||||
|
|
||||||
|
//make the AJAX call over POST
|
||||||
|
|
||||||
|
$.post({
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify(requestData),
|
||||||
|
url: "/add_host",
|
||||||
|
error:function () {
|
||||||
|
alert('An error occured while adding host. Please try again later.');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
|
||||||
|
if(response.done){
|
||||||
|
$("table").append(row)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert(`Error: ${response.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
row= '<tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$('.sel-add option:contains("Always On")').prop('selected', true)
|
||||||
|
|
||||||
|
$("#addHostModal").modal('toggle');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var curr_idx= -1
|
||||||
|
$("table").on('click', 'a.edit', function(){
|
||||||
|
//console.log(idx);
|
||||||
|
td= $(this).parents("tr").find("td:not(:last-child)")
|
||||||
|
curr_idx= $(this).parents("tr").index()
|
||||||
|
data= []
|
||||||
|
td.each(function(){
|
||||||
|
data.push($(this).text())
|
||||||
|
});
|
||||||
|
|
||||||
|
bdy= $("#edit-form").find(".modal-body")
|
||||||
|
input= bdy.find("input");
|
||||||
|
input.each(function(index){
|
||||||
|
var elem = this
|
||||||
|
if(elem.type=="checkbox"){
|
||||||
|
if(data[5]=="Yes"){
|
||||||
|
$(elem).prop('checked', true)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$(elem).prop('checked', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
elem.value= data[index]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(`.sel-edit option:contains(${data[6]})`).prop('selected', true)
|
||||||
|
$("#editHostModal").modal('toggle');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#edit-form").submit((e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if($("#edit-form")[0].checkValidity()){
|
||||||
|
var requestData={
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
|
||||||
|
par = $("#save").parent().parent();
|
||||||
|
bdy= par.find(".modal-body");
|
||||||
|
input= bdy.find("input");
|
||||||
|
input.each(function(){
|
||||||
|
var elem = this //.children[1];
|
||||||
|
if(elem.type=="checkbox"){
|
||||||
|
if(elem.checked){
|
||||||
|
requestData.gpu= "Yes"
|
||||||
|
row += '<td class="text-success">Yes</td>'
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
requestData.gpu= "No"
|
||||||
|
row += '<td class="text-secondary">No</td>'
|
||||||
|
}
|
||||||
|
elem.checked= false;
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
requestData.data.push(elem.value)
|
||||||
|
row += `<td>${elem.value}</td>`
|
||||||
|
elem.value=""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestData.power= $(".sel-edit").find(":selected").text()
|
||||||
|
row += `<td>${$(".sel-edit").find(":selected").text()}</td>`
|
||||||
|
row += actions + '</tr>';
|
||||||
|
|
||||||
|
$.post({
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify(requestData),
|
||||||
|
url: "/edit_host",
|
||||||
|
error:function () {
|
||||||
|
alert('An error occured while updating host. Please try again later.');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
|
||||||
|
if(response.done){
|
||||||
|
|
||||||
|
$("table tbody tr").eq(curr_idx).remove()
|
||||||
|
if(curr_idx==0){
|
||||||
|
$("table tbody tr:first").before(row)
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("table tbody tr").eq(curr_idx-1).after(row)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert(`Error: ${response.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
row= '<tr>'
|
||||||
|
$('.sel-edit option:contains("Always On")').prop('selected', true)
|
||||||
|
curr_idx= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$("#editHostModal").modal('toggle');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var del_idx= -1
|
||||||
|
$("table").on('click', 'a.delete', function(){
|
||||||
|
hname= $(this).parents("tr").find("td:first").text()
|
||||||
|
del_idx = $(this).parents("tr").index()
|
||||||
|
bdy= $("#edit-form").find(".modal-body")
|
||||||
|
p= bdy.find(".info")
|
||||||
|
$("#info-text").text(`Are you sure you want to delete ${hname}?`)
|
||||||
|
|
||||||
|
$("#deleteHostModal").modal('toggle')
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#delete-form").submit((e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
requestData = {
|
||||||
|
hostname: $("table tbody tr").eq(del_idx).find("td:first").text()
|
||||||
|
}
|
||||||
|
$.post({
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify(requestData),
|
||||||
|
url: "/delete_host",
|
||||||
|
error:function () {
|
||||||
|
alert('An error occured while deleting host. Please try again later.');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
|
||||||
|
$("table tbody tr").eq(del_idx).remove()
|
||||||
|
del_idx= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$("#deleteHostModal").modal('toggle')
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<h2>Manage <b>Hosts</b></h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<a href="#addHostModal" class="btn btn-success" data-toggle="modal"><i class="material-icons"></i> <span>Add New Host</span></a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-hover" id="main-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<!--th>
|
||||||
|
<span class="custom-checkbox">
|
||||||
|
<input type="checkbox" id="selectAll">
|
||||||
|
<label for="selectAll"></label>
|
||||||
|
</span>
|
||||||
|
</th-->
|
||||||
|
<th>HostName</th>
|
||||||
|
<th>CPUs</th>
|
||||||
|
<th>Memory</th>
|
||||||
|
<th>Ip Address</th>
|
||||||
|
<th>Mac Address</th>
|
||||||
|
<th>GPU</th>
|
||||||
|
<th>Power Option</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Host1</td>
|
||||||
|
<td>4</td>
|
||||||
|
<td>7826</td>
|
||||||
|
<td>34.10.20.0</td>
|
||||||
|
<td>9C-35-5B-5F-4C-D7</td>
|
||||||
|
<td class="text-secondary">No</td>
|
||||||
|
<td>Always On</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
||||||
|
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Host2</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>7826</td>
|
||||||
|
<td>34.10.20.0</td>
|
||||||
|
<td>9C-35-5B-5F-4C-D7</td>
|
||||||
|
<td class="text-success">Yes</td>
|
||||||
|
<td>Wake on LAN</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
||||||
|
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Host3</td>
|
||||||
|
<td>4</td>
|
||||||
|
<td>4096</td>
|
||||||
|
<td>34.10.20.0</td>
|
||||||
|
<td>9C-35-5B-5F-4C-D7</td>
|
||||||
|
<td class="text-secondary">No</td>
|
||||||
|
<td>Tasmota</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
||||||
|
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Add Modal -->
|
||||||
|
<div id="addHostModal" class="modal fade">
|
||||||
|
<div id="overlay">
|
||||||
|
<div class="loader"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form id="add-form">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Add Host</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="add-modal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>HostName</label>
|
||||||
|
<input id="add-hostname" type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>CPUs</label>
|
||||||
|
<input type="number" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Memory</label>
|
||||||
|
<input type="number" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Ip Address</label>
|
||||||
|
<input type="text" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Mac Address</label>
|
||||||
|
<input type="text" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>GPU Available </label>
|
||||||
|
<input type="checkbox" id="selectAll">
|
||||||
|
<!--span class="custom-checkbox">
|
||||||
|
<input type="checkbox" id="selectAll">
|
||||||
|
<label for="selectAll"></label>
|
||||||
|
</span-->
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Power Option</label>
|
||||||
|
<select class="browser-default custom-select sel-add" type="select">
|
||||||
|
<option selected>Always On</option>
|
||||||
|
<option >Wake on LAN</option>
|
||||||
|
<option >Tasmota</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
||||||
|
<input type="submit" class="btn btn-success" value="Add" id="add" disabled>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal HTML -->
|
||||||
|
<div id="editHostModal" class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form id="edit-form">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Edit Host</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>HostName</label>
|
||||||
|
<input type="text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>CPUs</label>
|
||||||
|
<input type="number" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Memory</label>
|
||||||
|
<input type="number" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Ip Address</label>
|
||||||
|
<input type="text" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Mac Address</label>
|
||||||
|
<input type="text" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>GPU Available </label>
|
||||||
|
<input type="checkbox" id="selectAll" disabled>
|
||||||
|
<!--span class="custom-checkbox">
|
||||||
|
<input type="checkbox" id="selectAll">
|
||||||
|
<label for="selectAll"></label>
|
||||||
|
</span-->
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Power Option</label>
|
||||||
|
<select class="browser-default custom-select sel-edit" type="select">
|
||||||
|
<option selected>Always On</option>
|
||||||
|
<option >Wake on LAN</option>
|
||||||
|
<option >Tasmota</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
||||||
|
<input type="submit" class="btn btn-success" value="Save" id="save">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Delete Modal HTML -->
|
||||||
|
<div id="deleteHostModal" class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form id="delete-form">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Delete Host</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="info-text"></p>
|
||||||
|
<p class="text-warning"><small>This action cannot be undone.</small></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
||||||
|
<input type="submit" class="btn btn-danger" value="Delete" id=delete>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
198
virtpool.py
Normal file → Executable file
198
virtpool.py
Normal file → Executable file
|
@ -7,33 +7,49 @@ import sys
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import libvirt
|
import libvirt
|
||||||
import json
|
import json
|
||||||
import threading
|
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
|
|
||||||
pool = ThreadPool(processes=1)
|
pool = ThreadPool(processes=1)
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
PATH = os.path.dirname(os.path.abspath(__file__))
|
PATH = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read("config.ini")
|
||||||
|
|
||||||
TEMPLATE_ENVIRONMENT = Environment(
|
TEMPLATE_ENVIRONMENT = Environment(
|
||||||
autoescape=False,
|
autoescape=False,
|
||||||
loader=FileSystemLoader(os.path.join(PATH, 'templates')),
|
loader=FileSystemLoader(os.path.join(PATH, "templates")),
|
||||||
trim_blocks=False)
|
trim_blocks=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TEMPLATE_ENVIRONMENT.globals['STATIC_PREFIX'] = '/templates/'
|
||||||
|
|
||||||
#TEMPLATE_ENVIRONMENT.globals['STATIC_PREFIX'] = '/templates/'
|
|
||||||
|
|
||||||
def render_template(template_filename, context):
|
def render_template(template_filename, context):
|
||||||
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
|
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
|
||||||
|
|
||||||
|
|
||||||
class Client():
|
class Client:
|
||||||
"""Main manager class"""
|
"""Main manager class"""
|
||||||
uris = [
|
|
||||||
|
""" uris = [
|
||||||
#'qemu+ssh://pool@reserve.homedevops/system',
|
#'qemu+ssh://pool@reserve.homedevops/system',
|
||||||
#'qemu+ssh://root@check2.homedevops/system',
|
#'qemu+ssh://root@check2.homedevops/system',
|
||||||
#'qemu+ssh://root@check3.homedevops/system'
|
#'qemu+ssh://root@check3.homedevops/system'
|
||||||
'qemu:///system'
|
'qemu:///system'
|
||||||
]
|
] """
|
||||||
|
|
||||||
|
host_str = config["host"]["available_hosts"]
|
||||||
|
uris = [
|
||||||
|
each.strip()
|
||||||
|
for each in host_str.replace("'", "").replace('"', "").split(",")
|
||||||
|
if each.strip()
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.status = {}
|
self.status = {}
|
||||||
|
@ -58,14 +74,12 @@ class Client():
|
||||||
for did in domain_ids:
|
for did in domain_ids:
|
||||||
dom = c.lookupByID(did)
|
dom = c.lookupByID(did)
|
||||||
state, maxmem, mem2, cpus, cput = dom.info()
|
state, maxmem, mem2, cpus, cput = dom.info()
|
||||||
usedmem += maxmem/1024
|
usedmem += maxmem / 1024
|
||||||
usedcpus += cpus
|
usedcpus += cpus
|
||||||
|
|
||||||
out += "{}: {}/{} CPUs free,{}MiB/{}MiB memory free<br/>\n".format(
|
out += "{}: {}/{} CPUs free,{}MiB/{}MiB memory free<br/>\n".format(
|
||||||
n,
|
n, cpus - usedcpus, cpus, str((mem - usedmem)), str(mem)
|
||||||
cpus - usedcpus, cpus,
|
)
|
||||||
str((mem - usedmem)),
|
|
||||||
str(mem))
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
@ -77,21 +91,21 @@ class Client():
|
||||||
domain_type="kvm",
|
domain_type="kvm",
|
||||||
memory_unit="MiB",
|
memory_unit="MiB",
|
||||||
memory=1024,
|
memory=1024,
|
||||||
current_memory= 1024,
|
current_memory=1024,
|
||||||
vcpu=2,
|
vcpu=2,
|
||||||
arch="x86_64",
|
arch="x86_64",
|
||||||
machine="pc-q35-4.2",
|
machine="pc-q35-4.2",
|
||||||
offset="utc",
|
offset="utc",
|
||||||
emulator="/usr/bin/qemu-system-x86_64"
|
emulator="/usr/bin/qemu-system-x86_64",
|
||||||
):
|
):
|
||||||
|
|
||||||
""" create a VM with given specs
|
"""create a VM with given specs
|
||||||
boot it and return the ID for future ref
|
boot it and return the ID for future ref
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#TODO: Check host resources(cpus, mem, etc.) before creating a VM
|
# TODO: Check host resources(cpus, mem, etc.) before creating a VM
|
||||||
|
|
||||||
param= {
|
param = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"uuid": str(uuid.uuid4()),
|
"uuid": str(uuid.uuid4()),
|
||||||
"iso_source": iso_source,
|
"iso_source": iso_source,
|
||||||
|
@ -100,33 +114,30 @@ class Client():
|
||||||
"memory_unit": memory_unit,
|
"memory_unit": memory_unit,
|
||||||
"memory": memory,
|
"memory": memory,
|
||||||
"current_memory": current_memory,
|
"current_memory": current_memory,
|
||||||
"vcpu":vcpu,
|
"vcpu": vcpu,
|
||||||
"arch": arch,
|
"arch": arch,
|
||||||
"machine": machine,
|
"machine": machine,
|
||||||
"offset": offset,
|
"offset": offset,
|
||||||
"emulator": emulator,
|
"emulator": emulator,
|
||||||
}
|
}
|
||||||
|
|
||||||
context= {
|
context = {"args": param}
|
||||||
'args': param
|
|
||||||
}
|
|
||||||
|
|
||||||
xml= render_template("config.tmpl.xml", context)
|
xml = render_template("config.tmpl.xml", context)
|
||||||
#print(xml)
|
# print(xml)
|
||||||
|
|
||||||
host= self.connections[self.uris[0]]
|
host = self.connections[self.uris[0]]
|
||||||
if host:
|
if host:
|
||||||
async_result = pool.apply_async(self.__create_vm, (host, xml))
|
async_result = pool.apply_async(self.__create_vm, (host, xml))
|
||||||
return async_result.get()
|
return async_result.get()
|
||||||
return "No host available"
|
return "No host available"
|
||||||
|
|
||||||
|
|
||||||
def __create_vm(self, host, xml):
|
def __create_vm(self, host, xml):
|
||||||
try:
|
try:
|
||||||
vm= host.createXML(xml)
|
vm = host.createXML(xml)
|
||||||
return json.dumps({"uuid":vm.UUIDString(), "id":vm.ID(), "status":1})
|
return json.dumps({"uuid": vm.UUIDString(), "id": vm.ID(), "status": 1})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({"err":str(e), "status":0})
|
return json.dumps({"err": str(e), "status": 0})
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def gethost(self):
|
def gethost(self):
|
||||||
|
@ -134,35 +145,35 @@ class Client():
|
||||||
It'll show relevant info about all available hosts
|
It'll show relevant info about all available hosts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
host_data= []
|
host_data = []
|
||||||
|
|
||||||
for i, each in enumerate(self.uris):
|
for i, each in enumerate(self.uris):
|
||||||
tmp= {}
|
tmp = {}
|
||||||
c= self.connections[each]
|
c = self.connections[each]
|
||||||
nodeinfo = c.getInfo()
|
nodeinfo = c.getInfo()
|
||||||
mem = nodeinfo[1]
|
mem = nodeinfo[1]
|
||||||
cpus = nodeinfo[2]
|
cpus = nodeinfo[2]
|
||||||
usedmem = 0
|
usedmem = 0
|
||||||
usedcpus = 0
|
usedcpus = 0
|
||||||
|
|
||||||
tmp["sn"]= i+1
|
tmp["sn"] = i + 1
|
||||||
tmp["hostname"]= c.getHostname()
|
tmp["hostname"] = c.getHostname()
|
||||||
tmp["status"]= "Alive" if c.isAlive() else "Dead"
|
tmp["status"] = "Alive" if c.isAlive() else "Dead"
|
||||||
tmp["uri"]= c.getURI()
|
tmp["uri"] = c.getURI()
|
||||||
tmp["cpu"]= {}
|
tmp["cpu"] = {}
|
||||||
tmp["mem"]= {}
|
tmp["mem"] = {}
|
||||||
|
|
||||||
domain_ids = c.listDomainsID()
|
domain_ids = c.listDomainsID()
|
||||||
for did in domain_ids:
|
for did in domain_ids:
|
||||||
dom = c.lookupByID(did)
|
dom = c.lookupByID(did)
|
||||||
state, maxmem, mem2, cpus2, cput = dom.info()
|
state, maxmem, mem2, cpus2, cput = dom.info()
|
||||||
usedmem += maxmem/1024
|
usedmem += maxmem / 1024
|
||||||
usedcpus += cpus2
|
usedcpus += cpus2
|
||||||
|
|
||||||
tmp["cpu"]["total"]= cpus
|
tmp["cpu"]["total"] = cpus
|
||||||
tmp["cpu"]["free"]= cpus - usedcpus
|
tmp["cpu"]["free"] = cpus - usedcpus
|
||||||
tmp["mem"]["total"]= mem
|
tmp["mem"]["total"] = mem
|
||||||
tmp["mem"]["free"]= int(mem - usedmem)
|
tmp["mem"]["free"] = int(mem - usedmem)
|
||||||
|
|
||||||
host_data.append(tmp)
|
host_data.append(tmp)
|
||||||
|
|
||||||
|
@ -174,51 +185,106 @@ class Client():
|
||||||
It'll show host-wise available guests and detailed info about them
|
It'll show host-wise available guests and detailed info about them
|
||||||
"""
|
"""
|
||||||
|
|
||||||
guest_data= []
|
guest_data = []
|
||||||
cnt=0
|
cnt = 0
|
||||||
for i, each in enumerate(self.uris):
|
for i, each in enumerate(self.uris):
|
||||||
c= self.connections[each]
|
c = self.connections[each]
|
||||||
for did in c.listDomainsID():
|
for did in c.listDomainsID():
|
||||||
dom= c.lookupByID(did)
|
dom = c.lookupByID(did)
|
||||||
tmp= {
|
tmp = {
|
||||||
"sn": cnt+1,
|
"sn": cnt + 1,
|
||||||
"guestname": dom.name(),
|
"guestname": dom.name(),
|
||||||
"hostname": c.getHostname(),
|
"hostname": c.getHostname(),
|
||||||
"id_": dom.ID(),
|
"id_": dom.ID(),
|
||||||
"mem": int(dom.info()[1]/1024),
|
"mem": int(dom.info()[1] / 1024),
|
||||||
"cpu": dom.info()[3],
|
"cpu": dom.info()[3],
|
||||||
"status": "Active" if dom.isActive() else "Inactive",
|
"status": "Active" if dom.isActive() else "Inactive",
|
||||||
"uuid": dom.UUIDString()
|
"uuid": dom.UUIDString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
guest_data.append(tmp)
|
guest_data.append(tmp)
|
||||||
cnt+=1
|
cnt += 1
|
||||||
|
|
||||||
return render_template("guest.html", {"guest_data": guest_data})
|
return render_template("guest.html", {"guest_data": guest_data})
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def manage(self):
|
||||||
|
# get available hosts from the database(if it's stored in one)
|
||||||
|
# and return the updated template
|
||||||
|
|
||||||
|
return render_template("manage.html", {})
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_in()
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
def query_host(self):
|
||||||
|
# make ajax call to query host
|
||||||
|
|
||||||
|
json_obj = cherrypy.request.json
|
||||||
|
print(json_obj)
|
||||||
|
import time
|
||||||
|
|
||||||
|
time.sleep(1) # simulate delay
|
||||||
|
return {
|
||||||
|
"data": [json_obj["hostname"], 4, 4096, "10.56.0.1", "9C-35-5B-5F-4C-D7"],
|
||||||
|
"gpu": True,
|
||||||
|
"done": True,
|
||||||
|
"msg": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_in()
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
def add_host(self):
|
||||||
|
# make ajax call to add host, maybe save it in a database
|
||||||
|
|
||||||
|
json_obj = cherrypy.request.json
|
||||||
|
print(json_obj)
|
||||||
|
return {"done": True, "msg": ""}
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_in()
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
def edit_host(self):
|
||||||
|
# make ajax call to edit host. Update it in database
|
||||||
|
|
||||||
|
json_obj = cherrypy.request.json
|
||||||
|
|
||||||
|
print(json_obj)
|
||||||
|
return {"done": True, "msg": ""}
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_in()
|
||||||
|
@cherrypy.tools.json_out()
|
||||||
|
def delete_host(self):
|
||||||
|
# make ajax call to delete host from database
|
||||||
|
|
||||||
|
json_obj = cherrypy.request.json
|
||||||
|
|
||||||
|
print(json_obj)
|
||||||
|
return {"done": True, "msg": ""}
|
||||||
|
|
||||||
def manage_host(self,host_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
ROOT = Client()
|
ROOT = Client()
|
||||||
CONF = os.path.join(os.path.dirname(__file__), 'site.conf')
|
CONF = os.path.join(os.path.dirname(__file__), "site.conf")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
# cherrypy.config.update(CONF)
|
# cherrypy.config.update(CONF)
|
||||||
cherrypy.server.socket_host = '127.0.0.1'
|
cherrypy.server.socket_host = config["server"].get("server_host", "127.0.0.1")
|
||||||
cherrypy.server.socket_port = 8080
|
cherrypy.server.socket_port = config["server"].getint("server_port", 8080)
|
||||||
ENGINE = cherrypy.engine
|
ENGINE = cherrypy.engine
|
||||||
# cherrypy.process.plugins.Daemonizer(engine).subscribe()
|
# cherrypy.process.plugins.Daemonizer(engine).subscribe()
|
||||||
# cherrypy.process.plugins.DropPrivileges(
|
# cherrypy.process.plugins.DropPrivileges(
|
||||||
# ENGINE, uid='cherrypy', gid='cherrypy').subscribe()
|
# ENGINE, uid='cherrypy', gid='cherrypy').subscribe()
|
||||||
cherrypy.tree.mount(Client(),
|
cherrypy.tree.mount(
|
||||||
'/',
|
Client(),
|
||||||
config={
|
"/",
|
||||||
'/':{
|
config={
|
||||||
'tools.staticdir.on': True,
|
"/": {
|
||||||
'tools.staticdir.dir': PATH+"/templates"
|
"tools.staticdir.on": True,
|
||||||
}
|
"tools.staticdir.dir": PATH + "/templates",
|
||||||
}
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if hasattr(ENGINE, "signal_handler"):
|
if hasattr(ENGINE, "signal_handler"):
|
||||||
ENGINE.signal_handler.subscribe()
|
ENGINE.signal_handler.subscribe()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user