forked from shaba/openuds
Formating & fixing type checkings
This commit is contained in:
@ -49,6 +49,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""
|
"""
|
||||||
Module to manage oVirt connections using ovirtsdk.
|
Module to manage oVirt connections using ovirtsdk.
|
||||||
@ -59,11 +60,14 @@ class Client:
|
|||||||
This can waste a lot of time, so use of cache here is more than important to achieve aceptable performance.
|
This can waste a lot of time, so use of cache here is more than important to achieve aceptable performance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cached_api: typing.ClassVar[typing.Optional[ovirt.Connection]] = None
|
cached_api: typing.ClassVar[typing.Optional[ovirt.Connection]] = None
|
||||||
cached_api_key: typing.ClassVar[typing.Optional[str]] = None
|
cached_api_key: typing.ClassVar[typing.Optional[str]] = None
|
||||||
|
|
||||||
CACHE_TIME_LOW = 60 * 5 # Cache time for requests are 5 minutes by default
|
CACHE_TIME_LOW = 60 * 5 # Cache time for requests are 5 minutes by default
|
||||||
CACHE_TIME_HIGH = 60 * 30 # Cache time for requests that are less probable to change (as cluster perteinance of a machine)
|
CACHE_TIME_HIGH = (
|
||||||
|
60 * 30
|
||||||
|
) # Cache time for requests that are less probable to change (as cluster perteinance of a machine)
|
||||||
|
|
||||||
_host: str
|
_host: str
|
||||||
_username: str
|
_username: str
|
||||||
@ -79,7 +83,9 @@ class Client:
|
|||||||
Returns:
|
Returns:
|
||||||
The cache key, taking into consideration the prefix
|
The cache key, taking into consideration the prefix
|
||||||
"""
|
"""
|
||||||
return "{}{}{}{}{}".format(prefix, self._host, self._username, self._password, self._timeout)
|
return "{}{}{}{}{}".format(
|
||||||
|
prefix, self._host, self._username, self._password, self._timeout
|
||||||
|
)
|
||||||
|
|
||||||
def __getApi(self) -> ovirt.Connection:
|
def __getApi(self) -> ovirt.Connection:
|
||||||
"""
|
"""
|
||||||
@ -102,7 +108,13 @@ class Client:
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
Client.cached_api_key = aKey
|
Client.cached_api_key = aKey
|
||||||
Client.cached_api = ovirt.Connection(url='https://' + self._host + '/ovirt-engine/api', username=self._username, password=self._password, timeout=self._timeout, insecure=True) # , debug=True, log=logger )
|
Client.cached_api = ovirt.Connection(
|
||||||
|
url='https://' + self._host + '/ovirt-engine/api',
|
||||||
|
username=self._username,
|
||||||
|
password=self._password,
|
||||||
|
timeout=self._timeout,
|
||||||
|
insecure=True,
|
||||||
|
) # , debug=True, log=logger )
|
||||||
|
|
||||||
return Client.cached_api
|
return Client.cached_api
|
||||||
except:
|
except:
|
||||||
@ -111,7 +123,14 @@ class Client:
|
|||||||
Client.cached_api_key = None
|
Client.cached_api_key = None
|
||||||
raise Exception("Can't connet to server at {}".format(self._host))
|
raise Exception("Can't connet to server at {}".format(self._host))
|
||||||
|
|
||||||
def __init__(self, host: str, username: str, password: str, timeout: typing.Union[str, int], cache: 'Cache'):
|
def __init__(
|
||||||
|
self,
|
||||||
|
host: str,
|
||||||
|
username: str,
|
||||||
|
password: str,
|
||||||
|
timeout: typing.Union[str, int],
|
||||||
|
cache: 'Cache',
|
||||||
|
):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
@ -135,7 +154,9 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return True, 'Test successfully passed'
|
return True, 'Test successfully passed'
|
||||||
|
|
||||||
def getVms(self, force: bool = False) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
def getVms(
|
||||||
|
self, force: bool = False
|
||||||
|
) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Obtains the list of machines inside ovirt that do aren't part of uds
|
Obtains the list of machines inside ovirt that do aren't part of uds
|
||||||
|
|
||||||
@ -161,7 +182,7 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vms: typing.Iterable[typing.Any] = api.system_service().vms_service().list()
|
vms: typing.Iterable[typing.Any] = api.system_service().vms_service().list() # type: ignore
|
||||||
|
|
||||||
logger.debug('oVirt VMS: %s', vms)
|
logger.debug('oVirt VMS: %s', vms)
|
||||||
|
|
||||||
@ -172,7 +193,14 @@ class Client:
|
|||||||
pair = [vm.usb.enabled, vm.usb.type.value]
|
pair = [vm.usb.enabled, vm.usb.type.value]
|
||||||
except Exception:
|
except Exception:
|
||||||
pair = [False, '']
|
pair = [False, '']
|
||||||
res.append({'name': vm.name, 'id': vm.id, 'cluster_id': vm.cluster.id, 'usb': pair})
|
res.append(
|
||||||
|
{
|
||||||
|
'name': vm.name,
|
||||||
|
'id': vm.id,
|
||||||
|
'cluster_id': vm.cluster.id,
|
||||||
|
'usb': pair,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self._cache.put(vmsKey, res, Client.CACHE_TIME_LOW)
|
self._cache.put(vmsKey, res, Client.CACHE_TIME_LOW)
|
||||||
|
|
||||||
@ -181,7 +209,9 @@ class Client:
|
|||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
def getClusters(self, force: bool = False) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
def getClusters(
|
||||||
|
self, force: bool = False
|
||||||
|
) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Obtains the list of clusters inside ovirt
|
Obtains the list of clusters inside ovirt
|
||||||
|
|
||||||
@ -208,7 +238,7 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
clusters = api.system_service().clusters_service().list()
|
clusters: typing.List[typing.Any] = api.system_service().clusters_service().list() # type: ignore
|
||||||
|
|
||||||
res: typing.List[typing.MutableMapping[str, typing.Any]] = []
|
res: typing.List[typing.MutableMapping[str, typing.Any]] = []
|
||||||
|
|
||||||
@ -216,7 +246,11 @@ class Client:
|
|||||||
for cluster in clusters:
|
for cluster in clusters:
|
||||||
dc = cluster.data_center
|
dc = cluster.data_center
|
||||||
|
|
||||||
val = {'name': cluster.name, 'id': cluster.id, 'datacenter_id': dc.id if dc else None}
|
val = {
|
||||||
|
'name': cluster.name,
|
||||||
|
'id': cluster.id,
|
||||||
|
'datacenter_id': dc.id if dc else None,
|
||||||
|
}
|
||||||
|
|
||||||
# Updates cache info for every single cluster
|
# Updates cache info for every single cluster
|
||||||
clKey = self.__getKey('o-cluster' + cluster.id)
|
clKey = self.__getKey('o-cluster' + cluster.id)
|
||||||
@ -232,7 +266,9 @@ class Client:
|
|||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
def getClusterInfo(self, clusterId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getClusterInfo(
|
||||||
|
self, clusterId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the cluster info
|
Obtains the cluster info
|
||||||
|
|
||||||
@ -259,7 +295,7 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
c = api.system_service().clusters_service().service(clusterId).get()
|
c: typing.Any = api.system_service().clusters_service().service(clusterId).get() # type: ignore
|
||||||
|
|
||||||
dc = c.data_center
|
dc = c.data_center
|
||||||
|
|
||||||
@ -272,7 +308,9 @@ class Client:
|
|||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
def getDatacenterInfo(self, datacenterId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getDatacenterInfo(
|
||||||
|
self, datacenterId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the datacenter info
|
Obtains the datacenter info
|
||||||
|
|
||||||
@ -308,26 +346,35 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
datacenter_service = api.system_service().data_centers_service().service(datacenterId)
|
datacenter_service = (
|
||||||
d = datacenter_service.get()
|
api.system_service().data_centers_service().service(datacenterId)
|
||||||
|
)
|
||||||
|
d: typing.Any = datacenter_service.get() # type: ignore
|
||||||
|
|
||||||
storage = []
|
storage = []
|
||||||
for dd in datacenter_service.storage_domains_service().list():
|
for dd in typing.cast(typing.Iterable, datacenter_service.storage_domains_service().list()): # type: ignore
|
||||||
try:
|
try:
|
||||||
active = dd.status.value
|
active = dd.status.value
|
||||||
except Exception:
|
except Exception:
|
||||||
active = 'inactive'
|
active = 'inactive'
|
||||||
|
|
||||||
storage.append({'id': dd.id, 'name': dd.name, 'type': dd.type.value,
|
storage.append(
|
||||||
'available': dd.available, 'used': dd.used,
|
{
|
||||||
'active': active == 'active'})
|
'id': dd.id,
|
||||||
|
'name': dd.name,
|
||||||
|
'type': dd.type.value,
|
||||||
|
'available': dd.available,
|
||||||
|
'used': dd.used,
|
||||||
|
'active': active == 'active',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
res = {
|
res = {
|
||||||
'name': d.name,
|
'name': d.name,
|
||||||
'id': d.id,
|
'id': d.id,
|
||||||
'storage_type': d.local and 'local' or 'shared',
|
'storage_type': d.local and 'local' or 'shared',
|
||||||
'description': d.description,
|
'description': d.description,
|
||||||
'storage': storage
|
'storage': storage,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._cache.put(dcKey, res, Client.CACHE_TIME_HIGH)
|
self._cache.put(dcKey, res, Client.CACHE_TIME_HIGH)
|
||||||
@ -335,7 +382,9 @@ class Client:
|
|||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
def getStorageInfo(self, storageId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getStorageInfo(
|
||||||
|
self, storageId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the datacenter info
|
Obtains the datacenter info
|
||||||
|
|
||||||
@ -366,14 +415,14 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
dd = api.system_service().storage_domains_service().service(storageId).get()
|
dd: typing.Any = api.system_service().storage_domains_service().service(storageId).get() # type: ignore
|
||||||
|
|
||||||
res = {
|
res = {
|
||||||
'id': dd.id,
|
'id': dd.id,
|
||||||
'name': dd.name,
|
'name': dd.name,
|
||||||
'type': dd.type.value,
|
'type': dd.type.value,
|
||||||
'available': dd.available,
|
'available': dd.available,
|
||||||
'used': dd.used
|
'used': dd.used,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._cache.put(sdKey, res, Client.CACHE_TIME_LOW)
|
self._cache.put(sdKey, res, Client.CACHE_TIME_LOW)
|
||||||
@ -388,7 +437,7 @@ class Client:
|
|||||||
machineId: str,
|
machineId: str,
|
||||||
clusterId: str,
|
clusterId: str,
|
||||||
storageId: str,
|
storageId: str,
|
||||||
displayType: str
|
displayType: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||||
@ -404,7 +453,15 @@ class Client:
|
|||||||
Returns
|
Returns
|
||||||
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
||||||
"""
|
"""
|
||||||
logger.debug("n: %s, c: %s, vm: %s, cl: %s, st: %s, dt: %s", name, comments, machineId, clusterId, storageId, displayType)
|
logger.debug(
|
||||||
|
"n: %s, c: %s, vm: %s, cl: %s, st: %s, dt: %s",
|
||||||
|
name,
|
||||||
|
comments,
|
||||||
|
machineId,
|
||||||
|
clusterId,
|
||||||
|
storageId,
|
||||||
|
displayType,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lock.acquire(True)
|
lock.acquire(True)
|
||||||
@ -416,8 +473,8 @@ class Client:
|
|||||||
|
|
||||||
vms = api.system_service().vms_service().service(machineId)
|
vms = api.system_service().vms_service().service(machineId)
|
||||||
|
|
||||||
cluster = api.system_service().clusters_service().service(clusterId).get()
|
cluster: typing.Any = api.system_service().clusters_service().service(clusterId).get() # type: ignore
|
||||||
vm = vms.get()
|
vm: typing.Any = vms.get() # type: ignore
|
||||||
|
|
||||||
if vm is None:
|
if vm is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
@ -439,15 +496,12 @@ class Client:
|
|||||||
tcluster = ovirt.types.Cluster(id=cluster.id)
|
tcluster = ovirt.types.Cluster(id=cluster.id)
|
||||||
|
|
||||||
template = ovirt.types.Template(
|
template = ovirt.types.Template(
|
||||||
name=name,
|
name=name, vm=tvm, cluster=tcluster, description=comments
|
||||||
vm=tvm,
|
|
||||||
cluster=tcluster,
|
|
||||||
description=comments
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# display=display)
|
# display=display)
|
||||||
|
|
||||||
return api.system_service().templates_service().add(template).id
|
return api.system_service().templates_service().add(template).id # type: ignore
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
@ -469,7 +523,9 @@ class Client:
|
|||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = api.system_service().templates_service().service(templateId).get()
|
template: typing.Any = (
|
||||||
|
api.system_service().templates_service().service(templateId).get() # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
if not template:
|
if not template:
|
||||||
return 'removed'
|
return 'removed'
|
||||||
@ -490,7 +546,7 @@ class Client:
|
|||||||
displayType: str,
|
displayType: str,
|
||||||
usbType: str,
|
usbType: str,
|
||||||
memoryMB: int,
|
memoryMB: int,
|
||||||
guaranteedMB: int
|
guaranteedMB: int,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Deploys a virtual machine on selected cluster from selected template
|
Deploys a virtual machine on selected cluster from selected template
|
||||||
@ -507,8 +563,16 @@ class Client:
|
|||||||
Returns:
|
Returns:
|
||||||
Id of the machine being created form template
|
Id of the machine being created form template
|
||||||
"""
|
"""
|
||||||
logger.debug('Deploying machine with name "%s" from template %s at cluster %s with display %s and usb %s, memory %s and guaranteed %s',
|
logger.debug(
|
||||||
name, templateId, clusterId, displayType, usbType, memoryMB, guaranteedMB)
|
'Deploying machine with name "%s" from template %s at cluster %s with display %s and usb %s, memory %s and guaranteed %s',
|
||||||
|
name,
|
||||||
|
templateId,
|
||||||
|
clusterId,
|
||||||
|
displayType,
|
||||||
|
usbType,
|
||||||
|
memoryMB,
|
||||||
|
guaranteedMB,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
lock.acquire(True)
|
lock.acquire(True)
|
||||||
|
|
||||||
@ -519,18 +583,28 @@ class Client:
|
|||||||
cluster = ovirt.types.Cluster(id=clusterId)
|
cluster = ovirt.types.Cluster(id=clusterId)
|
||||||
template = ovirt.types.Template(id=templateId)
|
template = ovirt.types.Template(id=templateId)
|
||||||
|
|
||||||
if self._needsUsbFix is False and usbType in ('native',): # Removed 'legacy', from 3.6 is not used anymore, and from 4.0 not available
|
if self._needsUsbFix is False and usbType in (
|
||||||
|
'native',
|
||||||
|
): # Removed 'legacy', from 3.6 is not used anymore, and from 4.0 not available
|
||||||
usb = ovirt.types.Usb(enabled=True, type=ovirt.types.UsbType.NATIVE)
|
usb = ovirt.types.Usb(enabled=True, type=ovirt.types.UsbType.NATIVE)
|
||||||
else:
|
else:
|
||||||
usb = ovirt.types.Usb(enabled=False)
|
usb = ovirt.types.Usb(enabled=False)
|
||||||
|
|
||||||
memoryPolicy = ovirt.types.MemoryPolicy(guaranteed=guaranteedMB * 1024 * 1024)
|
memoryPolicy = ovirt.types.MemoryPolicy(
|
||||||
|
guaranteed=guaranteedMB * 1024 * 1024
|
||||||
|
)
|
||||||
par = ovirt.types.Vm(
|
par = ovirt.types.Vm(
|
||||||
name=name, cluster=cluster, template=template, description=comments,
|
name=name,
|
||||||
type=ovirt.types.VmType.DESKTOP, memory=memoryMB * 1024 * 1024, memory_policy=memoryPolicy,
|
cluster=cluster,
|
||||||
usb=usb) # display=display,
|
template=template,
|
||||||
|
description=comments,
|
||||||
|
type=ovirt.types.VmType.DESKTOP,
|
||||||
|
memory=memoryMB * 1024 * 1024,
|
||||||
|
memory_policy=memoryPolicy,
|
||||||
|
usb=usb,
|
||||||
|
) # display=display,
|
||||||
|
|
||||||
return api.system_service().vms_service().add(par).id
|
return api.system_service().vms_service().add(par).id # type: ignore
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
@ -546,7 +620,7 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
api.system_service().templates_service().service(templateId).remove()
|
api.system_service().templates_service().service(templateId).remove() # type: ignore
|
||||||
# This returns nothing, if it fails it raises an exception
|
# This returns nothing, if it fails it raises an exception
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
@ -573,12 +647,12 @@ class Client:
|
|||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vm = api.system_service().vms_service().service(machineId).get()
|
vm = api.system_service().vms_service().service(machineId).get() # type: ignore
|
||||||
|
|
||||||
if vm is None or vm.status is None:
|
if vm is None or vm.status is None: # type: ignore
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
return vm.status.value
|
return vm.status.value # type: ignore
|
||||||
except Exception: # machine not found
|
except Exception: # machine not found
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
@ -601,7 +675,9 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
|
|
||||||
if vmService.get() is None:
|
if vmService.get() is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
@ -625,7 +701,9 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
|
|
||||||
if vmService.get() is None:
|
if vmService.get() is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
@ -649,7 +727,9 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
|
|
||||||
if vmService.get() is None:
|
if vmService.get() is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
@ -673,7 +753,9 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
|
|
||||||
if vmService.get() is None:
|
if vmService.get() is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
@ -692,12 +774,16 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
|
|
||||||
if vmService.get() is None:
|
if vmService.get() is None:
|
||||||
raise Exception('Machine not found')
|
raise Exception('Machine not found')
|
||||||
|
|
||||||
nic = vmService.nics_service().list()[0] # If has no nic, will raise an exception (IndexError)
|
nic = vmService.nics_service().list()[
|
||||||
|
0
|
||||||
|
] # If has no nic, will raise an exception (IndexError)
|
||||||
nic.mac.address = macAddres
|
nic.mac.address = macAddres
|
||||||
nicService = vmService.nics_service().service(nic.id)
|
nicService = vmService.nics_service().service(nic.id)
|
||||||
nicService.update(nic)
|
nicService.update(nic)
|
||||||
@ -715,13 +801,15 @@ class Client:
|
|||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
usb = ovirt.types.Usb(enabled=True, type=ovirt.types.UsbType.NATIVE)
|
usb = ovirt.types.Usb(enabled=True, type=ovirt.types.UsbType.NATIVE)
|
||||||
vms = api.system_service().vms_service().service(machineId)
|
vms: typing.Any = api.system_service().vms_service().service(machineId)
|
||||||
vmu = ovirt.types.Vm(usb=usb)
|
vmu = ovirt.types.Vm(usb=usb)
|
||||||
vms.update(vmu)
|
vms.update(vmu)
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
def getConsoleConnection(
|
||||||
|
self, machineId: str
|
||||||
|
) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Gets the connetion info for the specified machine
|
Gets the connetion info for the specified machine
|
||||||
"""
|
"""
|
||||||
@ -729,7 +817,9 @@ class Client:
|
|||||||
lock.acquire(True)
|
lock.acquire(True)
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
vmService = api.system_service().vms_service().service(machineId)
|
vmService: typing.Any = (
|
||||||
|
api.system_service().vms_service().service(machineId)
|
||||||
|
)
|
||||||
vm = vmService.get()
|
vm = vmService.get()
|
||||||
|
|
||||||
if vm is None:
|
if vm is None:
|
||||||
@ -743,8 +833,17 @@ class Client:
|
|||||||
if display.certificate is not None:
|
if display.certificate is not None:
|
||||||
cert_subject = display.certificate.subject
|
cert_subject = display.certificate.subject
|
||||||
else:
|
else:
|
||||||
for i in api.system_service().hosts_service().list():
|
for i in typing.cast(
|
||||||
for k in api.system_service().hosts_service().service(i.id).nics_service().list():
|
typing.Iterable, api.system_service().hosts_service().list()
|
||||||
|
):
|
||||||
|
for k in typing.cast(
|
||||||
|
typing.Iterable,
|
||||||
|
api.system_service()
|
||||||
|
.hosts_service()
|
||||||
|
.service(i.id)
|
||||||
|
.nics_service() # type: ignore
|
||||||
|
.list(),
|
||||||
|
):
|
||||||
if k.ip.address == display.address:
|
if k.ip.address == display.address:
|
||||||
cert_subject = i.certificate.subject
|
cert_subject = i.certificate.subject
|
||||||
break
|
break
|
||||||
@ -759,10 +858,7 @@ class Client:
|
|||||||
'secure_port': display.secure_port,
|
'secure_port': display.secure_port,
|
||||||
'monitors': display.monitors,
|
'monitors': display.monitors,
|
||||||
'cert_subject': cert_subject,
|
'cert_subject': cert_subject,
|
||||||
'ticket': {
|
'ticket': {'value': ticket.value, 'expiry': ticket.expiry},
|
||||||
'value': ticket.value,
|
|
||||||
'expiry': ticket.expiry
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
# this list of conditions and the following disclaimer.
|
# this list of conditions and the following disclaimer.
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.u
|
||||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
# may be used to endorse or promote products derived from this software
|
# may be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
@ -49,7 +49,18 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry, opChangeMac = range(10)
|
(
|
||||||
|
opCreate,
|
||||||
|
opStart,
|
||||||
|
opStop,
|
||||||
|
opSuspend,
|
||||||
|
opRemove,
|
||||||
|
opWait,
|
||||||
|
opError,
|
||||||
|
opFinish,
|
||||||
|
opRetry,
|
||||||
|
opChangeMac,
|
||||||
|
) = range(10)
|
||||||
|
|
||||||
NO_MORE_NAMES = 'NO-NAME-ERROR'
|
NO_MORE_NAMES = 'NO-NAME-ERROR'
|
||||||
UP_STATES = ('up', 'reboot_in_progress', 'powering_up', 'restoring_state')
|
UP_STATES = ('up', 'reboot_in_progress', 'powering_up', 'restoring_state')
|
||||||
@ -66,6 +77,7 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
|||||||
The logic for managing ovirt deployments (user machines in this case) is here.
|
The logic for managing ovirt deployments (user machines in this case) is here.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : Recheck every six seconds by default (for task methods)
|
# : Recheck every six seconds by default (for task methods)
|
||||||
suggestedTime = 6
|
suggestedTime = 6
|
||||||
|
|
||||||
@ -100,15 +112,17 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
|||||||
"""
|
"""
|
||||||
Does nothing right here, we will use environment storage in this sample
|
Does nothing right here, we will use environment storage in this sample
|
||||||
"""
|
"""
|
||||||
return b'\1'.join([
|
return b'\1'.join(
|
||||||
|
[
|
||||||
b'v1',
|
b'v1',
|
||||||
self._name.encode('utf8'),
|
self._name.encode('utf8'),
|
||||||
self._ip.encode('utf8'),
|
self._ip.encode('utf8'),
|
||||||
self._mac.encode('utf8'),
|
self._mac.encode('utf8'),
|
||||||
self._vmid.encode('utf8'),
|
self._vmid.encode('utf8'),
|
||||||
self._reason.encode('utf8'),
|
self._reason.encode('utf8'),
|
||||||
pickle.dumps(self._queue, protocol=0)
|
pickle.dumps(self._queue, protocol=0),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -126,7 +140,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
|||||||
def getName(self) -> str:
|
def getName(self) -> str:
|
||||||
if self._name == '':
|
if self._name == '':
|
||||||
try:
|
try:
|
||||||
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
|
self._name = self.nameGenerator().get(
|
||||||
|
self.service().getBaseName(), self.service().getLenName()
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return NO_MORE_NAMES
|
return NO_MORE_NAMES
|
||||||
return self._name
|
return self._name
|
||||||
@ -207,7 +223,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
|||||||
if self._vmid != '':
|
if self._vmid != '':
|
||||||
self.service().stopMachine(self._vmid)
|
self.service().stopMachine(self._vmid)
|
||||||
|
|
||||||
def getConsoleConnection(self) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
def getConsoleConnection(
|
||||||
|
self,
|
||||||
|
) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
||||||
return self.service().getConsoleConnection(self._vmid)
|
return self.service().getConsoleConnection(self._vmid)
|
||||||
|
|
||||||
def desktopLogin(self, username: str, password: str, domain: str = '') -> None:
|
def desktopLogin(self, username: str, password: str, domain: str = '') -> None:
|
||||||
@ -215,7 +233,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from uds import operations
|
from uds import operations
|
||||||
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", struct.pack('!IsIs', 1, '{username}'.encode('utf8'), 2, '{password}'.encode('utf8')), True)
|
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", struct.pack('!IsIs', 1, '{username}'.encode('utf8'), 2, '{password}'.encode('utf8')), True)
|
||||||
'''.format(username=username, password=password)
|
'''.format(
|
||||||
|
username=username, password=password
|
||||||
|
)
|
||||||
# Post script to service
|
# Post script to service
|
||||||
# operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
# operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
||||||
dbService = self.dbservice()
|
dbService = self.dbservice()
|
||||||
@ -254,8 +274,15 @@ if sys.platform == 'win32':
|
|||||||
else:
|
else:
|
||||||
self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
|
self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
|
||||||
|
|
||||||
def __checkMachineState(self, chkState: typing.Union[typing.List[str], typing.Tuple[str, ...], str]) -> str:
|
def __checkMachineState(
|
||||||
logger.debug('Checking that state of machine %s (%s) is %s', self._vmid, self._name, chkState)
|
self, chkState: typing.Union[typing.List[str], typing.Tuple[str, ...], str]
|
||||||
|
) -> str:
|
||||||
|
logger.debug(
|
||||||
|
'Checking that state of machine %s (%s) is %s',
|
||||||
|
self._vmid,
|
||||||
|
self._name,
|
||||||
|
chkState,
|
||||||
|
)
|
||||||
state = self.service().getMachineState(self._vmid)
|
state = self.service().getMachineState(self._vmid)
|
||||||
|
|
||||||
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
||||||
@ -329,14 +356,16 @@ if sys.platform == 'win32':
|
|||||||
opSuspend: self.__suspendMachine,
|
opSuspend: self.__suspendMachine,
|
||||||
opWait: self.__wait,
|
opWait: self.__wait,
|
||||||
opRemove: self.__remove,
|
opRemove: self.__remove,
|
||||||
opChangeMac: self.__changeMac
|
opChangeMac: self.__changeMac,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||||
|
|
||||||
if execFnc is None:
|
if execFnc is None:
|
||||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at execution queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
execFnc()
|
execFnc()
|
||||||
|
|
||||||
@ -368,9 +397,13 @@ if sys.platform == 'win32':
|
|||||||
templateId = self.publication().getTemplateId()
|
templateId = self.publication().getTemplateId()
|
||||||
name = self.getName()
|
name = self.getName()
|
||||||
if name == NO_MORE_NAMES:
|
if name == NO_MORE_NAMES:
|
||||||
raise Exception('No more names available for this service. (Increase digits for this service to fix)')
|
raise Exception(
|
||||||
|
'No more names available for this service. (Increase digits for this service to fix)'
|
||||||
|
)
|
||||||
|
|
||||||
name = self.service().sanitizeVmName(name) # oVirt don't let us to create machines with more than 15 chars!!!
|
name = self.service().sanitizeVmName(
|
||||||
|
name
|
||||||
|
) # oVirt don't let us to create machines with more than 15 chars!!!
|
||||||
comments = 'UDS Linked clone'
|
comments = 'UDS Linked clone'
|
||||||
|
|
||||||
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
|
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
|
||||||
@ -409,7 +442,9 @@ if sys.platform == 'win32':
|
|||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
if state != 'down' and state != 'suspended':
|
if state != 'down' and state != 'suspended':
|
||||||
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
|
self.__pushFrontOp(
|
||||||
|
opRetry
|
||||||
|
) # Will call "check Retry", that will finish inmediatly and again call this one
|
||||||
self.service().startMachine(self._vmid)
|
self.service().startMachine(self._vmid)
|
||||||
|
|
||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
@ -427,7 +462,9 @@ if sys.platform == 'win32':
|
|||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
if state != 'up' and state != 'suspended':
|
if state != 'up' and state != 'suspended':
|
||||||
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
|
self.__pushFrontOp(
|
||||||
|
opRetry
|
||||||
|
) # Will call "check Retry", that will finish inmediatly and again call this one
|
||||||
else:
|
else:
|
||||||
self.service().stopMachine(self._vmid)
|
self.service().stopMachine(self._vmid)
|
||||||
|
|
||||||
@ -446,7 +483,9 @@ if sys.platform == 'win32':
|
|||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
if state != 'up':
|
if state != 'up':
|
||||||
self.__pushFrontOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
|
self.__pushFrontOp(
|
||||||
|
opRetry
|
||||||
|
) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
|
||||||
else:
|
else:
|
||||||
self.service().suspendMachine(self._vmid)
|
self.service().suspendMachine(self._vmid)
|
||||||
|
|
||||||
@ -522,14 +561,18 @@ if sys.platform == 'win32':
|
|||||||
opStop: self.__checkStop,
|
opStop: self.__checkStop,
|
||||||
opSuspend: self.__checkSuspend,
|
opSuspend: self.__checkSuspend,
|
||||||
opRemove: self.__checkRemoved,
|
opRemove: self.__checkRemoved,
|
||||||
opChangeMac: self.__checkMac
|
opChangeMac: self.__checkMac,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
chkFnc: typing.Optional[typing.Optional[typing.Callable[[], str]]] = fncs.get(op, None)
|
chkFnc: typing.Optional[
|
||||||
|
typing.Optional[typing.Callable[[], str]]
|
||||||
|
] = fncs.get(op, None)
|
||||||
|
|
||||||
if chkFnc is None:
|
if chkFnc is None:
|
||||||
return self.__error('Unknown operation found at check queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at check queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
state = chkFnc()
|
state = chkFnc()
|
||||||
if state == State.FINISHED:
|
if state == State.FINISHED:
|
||||||
@ -613,8 +656,16 @@ if sys.platform == 'win32':
|
|||||||
opError: 'error',
|
opError: 'error',
|
||||||
opFinish: 'finish',
|
opFinish: 'finish',
|
||||||
opRetry: 'retry',
|
opRetry: 'retry',
|
||||||
opChangeMac: 'changing mac'
|
opChangeMac: 'changing mac',
|
||||||
}.get(op, '????')
|
}.get(op, '????')
|
||||||
|
|
||||||
def __debug(self, txt):
|
def __debug(self, txt):
|
||||||
logger.debug('State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s', txt, self._name, self._ip, self._mac, self._vmid, [OVirtLinkedDeployment.__op2str(op) for op in self._queue])
|
logger.debug(
|
||||||
|
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
|
||||||
|
txt,
|
||||||
|
self._name,
|
||||||
|
self._ip,
|
||||||
|
self._mac,
|
||||||
|
self._vmid,
|
||||||
|
[OVirtLinkedDeployment.__op2str(op) for op in self._queue],
|
||||||
|
)
|
||||||
|
@ -13,12 +13,14 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
This helper is designed as a callback for machine selector, so we can provide valid clusters and datastores domains based on it
|
This helper is designed as a callback for machine selector, so we can provide valid clusters and datastores domains based on it
|
||||||
"""
|
"""
|
||||||
from .provider import OVirtProvider
|
from .provider import OVirtProvider
|
||||||
from uds.core.environment import Environment
|
from uds.core.environment import Environment
|
||||||
|
|
||||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||||
env = Environment(parameters['ev'])
|
env = Environment(parameters['ev'])
|
||||||
provider: 'OVirtProvider' = OVirtProvider(env)
|
provider: 'OVirtProvider' = OVirtProvider(env)
|
||||||
@ -31,15 +33,23 @@ def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.
|
|||||||
# Get storages for that datacenter
|
# Get storages for that datacenter
|
||||||
for storage in provider.getDatacenterInfo(ci['datacenter_id'])['storage']:
|
for storage in provider.getDatacenterInfo(ci['datacenter_id'])['storage']:
|
||||||
if storage['type'] == 'data':
|
if storage['type'] == 'data':
|
||||||
space, free = (storage['available'] + storage['used']) / 1024 / 1024 / 1024, storage['available'] / 1024 / 1024 / 1024
|
space, free = (
|
||||||
|
storage['available'] + storage['used']
|
||||||
|
) / 1024 / 1024 / 1024, storage['available'] / 1024 / 1024 / 1024
|
||||||
|
|
||||||
res.append({'id': storage['id'], 'text': "%s (%4.2f GB/%4.2f GB) %s" % (storage['name'], space, free, storage['active'] and '(ok)' or '(disabled)')})
|
res.append(
|
||||||
data = [
|
|
||||||
{
|
{
|
||||||
'name': 'datastore',
|
'id': storage['id'],
|
||||||
'values': res
|
'text': "%s (%4.2f GB/%4.2f GB) %s"
|
||||||
|
% (
|
||||||
|
storage['name'],
|
||||||
|
space,
|
||||||
|
free,
|
||||||
|
storage['active'] and '(ok)' or '(disabled)',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
]
|
)
|
||||||
|
data = [{'name': 'datastore', 'values': res}]
|
||||||
|
|
||||||
logger.debug('return data: %s', data)
|
logger.debug('return data: %s', data)
|
||||||
return data
|
return data
|
||||||
|
@ -58,7 +58,9 @@ class OVirtDeferredRemoval(jobs.Job):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove(providerInstance: 'OVirtProvider', vmId: str) -> None:
|
def remove(providerInstance: 'OVirtProvider', vmId: str) -> None:
|
||||||
logger.debug('Adding %s from %s to defeffed removal process', vmId, providerInstance)
|
logger.debug(
|
||||||
|
'Adding %s from %s to defeffed removal process', vmId, providerInstance
|
||||||
|
)
|
||||||
OVirtDeferredRemoval.counter += 1
|
OVirtDeferredRemoval.counter += 1
|
||||||
try:
|
try:
|
||||||
# Tries to stop machine sync when found, if any error is done, defer removal for a scheduled task
|
# Tries to stop machine sync when found, if any error is done, defer removal for a scheduled task
|
||||||
@ -72,7 +74,11 @@ class OVirtDeferredRemoval(jobs.Job):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
providerInstance.storage.saveData('tr' + vmId, vmId, attr1='tRm')
|
providerInstance.storage.saveData('tr' + vmId, vmId, attr1='tRm')
|
||||||
logger.info('Machine %s could not be removed right now, queued for later: %s', vmId, e)
|
logger.info(
|
||||||
|
'Machine %s could not be removed right now, queued for later: %s',
|
||||||
|
vmId,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning('Exception got queuing for Removal: %s', e)
|
logger.warning('Exception got queuing for Removal: %s', e)
|
||||||
@ -84,7 +90,9 @@ class OVirtDeferredRemoval(jobs.Job):
|
|||||||
|
|
||||||
provider: Provider
|
provider: Provider
|
||||||
# Look for Providers of type Ovirt
|
# Look for Providers of type Ovirt
|
||||||
for provider in Provider.objects.filter(maintenance_mode=False, data_type=OVirtProvider.typeType):
|
for provider in Provider.objects.filter(
|
||||||
|
maintenance_mode=False, data_type=OVirtProvider.typeType
|
||||||
|
):
|
||||||
logger.debug('Provider %s if os type ovirt', provider)
|
logger.debug('Provider %s if os type ovirt', provider)
|
||||||
|
|
||||||
storage = provider.getEnvironment().storage
|
storage = provider.getEnvironment().storage
|
||||||
|
@ -52,7 +52,9 @@ logger = logging.getLogger(__name__)
|
|||||||
CACHE_TIME_FOR_SERVER = 1800
|
CACHE_TIME_FOR_SERVER = 1800
|
||||||
|
|
||||||
|
|
||||||
class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-public-methods
|
class OVirtProvider(
|
||||||
|
services.ServiceProvider
|
||||||
|
): # pylint: disable=too-many-public-methods
|
||||||
"""
|
"""
|
||||||
This class represents the sample services provider
|
This class represents the sample services provider
|
||||||
|
|
||||||
@ -69,6 +71,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
we MUST register it at package __init__.
|
we MUST register it at package __init__.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : What kind of services we offer, this are classes inherited from Service
|
# : What kind of services we offer, this are classes inherited from Service
|
||||||
offers = [OVirtLinkedService]
|
offers = [OVirtLinkedService]
|
||||||
# : Name to show the administrator. This string will be translated BEFORE
|
# : Name to show the administrator. This string will be translated BEFORE
|
||||||
@ -103,19 +106,74 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
values=[
|
values=[
|
||||||
gui.choiceItem('4', '4.x'),
|
gui.choiceItem('4', '4.x'),
|
||||||
],
|
],
|
||||||
defvalue='4' # Default value is the ID of the choicefield
|
defvalue='4', # Default value is the ID of the choicefield
|
||||||
)
|
)
|
||||||
|
|
||||||
host = gui.TextField(length=64, label=_('Host'), order=2, tooltip=_('oVirt Server IP or Hostname'), required=True)
|
host = gui.TextField(
|
||||||
username = gui.TextField(length=32, label=_('Username'), order=3, tooltip=_('User with valid privileges on oVirt, (use "user@domain" form)'), required=True, defvalue='admin@internal')
|
length=64,
|
||||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=4, tooltip=_('Password of the user of oVirt'), required=True)
|
label=_('Host'),
|
||||||
|
order=2,
|
||||||
|
tooltip=_('oVirt Server IP or Hostname'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
username = gui.TextField(
|
||||||
|
length=32,
|
||||||
|
label=_('Username'),
|
||||||
|
order=3,
|
||||||
|
tooltip=_('User with valid privileges on oVirt, (use "user@domain" form)'),
|
||||||
|
required=True,
|
||||||
|
defvalue='admin@internal',
|
||||||
|
)
|
||||||
|
password = gui.PasswordField(
|
||||||
|
lenth=32,
|
||||||
|
label=_('Password'),
|
||||||
|
order=4,
|
||||||
|
tooltip=_('Password of the user of oVirt'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
|
maxPreparingServices = gui.NumericField(
|
||||||
maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)
|
length=3,
|
||||||
|
label=_('Creation concurrency'),
|
||||||
|
defvalue='10',
|
||||||
|
minValue=1,
|
||||||
|
maxValue=65536,
|
||||||
|
order=50,
|
||||||
|
tooltip=_('Maximum number of concurrently creating VMs'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
maxRemovingServices = gui.NumericField(
|
||||||
|
length=3,
|
||||||
|
label=_('Removal concurrency'),
|
||||||
|
defvalue='5',
|
||||||
|
minValue=1,
|
||||||
|
maxValue=65536,
|
||||||
|
order=51,
|
||||||
|
tooltip=_('Maximum number of concurrently removing VMs'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
|
||||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to oVirt'), required=True, tab=gui.ADVANCED_TAB)
|
timeout = gui.NumericField(
|
||||||
macsRange = gui.TextField(length=36, label=_('Macs range'), defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF', order=91, rdonly=True,
|
length=3,
|
||||||
tooltip=_('Range of valid macs for UDS managed machines'), required=True, tab=gui.ADVANCED_TAB)
|
label=_('Timeout'),
|
||||||
|
defvalue='10',
|
||||||
|
order=90,
|
||||||
|
tooltip=_('Timeout in seconds of connection to oVirt'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
macsRange = gui.TextField(
|
||||||
|
length=36,
|
||||||
|
label=_('Macs range'),
|
||||||
|
defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF',
|
||||||
|
order=91,
|
||||||
|
rdonly=True,
|
||||||
|
tooltip=_('Range of valid macs for UDS managed machines'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
|
||||||
# Own variables
|
# Own variables
|
||||||
_api: typing.Optional[client.Client] = None
|
_api: typing.Optional[client.Client] = None
|
||||||
@ -129,7 +187,13 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
Returns the connection API object for oVirt (using ovirtsdk)
|
Returns the connection API object for oVirt (using ovirtsdk)
|
||||||
"""
|
"""
|
||||||
if self._api is None:
|
if self._api is None:
|
||||||
self._api = client.Client(self.host.value, self.username.value, self.password.value, self.timeout.value, self.cache)
|
self._api = client.Client(
|
||||||
|
self.host.value,
|
||||||
|
self.username.value,
|
||||||
|
self.password.value,
|
||||||
|
self.timeout.value,
|
||||||
|
self.cache,
|
||||||
|
)
|
||||||
|
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
@ -164,7 +228,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
"""
|
"""
|
||||||
return list(self.__getApi().isFullyFunctionalVersion())
|
return list(self.__getApi().isFullyFunctionalVersion())
|
||||||
|
|
||||||
def getMachines(self, force: bool = False) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
def getMachines(
|
||||||
|
self, force: bool = False
|
||||||
|
) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Obtains the list of machines inside oVirt.
|
Obtains the list of machines inside oVirt.
|
||||||
Machines starting with UDS are filtered out
|
Machines starting with UDS are filtered out
|
||||||
@ -182,7 +248,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
|
|
||||||
return self.__getApi().getVms(force)
|
return self.__getApi().getVms(force)
|
||||||
|
|
||||||
def getClusters(self, force: bool = False) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
def getClusters(
|
||||||
|
self, force: bool = False
|
||||||
|
) -> typing.List[typing.MutableMapping[str, typing.Any]]:
|
||||||
"""
|
"""
|
||||||
Obtains the list of clusters inside oVirt.
|
Obtains the list of clusters inside oVirt.
|
||||||
|
|
||||||
@ -200,7 +268,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
|
|
||||||
return self.__getApi().getClusters(force)
|
return self.__getApi().getClusters(force)
|
||||||
|
|
||||||
def getClusterInfo(self, clusterId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getClusterInfo(
|
||||||
|
self, clusterId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the cluster info
|
Obtains the cluster info
|
||||||
|
|
||||||
@ -218,7 +288,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
"""
|
"""
|
||||||
return self.__getApi().getClusterInfo(clusterId, force)
|
return self.__getApi().getClusterInfo(clusterId, force)
|
||||||
|
|
||||||
def getDatacenterInfo(self, datacenterId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getDatacenterInfo(
|
||||||
|
self, datacenterId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the datacenter info
|
Obtains the datacenter info
|
||||||
|
|
||||||
@ -246,7 +318,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
"""
|
"""
|
||||||
return self.__getApi().getDatacenterInfo(datacenterId, force)
|
return self.__getApi().getDatacenterInfo(datacenterId, force)
|
||||||
|
|
||||||
def getStorageInfo(self, storageId: str, force: bool = False) -> typing.MutableMapping[str, typing.Any]:
|
def getStorageInfo(
|
||||||
|
self, storageId: str, force: bool = False
|
||||||
|
) -> typing.MutableMapping[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
Obtains the storage info
|
Obtains the storage info
|
||||||
|
|
||||||
@ -275,7 +349,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
machineId: str,
|
machineId: str,
|
||||||
clusterId: str,
|
clusterId: str,
|
||||||
storageId: str,
|
storageId: str,
|
||||||
displayType: str
|
displayType: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||||
@ -291,7 +365,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
Returns
|
Returns
|
||||||
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
||||||
"""
|
"""
|
||||||
return self.__getApi().makeTemplate(name, comments, machineId, clusterId, storageId, displayType)
|
return self.__getApi().makeTemplate(
|
||||||
|
name, comments, machineId, clusterId, storageId, displayType
|
||||||
|
)
|
||||||
|
|
||||||
def getTemplateState(self, templateId: str) -> str:
|
def getTemplateState(self, templateId: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -341,7 +417,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
displayType: str,
|
displayType: str,
|
||||||
usbType: str,
|
usbType: str,
|
||||||
memoryMB: int,
|
memoryMB: int,
|
||||||
guaranteedMB: int
|
guaranteedMB: int,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Deploys a virtual machine on selected cluster from selected template
|
Deploys a virtual machine on selected cluster from selected template
|
||||||
@ -358,7 +434,16 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
Returns:
|
Returns:
|
||||||
Id of the machine being created form template
|
Id of the machine being created form template
|
||||||
"""
|
"""
|
||||||
return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId, displayType, usbType, memoryMB, guaranteedMB)
|
return self.__getApi().deployFromTemplate(
|
||||||
|
name,
|
||||||
|
comments,
|
||||||
|
templateId,
|
||||||
|
clusterId,
|
||||||
|
displayType,
|
||||||
|
usbType,
|
||||||
|
memoryMB,
|
||||||
|
guaranteedMB,
|
||||||
|
)
|
||||||
|
|
||||||
def startMachine(self, machineId: str) -> None:
|
def startMachine(self, machineId: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -418,7 +503,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
|||||||
def getMacRange(self) -> str:
|
def getMacRange(self) -> str:
|
||||||
return self.macsRange.value
|
return self.macsRange.value
|
||||||
|
|
||||||
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
def getConsoleConnection(
|
||||||
|
self, machineId: str
|
||||||
|
) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
||||||
return self.__getApi().getConsoleConnection(machineId)
|
return self.__getApi().getConsoleConnection(machineId)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -50,7 +50,9 @@ class OVirtPublication(Publication):
|
|||||||
This class provides the publication of a oVirtLinkedService
|
This class provides the publication of a oVirtLinkedService
|
||||||
"""
|
"""
|
||||||
|
|
||||||
suggestedTime = 20 # : Suggested recheck time if publication is unfinished in seconds
|
suggestedTime = (
|
||||||
|
20 # : Suggested recheck time if publication is unfinished in seconds
|
||||||
|
)
|
||||||
_name: str
|
_name: str
|
||||||
_reason: str
|
_reason: str
|
||||||
_destroyAfter: str
|
_destroyAfter: str
|
||||||
@ -80,7 +82,16 @@ class OVirtPublication(Publication):
|
|||||||
"""
|
"""
|
||||||
returns data from an instance of Sample Publication serialized
|
returns data from an instance of Sample Publication serialized
|
||||||
"""
|
"""
|
||||||
return '\t'.join(['v1', self._name, self._reason, self._destroyAfter, self._templateId, self._state]).encode('utf8')
|
return '\t'.join(
|
||||||
|
[
|
||||||
|
'v1',
|
||||||
|
self._name,
|
||||||
|
self._reason,
|
||||||
|
self._destroyAfter,
|
||||||
|
self._templateId,
|
||||||
|
self._state,
|
||||||
|
]
|
||||||
|
).encode('utf8')
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -89,14 +100,24 @@ class OVirtPublication(Publication):
|
|||||||
logger.debug('Data: %s', data)
|
logger.debug('Data: %s', data)
|
||||||
vals = data.decode('utf8').split('\t')
|
vals = data.decode('utf8').split('\t')
|
||||||
if vals[0] == 'v1':
|
if vals[0] == 'v1':
|
||||||
self._name, self._reason, self._destroyAfter, self._templateId, self._state = vals[1:]
|
(
|
||||||
|
self._name,
|
||||||
|
self._reason,
|
||||||
|
self._destroyAfter,
|
||||||
|
self._templateId,
|
||||||
|
self._state,
|
||||||
|
) = vals[1:]
|
||||||
|
|
||||||
def publish(self) -> str:
|
def publish(self) -> str:
|
||||||
"""
|
"""
|
||||||
Realizes the publication of the service
|
Realizes the publication of the service
|
||||||
"""
|
"""
|
||||||
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
|
self._name = self.service().sanitizeVmName(
|
||||||
comments = _('UDS pub for {0} at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
|
'UDSP ' + self.dsName() + "-" + str(self.revision())
|
||||||
|
)
|
||||||
|
comments = _('UDS pub for {0} at {1}').format(
|
||||||
|
self.dsName(), str(datetime.now()).split('.')[0]
|
||||||
|
)
|
||||||
self._reason = '' # No error, no reason for it
|
self._reason = '' # No error, no reason for it
|
||||||
self._destroyAfter = 'f'
|
self._destroyAfter = 'f'
|
||||||
self._state = 'locked'
|
self._state = 'locked'
|
||||||
|
@ -57,6 +57,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
oVirt Linked clones service. This is based on creating a template from selected vm, and then use it to
|
oVirt Linked clones service. This is based on creating a template from selected vm, and then use it to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : Name to show the administrator. This string will be translated BEFORE
|
# : Name to show the administrator. This string will be translated BEFORE
|
||||||
# : sending it to administration interface, so don't forget to
|
# : sending it to administration interface, so don't forget to
|
||||||
# : mark it as _ (using ugettext_noop)
|
# : mark it as _ (using ugettext_noop)
|
||||||
@ -113,9 +114,10 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
fills={
|
fills={
|
||||||
'callbackName': 'ovFillResourcesFromCluster',
|
'callbackName': 'ovFillResourcesFromCluster',
|
||||||
'function': helpers.getResources,
|
'function': helpers.getResources,
|
||||||
'parameters': ['cluster', 'ov', 'ev']
|
'parameters': ['cluster', 'ov', 'ev'],
|
||||||
},
|
},
|
||||||
tooltip=_("Cluster to contain services"), required=True
|
tooltip=_("Cluster to contain services"),
|
||||||
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
datastore = gui.ChoiceField(
|
datastore = gui.ChoiceField(
|
||||||
@ -123,7 +125,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
rdonly=False,
|
rdonly=False,
|
||||||
order=101,
|
order=101,
|
||||||
tooltip=_('Datastore domain where to publish and put incrementals'),
|
tooltip=_('Datastore domain where to publish and put incrementals'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
minSpaceGB = gui.NumericField(
|
minSpaceGB = gui.NumericField(
|
||||||
@ -133,7 +135,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
minValue=0,
|
minValue=0,
|
||||||
order=102,
|
order=102,
|
||||||
tooltip=_('Minimal free space in GB'),
|
tooltip=_('Minimal free space in GB'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = gui.ChoiceField(
|
machine = gui.ChoiceField(
|
||||||
@ -141,7 +143,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=110,
|
order=110,
|
||||||
tooltip=_('Service base machine'),
|
tooltip=_('Service base machine'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
memory = gui.NumericField(
|
memory = gui.NumericField(
|
||||||
@ -153,7 +155,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=111,
|
order=111,
|
||||||
tooltip=_('Memory assigned to machines'),
|
tooltip=_('Memory assigned to machines'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
memoryGuaranteed = gui.NumericField(
|
memoryGuaranteed = gui.NumericField(
|
||||||
@ -165,7 +167,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=112,
|
order=112,
|
||||||
tooltip=_('Physical memory guaranteed to machines'),
|
tooltip=_('Physical memory guaranteed to machines'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
usb = gui.ChoiceField(
|
usb = gui.ChoiceField(
|
||||||
@ -179,7 +181,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
# gui.choiceItem('legacy', 'legacy (deprecated)'),
|
# gui.choiceItem('legacy', 'legacy (deprecated)'),
|
||||||
],
|
],
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
defvalue='1' # Default value is the ID of the choicefield
|
defvalue='1', # Default value is the ID of the choicefield
|
||||||
)
|
)
|
||||||
|
|
||||||
display = gui.ChoiceField(
|
display = gui.ChoiceField(
|
||||||
@ -187,12 +189,9 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
rdonly=False,
|
rdonly=False,
|
||||||
order=114,
|
order=114,
|
||||||
tooltip=_('Display type (only for administration purposes)'),
|
tooltip=_('Display type (only for administration purposes)'),
|
||||||
values=[
|
values=[gui.choiceItem('spice', 'Spice'), gui.choiceItem('vnc', 'Vnc')],
|
||||||
gui.choiceItem('spice', 'Spice'),
|
|
||||||
gui.choiceItem('vnc', 'Vnc')
|
|
||||||
],
|
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
defvalue='1' # Default value is the ID of the choicefield
|
defvalue='1', # Default value is the ID of the choicefield
|
||||||
)
|
)
|
||||||
baseName = gui.TextField(
|
baseName = gui.TextField(
|
||||||
label=_('Machine Names'),
|
label=_('Machine Names'),
|
||||||
@ -200,7 +199,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=115,
|
order=115,
|
||||||
tooltip=_('Base name for clones from this machine'),
|
tooltip=_('Base name for clones from this machine'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
lenName = gui.NumericField(
|
lenName = gui.NumericField(
|
||||||
@ -210,11 +209,13 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=116,
|
order=116,
|
||||||
tooltip=_('Size of numeric part for the names of these machines'),
|
tooltip=_('Size of numeric part for the names of these machines'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
ov = gui.HiddenField(value=None)
|
ov = gui.HiddenField(value=None)
|
||||||
ev = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
|
ev = gui.HiddenField(
|
||||||
|
value=None
|
||||||
|
) # We need to keep the env so we can instantiate the Provider
|
||||||
|
|
||||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||||
"""
|
"""
|
||||||
@ -226,7 +227,9 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
if values:
|
if values:
|
||||||
tools.checkValidBasename(self.baseName.value, self.lenName.num())
|
tools.checkValidBasename(self.baseName.value, self.lenName.num())
|
||||||
if int(self.memory.value) < 256 or int(self.memoryGuaranteed.value) < 256:
|
if int(self.memory.value) < 256 or int(self.memoryGuaranteed.value) < 256:
|
||||||
raise Service.ValidationException(_('The minimum allowed memory is 256 Mb'))
|
raise Service.ValidationException(
|
||||||
|
_('The minimum allowed memory is 256 Mb')
|
||||||
|
)
|
||||||
if int(self.memoryGuaranteed.value) > int(self.memory.value):
|
if int(self.memoryGuaranteed.value) > int(self.memory.value):
|
||||||
self.memoryGuaranteed.value = self.memory.value
|
self.memoryGuaranteed.value = self.memory.value
|
||||||
|
|
||||||
@ -275,7 +278,11 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
logger.debug('Datastore Info: %s', info)
|
logger.debug('Datastore Info: %s', info)
|
||||||
availableGB = info['available'] / (1024 * 1024 * 1024)
|
availableGB = info['available'] / (1024 * 1024 * 1024)
|
||||||
if availableGB < self.minSpaceGB.num():
|
if availableGB < self.minSpaceGB.num():
|
||||||
raise Exception('Not enough free space available: (Needs at least {0} GB and there is only {1} GB '.format(self.minSpaceGB.num(), availableGB))
|
raise Exception(
|
||||||
|
'Not enough free space available: (Needs at least {0} GB and there is only {1} GB '.format(
|
||||||
|
self.minSpaceGB.num(), availableGB
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def sanitizeVmName(self, name: str) -> str:
|
def sanitizeVmName(self, name: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -301,7 +308,14 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
# Get storages for that datacenter
|
# Get storages for that datacenter
|
||||||
|
|
||||||
self.datastoreHasSpace()
|
self.datastoreHasSpace()
|
||||||
return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value, self.display.value)
|
return self.parent().makeTemplate(
|
||||||
|
name,
|
||||||
|
comments,
|
||||||
|
self.machine.value,
|
||||||
|
self.cluster.value,
|
||||||
|
self.datastore.value,
|
||||||
|
self.display.value,
|
||||||
|
)
|
||||||
|
|
||||||
def getTemplateState(self, templateId: str) -> str:
|
def getTemplateState(self, templateId: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -333,8 +347,16 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
logger.debug('Deploying from template %s machine %s', templateId, name)
|
logger.debug('Deploying from template %s machine %s', templateId, name)
|
||||||
self.datastoreHasSpace()
|
self.datastoreHasSpace()
|
||||||
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
|
return self.parent().deployFromTemplate(
|
||||||
self.display.value, self.usb.value, int(self.memory.value), int(self.memoryGuaranteed.value))
|
name,
|
||||||
|
comments,
|
||||||
|
templateId,
|
||||||
|
self.cluster.value,
|
||||||
|
self.display.value,
|
||||||
|
self.usb.value,
|
||||||
|
int(self.memory.value),
|
||||||
|
int(self.memoryGuaranteed.value),
|
||||||
|
)
|
||||||
|
|
||||||
def removeTemplate(self, templateId: str) -> None:
|
def removeTemplate(self, templateId: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -440,5 +462,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
return self.display.value
|
return self.display.value
|
||||||
|
|
||||||
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
def getConsoleConnection(
|
||||||
|
self, machineId: str
|
||||||
|
) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
||||||
return self.parent().getConsoleConnection(machineId)
|
return self.parent().getConsoleConnection(machineId)
|
||||||
|
@ -48,6 +48,7 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OpenGnsysMaintainer(jobs.Job):
|
class OpenGnsysMaintainer(jobs.Job):
|
||||||
frecuency = 60 * 60 * 4 # Once every 4 hours
|
frecuency = 60 * 60 * 4 # Once every 4 hours
|
||||||
friendly_name = 'OpenGnsys cache renewal job'
|
friendly_name = 'OpenGnsys cache renewal job'
|
||||||
@ -57,18 +58,29 @@ class OpenGnsysMaintainer(jobs.Job):
|
|||||||
|
|
||||||
# Look for Providers of type VMWareVCServiceProvider
|
# Look for Providers of type VMWareVCServiceProvider
|
||||||
provider: models.Provider
|
provider: models.Provider
|
||||||
for provider in models.Provider.objects.filter(maintenance_mode=False, data_type=OGProvider.typeType):
|
for provider in models.Provider.objects.filter(
|
||||||
|
maintenance_mode=False, data_type=OGProvider.typeType
|
||||||
|
):
|
||||||
logger.debug('Provider %s is type openGnsys', provider)
|
logger.debug('Provider %s is type openGnsys', provider)
|
||||||
|
|
||||||
# Locate all services inside the provider
|
# Locate all services inside the provider
|
||||||
service: models.Service
|
service: models.Service
|
||||||
for service in provider.services.all():
|
for service in provider.services.all():
|
||||||
instance: OGService = typing.cast(OGService, service.getInstance())
|
instance: OGService = typing.cast(OGService, service.getInstance())
|
||||||
since = models.getSqlDatetime() - datetime.timedelta(hours=instance.maxReservationTime.num()-8) # If less than 8 hours of reservation...
|
since = models.getSqlDatetime() - datetime.timedelta(
|
||||||
|
hours=instance.maxReservationTime.num() - 8
|
||||||
|
) # If less than 8 hours of reservation...
|
||||||
# Now mark for removal every CACHED service that is about to expire its reservation on OpenGnsys
|
# Now mark for removal every CACHED service that is about to expire its reservation on OpenGnsys
|
||||||
userService: models.UserService
|
userService: models.UserService
|
||||||
for userService in models.UserService.objects.filter(deployed_service__service=service, creation_date__lt=since, cache_level=1):
|
for userService in models.UserService.objects.filter(
|
||||||
logger.info('The cached user service %s is about to expire. Removing it so it can be recreated', userService)
|
deployed_service__service=service,
|
||||||
|
creation_date__lt=since,
|
||||||
|
cache_level=1,
|
||||||
|
):
|
||||||
|
logger.info(
|
||||||
|
'The cached user service %s is about to expire. Removing it so it can be recreated',
|
||||||
|
userService,
|
||||||
|
)
|
||||||
userService.remove()
|
userService.remove()
|
||||||
|
|
||||||
logger.debug('OpenGnsys job finished')
|
logger.debug('OpenGnsys job finished')
|
||||||
|
@ -140,7 +140,9 @@ class OGService(Service):
|
|||||||
label=_('Start if unavailable'),
|
label=_('Start if unavailable'),
|
||||||
defvalue=gui.TRUE,
|
defvalue=gui.TRUE,
|
||||||
order=111,
|
order=111,
|
||||||
tooltip=_('If active, machines that are not available on user connect (on some OS) will try to power on through OpenGnsys.'),
|
tooltip=_(
|
||||||
|
'If active, machines that are not available on user connect (on some OS) will try to power on through OpenGnsys.'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
ov = gui.HiddenField(value=None)
|
ov = gui.HiddenField(value=None)
|
||||||
@ -180,7 +182,7 @@ class OGService(Service):
|
|||||||
machineId,
|
machineId,
|
||||||
self.getLoginNotifyURL(uuid, token),
|
self.getLoginNotifyURL(uuid, token),
|
||||||
self.getLogoutNotifyURL(uuid, token),
|
self.getLogoutNotifyURL(uuid, token),
|
||||||
self.getReleaseURL(uuid, token)
|
self.getReleaseURL(uuid, token),
|
||||||
)
|
)
|
||||||
|
|
||||||
def notifyDeadline(
|
def notifyDeadline(
|
||||||
@ -197,7 +199,7 @@ class OGService(Service):
|
|||||||
accessURL=self.parent().getUDSServerAccessUrl(),
|
accessURL=self.parent().getUDSServerAccessUrl(),
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
token=token,
|
token=token,
|
||||||
message=message
|
message=message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getLoginNotifyURL(self, uuid: str, token: str) -> str:
|
def getLoginNotifyURL(self, uuid: str, token: str) -> str:
|
||||||
|
@ -53,6 +53,7 @@ opCreate, opStart, opShutdown, opRemove, opWait, opError, opFinish, opRetry = ra
|
|||||||
|
|
||||||
NO_MORE_NAMES = 'NO-NAME-ERROR'
|
NO_MORE_NAMES = 'NO-NAME-ERROR'
|
||||||
|
|
||||||
|
|
||||||
class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||||
# : Recheck every six seconds by default (for task methods)
|
# : Recheck every six seconds by default (for task methods)
|
||||||
suggestedTime = 6
|
suggestedTime = 6
|
||||||
@ -65,7 +66,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
_reason: str = ''
|
_reason: str = ''
|
||||||
_queue: typing.List[int]
|
_queue: typing.List[int]
|
||||||
|
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self._name = ''
|
self._name = ''
|
||||||
self._ip = ''
|
self._ip = ''
|
||||||
@ -85,15 +85,17 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
# Serializable needed methods
|
# Serializable needed methods
|
||||||
def marshal(self) -> bytes:
|
def marshal(self) -> bytes:
|
||||||
return b'\1'.join([
|
return b'\1'.join(
|
||||||
|
[
|
||||||
b'v1',
|
b'v1',
|
||||||
self._name.encode('utf8'),
|
self._name.encode('utf8'),
|
||||||
self._ip.encode('utf8'),
|
self._ip.encode('utf8'),
|
||||||
self._mac.encode('utf8'),
|
self._mac.encode('utf8'),
|
||||||
self._vmid.encode('utf8'),
|
self._vmid.encode('utf8'),
|
||||||
self._reason.encode('utf8'),
|
self._reason.encode('utf8'),
|
||||||
pickle.dumps(self._queue, protocol=0)
|
pickle.dumps(self._queue, protocol=0),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
vals = data.split(b'\1')
|
vals = data.split(b'\1')
|
||||||
@ -108,7 +110,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
def getName(self) -> str:
|
def getName(self) -> str:
|
||||||
if self._name == '':
|
if self._name == '':
|
||||||
try:
|
try:
|
||||||
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
|
self._name = self.nameGenerator().get(
|
||||||
|
self.service().getBaseName(), self.service().getLenName()
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return NO_MORE_NAMES
|
return NO_MORE_NAMES
|
||||||
return self._name
|
return self._name
|
||||||
@ -179,11 +183,19 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
self._queue = [opCreate, opStart, opWait, opShutdown, opFinish]
|
self._queue = [opCreate, opStart, opWait, opShutdown, opFinish]
|
||||||
|
|
||||||
def __checkMachineState(self, chkState: on.types.VmState) -> str:
|
def __checkMachineState(self, chkState: on.types.VmState) -> str:
|
||||||
logger.debug('Checking that state of machine %s (%s) is %s', self._vmid, self._name, chkState)
|
logger.debug(
|
||||||
|
'Checking that state of machine %s (%s) is %s',
|
||||||
|
self._vmid,
|
||||||
|
self._name,
|
||||||
|
chkState,
|
||||||
|
)
|
||||||
state = self.service().getMachineState(self._vmid)
|
state = self.service().getMachineState(self._vmid)
|
||||||
|
|
||||||
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
||||||
if state in [on.types.VmState.UNKNOWN, on.types.VmState.DONE]: # @UndefinedVariable
|
if state in [
|
||||||
|
on.types.VmState.UNKNOWN,
|
||||||
|
on.types.VmState.DONE,
|
||||||
|
]: # @UndefinedVariable
|
||||||
return self.__error('Machine not found')
|
return self.__error('Machine not found')
|
||||||
|
|
||||||
ret = State.RUNNING
|
ret = State.RUNNING
|
||||||
@ -260,7 +272,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||||
|
|
||||||
if execFnc is None:
|
if execFnc is None:
|
||||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at execution queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
execFnc()
|
execFnc()
|
||||||
|
|
||||||
@ -293,9 +307,13 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
templateId = self.publication().getTemplateId()
|
templateId = self.publication().getTemplateId()
|
||||||
name = self.getName()
|
name = self.getName()
|
||||||
if name == NO_MORE_NAMES:
|
if name == NO_MORE_NAMES:
|
||||||
raise Exception('No more names available for this service. (Increase digits for this service to fix)')
|
raise Exception(
|
||||||
|
'No more names available for this service. (Increase digits for this service to fix)'
|
||||||
|
)
|
||||||
|
|
||||||
name = self.service().sanitizeVmName(name) # OpenNebula don't let us to create machines with more than 15 chars!!!
|
name = self.service().sanitizeVmName(
|
||||||
|
name
|
||||||
|
) # OpenNebula don't let us to create machines with more than 15 chars!!!
|
||||||
|
|
||||||
self._vmid = self.service().deployFromTemplate(name, templateId)
|
self._vmid = self.service().deployFromTemplate(name, templateId)
|
||||||
if self._vmid is None:
|
if self._vmid is None:
|
||||||
@ -395,7 +413,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
chkFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
chkFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||||
|
|
||||||
if chkFnc is None:
|
if chkFnc is None:
|
||||||
return self.__error('Unknown operation found at check queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at check queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
state = chkFnc()
|
state = chkFnc()
|
||||||
if state == State.FINISHED:
|
if state == State.FINISHED:
|
||||||
@ -476,4 +496,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
}.get(op, '????')
|
}.get(op, '????')
|
||||||
|
|
||||||
def __debug(self, txt: str) -> None:
|
def __debug(self, txt: str) -> None:
|
||||||
logger.debug('State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s', txt, self._name, self._ip, self._mac, self._vmid, [LiveDeployment.__op2str(op) for op in self._queue])
|
logger.debug(
|
||||||
|
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
|
||||||
|
txt,
|
||||||
|
self._name,
|
||||||
|
self._ip,
|
||||||
|
self._mac,
|
||||||
|
self._vmid,
|
||||||
|
[LiveDeployment.__op2str(op) for op in self._queue],
|
||||||
|
)
|
||||||
|
@ -39,7 +39,10 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def enumerateDatastores(api: 'client.OpenNebulaClient', datastoreType: int = 0) -> typing.Iterable['types.StorageType']:
|
|
||||||
|
def enumerateDatastores(
|
||||||
|
api: 'client.OpenNebulaClient', datastoreType: int = 0
|
||||||
|
) -> typing.Iterable['types.StorageType']:
|
||||||
"""
|
"""
|
||||||
0 seems to be images datastore
|
0 seems to be images datastore
|
||||||
"""
|
"""
|
||||||
|
@ -45,13 +45,18 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def getTemplates(api: 'client.OpenNebulaClient', force: bool = False) -> typing.Iterable[types.TemplateType]:
|
|
||||||
|
def getTemplates(
|
||||||
|
api: 'client.OpenNebulaClient', force: bool = False
|
||||||
|
) -> typing.Iterable[types.TemplateType]:
|
||||||
for t in api.enumTemplates():
|
for t in api.enumTemplates():
|
||||||
if t.name[:4] != 'UDSP':
|
if t.name[:4] != 'UDSP':
|
||||||
yield t
|
yield t
|
||||||
|
|
||||||
|
|
||||||
def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDataStore: str) -> str:
|
def create(
|
||||||
|
api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDataStore: str
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||||
the creating machine
|
the creating machine
|
||||||
@ -92,7 +97,11 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
|||||||
try:
|
try:
|
||||||
imgId = imgs[imgName.strip()]
|
imgId = imgs[imgName.strip()]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise Exception('Image "{}" could not be found!. Check the opennebula template'.format(imgName.strip()))
|
raise Exception(
|
||||||
|
'Image "{}" could not be found!. Check the opennebula template'.format(
|
||||||
|
imgName.strip()
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
fromId = True
|
fromId = True
|
||||||
node = imgIds[0].childNodes[0]
|
node = imgIds[0].childNodes[0]
|
||||||
@ -105,7 +114,9 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
|||||||
|
|
||||||
# Now clone the image
|
# Now clone the image
|
||||||
imgName = sanitizeName(name + ' DSK ' + str(counter))
|
imgName = sanitizeName(name + ' DSK ' + str(counter))
|
||||||
newId = api.cloneImage(imgId, imgName, toDataStore) # api.call('image.clone', int(imgId), imgName, int(toDataStore))
|
newId = api.cloneImage(
|
||||||
|
imgId, imgName, toDataStore
|
||||||
|
) # api.call('image.clone', int(imgId), imgName, int(toDataStore))
|
||||||
# Now Store id/name
|
# Now Store id/name
|
||||||
if fromId is True:
|
if fromId is True:
|
||||||
node.data = str(newId)
|
node.data = str(newId)
|
||||||
@ -120,7 +131,9 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception('Creating template on OpenNebula')
|
logger.exception('Creating template on OpenNebula')
|
||||||
try:
|
try:
|
||||||
api.deleteTemplate(templateId) # Try to remove created template in case of fail
|
api.deleteTemplate(
|
||||||
|
templateId
|
||||||
|
) # Try to remove created template in case of fail
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
@ -165,6 +178,7 @@ def remove(api: 'client.OpenNebulaClient', templateId: str) -> None:
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Removing template on OpenNebula')
|
logger.error('Removing template on OpenNebula')
|
||||||
|
|
||||||
|
|
||||||
def deployFrom(api: 'client.OpenNebulaClient', templateId: str, name: str) -> str:
|
def deployFrom(api: 'client.OpenNebulaClient', templateId: str, name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Deploys a virtual machine on selected cluster from selected template
|
Deploys a virtual machine on selected cluster from selected template
|
||||||
@ -177,9 +191,12 @@ def deployFrom(api: 'client.OpenNebulaClient', templateId: str, name: str) -> st
|
|||||||
Returns:
|
Returns:
|
||||||
Id of the machine being created form template
|
Id of the machine being created form template
|
||||||
"""
|
"""
|
||||||
vmId = api.instantiateTemplate(templateId, name, False, '', False) # api.call('template.instantiate', int(templateId), name, False, '')
|
vmId = api.instantiateTemplate(
|
||||||
|
templateId, name, False, '', False
|
||||||
|
) # api.call('template.instantiate', int(templateId), name, False, '')
|
||||||
return vmId
|
return vmId
|
||||||
|
|
||||||
|
|
||||||
def checkPublished(api: 'client.OpenNebulaClient', templateId):
|
def checkPublished(api: 'client.OpenNebulaClient', templateId):
|
||||||
"""
|
"""
|
||||||
checks if the template is fully published (images are ready...)
|
checks if the template is fully published (images are ready...)
|
||||||
@ -209,7 +226,9 @@ def checkPublished(api: 'client.OpenNebulaClient', templateId):
|
|||||||
if state in (types.ImageState.INIT, types.ImageState.LOCKED):
|
if state in (types.ImageState.INIT, types.ImageState.LOCKED):
|
||||||
return False
|
return False
|
||||||
if state != types.ImageState.READY: # If error is not READY
|
if state != types.ImageState.READY: # If error is not READY
|
||||||
raise Exception('Error publishing. Image is in an invalid state. (Check it and delete it if not needed anymore)')
|
raise Exception(
|
||||||
|
'Error publishing. Image is in an invalid state. (Check it and delete it if not needed anymore)'
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure image is non persistent. This may be invoked more than once, but it does not matters
|
# Ensure image is non persistent. This may be invoked more than once, but it does not matters
|
||||||
api.makePersistentImage(imgId, False)
|
api.makePersistentImage(imgId, False)
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
import enum
|
import enum
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class VmState(enum.Enum): # pylint: disable=too-few-public-methods
|
class VmState(enum.Enum): # pylint: disable=too-few-public-methods
|
||||||
INIT = 0
|
INIT = 0
|
||||||
PENDING = 1
|
PENDING = 1
|
||||||
|
@ -58,7 +58,9 @@ def getMachineState(api: 'client.OpenNebulaClient', machineId: str) -> types.VmS
|
|||||||
try:
|
try:
|
||||||
return api.getVMState(machineId)
|
return api.getVMState(machineId)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error obtaining machine state for %s on OpenNebula: %s', machineId, e)
|
logger.error(
|
||||||
|
'Error obtaining machine state for %s on OpenNebula: %s', machineId, e
|
||||||
|
)
|
||||||
|
|
||||||
return types.VmState.UNKNOWN
|
return types.VmState.UNKNOWN
|
||||||
|
|
||||||
@ -70,7 +72,9 @@ def getMachineSubstate(api: 'client.OpenNebulaClient', machineId: str) -> int:
|
|||||||
try:
|
try:
|
||||||
return api.getVMSubstate(machineId)
|
return api.getVMSubstate(machineId)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error obtaining machine substate for %s on OpenNebula: %s', machineId, e)
|
logger.error(
|
||||||
|
'Error obtaining machine substate for %s on OpenNebula: %s', machineId, e
|
||||||
|
)
|
||||||
|
|
||||||
return types.VmState.UNKNOWN.value
|
return types.VmState.UNKNOWN.value
|
||||||
|
|
||||||
@ -122,6 +126,7 @@ def suspendMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error suspending %s on OpenNebula: %s', machineId, e)
|
logger.error('Error suspending %s on OpenNebula: %s', machineId, e)
|
||||||
|
|
||||||
|
|
||||||
def shutdownMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
def shutdownMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||||
'''
|
'''
|
||||||
Tries to "gracefully" shutdown a machine. No check is done, it is simply requested to OpenNebula
|
Tries to "gracefully" shutdown a machine. No check is done, it is simply requested to OpenNebula
|
||||||
@ -171,7 +176,9 @@ def removeMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
|||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
|
|
||||||
def enumerateMachines(api: 'client.OpenNebulaClient') -> typing.Iterable[types.VirtualMachineType]:
|
def enumerateMachines(
|
||||||
|
api: 'client.OpenNebulaClient',
|
||||||
|
) -> typing.Iterable[types.VirtualMachineType]:
|
||||||
'''
|
'''
|
||||||
Obtains the list of machines inside OpenNebula.
|
Obtains the list of machines inside OpenNebula.
|
||||||
Machines starting with UDS are filtered out
|
Machines starting with UDS are filtered out
|
||||||
@ -189,7 +196,11 @@ def enumerateMachines(api: 'client.OpenNebulaClient') -> typing.Iterable[types.V
|
|||||||
yield from api.enumVMs()
|
yield from api.enumVMs()
|
||||||
|
|
||||||
|
|
||||||
def getNetInfo(api: 'client.OpenNebulaClient', machineId: str, networkId: typing.Optional[str] = None) -> typing.Tuple[str, str]:
|
def getNetInfo(
|
||||||
|
api: 'client.OpenNebulaClient',
|
||||||
|
machineId: str,
|
||||||
|
networkId: typing.Optional[str] = None,
|
||||||
|
) -> typing.Tuple[str, str]:
|
||||||
'''
|
'''
|
||||||
Get the MAC and the IP for the network and machine. If network is None, for the first network
|
Get the MAC and the IP for the network and machine. If network is None, for the first network
|
||||||
'''
|
'''
|
||||||
@ -204,7 +215,9 @@ def getNetInfo(api: 'client.OpenNebulaClient', machineId: str, networkId: typing
|
|||||||
node = nic
|
node = nic
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Exception('No network interface found on template. Please, add a network and republish.')
|
raise Exception(
|
||||||
|
'No network interface found on template. Please, add a network and republish.'
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug(node.toxml())
|
logger.debug(node.toxml())
|
||||||
|
|
||||||
@ -217,10 +230,14 @@ def getNetInfo(api: 'client.OpenNebulaClient', machineId: str, networkId: typing
|
|||||||
|
|
||||||
return (node.getElementsByTagName('MAC')[0].childNodes[0].data, ip)
|
return (node.getElementsByTagName('MAC')[0].childNodes[0].data, ip)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Exception('No network interface found on template. Please, add a network and republish.')
|
raise Exception(
|
||||||
|
'No network interface found on template. Please, add a network and republish.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def getDisplayConnection(api: 'client.OpenNebulaClient', machineId: str) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
def getDisplayConnection(
|
||||||
|
api: 'client.OpenNebulaClient', machineId: str
|
||||||
|
) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||||
'''
|
'''
|
||||||
If machine is not running or there is not a display, will return NONE
|
If machine is not running or there is not a display, will return NONE
|
||||||
SPICE connections should check that 'type' is 'SPICE'
|
SPICE connections should check that 'type' is 'SPICE'
|
||||||
@ -236,17 +253,18 @@ def getDisplayConnection(api: 'client.OpenNebulaClient', machineId: str) -> typi
|
|||||||
except Exception:
|
except Exception:
|
||||||
passwd = ''
|
passwd = ''
|
||||||
|
|
||||||
host = md.getElementsByTagName('HISTORY_RECORDS')[0].lastChild.getElementsByTagName('HOSTNAME')[0].childNodes[0].data
|
host = (
|
||||||
return {
|
md.getElementsByTagName('HISTORY_RECORDS')[0]
|
||||||
'type': type_,
|
.lastChild.getElementsByTagName('HOSTNAME')[0]
|
||||||
'host': host,
|
.childNodes[0]
|
||||||
'port': int(port),
|
.data
|
||||||
'passwd': passwd
|
)
|
||||||
}
|
return {'type': type_, 'host': host, 'port': int(port), 'passwd': passwd}
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return None # No SPICE connection
|
return None # No SPICE connection
|
||||||
|
|
||||||
|
|
||||||
# Sample NIC Content (there will be as much as nics)
|
# Sample NIC Content (there will be as much as nics)
|
||||||
# <NIC>
|
# <NIC>
|
||||||
# <BRIDGE><![CDATA[br0]]></BRIDGE>
|
# <BRIDGE><![CDATA[br0]]></BRIDGE>
|
||||||
|
@ -74,16 +74,72 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
|||||||
# but used for sample purposes
|
# but used for sample purposes
|
||||||
# If we don't indicate an order, the output order of fields will be
|
# If we don't indicate an order, the output order of fields will be
|
||||||
# "random"
|
# "random"
|
||||||
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('OpenNebula Host'), required=True)
|
host = gui.TextField(
|
||||||
port = gui.NumericField(length=5, label=_('Port'), defvalue='2633', order=2, tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'), required=True)
|
length=64, label=_('Host'), order=1, tooltip=_('OpenNebula Host'), required=True
|
||||||
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'))
|
)
|
||||||
username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenNebula'), required=True, defvalue='oneadmin')
|
port = gui.NumericField(
|
||||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenNebula'), required=True)
|
length=5,
|
||||||
|
label=_('Port'),
|
||||||
|
defvalue='2633',
|
||||||
|
order=2,
|
||||||
|
tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
ssl = gui.CheckBoxField(
|
||||||
|
label=_('Use SSL'),
|
||||||
|
order=3,
|
||||||
|
tooltip=_(
|
||||||
|
'If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
username = gui.TextField(
|
||||||
|
length=32,
|
||||||
|
label=_('Username'),
|
||||||
|
order=4,
|
||||||
|
tooltip=_('User with valid privileges on OpenNebula'),
|
||||||
|
required=True,
|
||||||
|
defvalue='oneadmin',
|
||||||
|
)
|
||||||
|
password = gui.PasswordField(
|
||||||
|
lenth=32,
|
||||||
|
label=_('Password'),
|
||||||
|
order=5,
|
||||||
|
tooltip=_('Password of the user of OpenNebula'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
|
maxPreparingServices = gui.NumericField(
|
||||||
maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)
|
length=3,
|
||||||
|
label=_('Creation concurrency'),
|
||||||
|
defvalue='10',
|
||||||
|
minValue=1,
|
||||||
|
maxValue=65536,
|
||||||
|
order=50,
|
||||||
|
tooltip=_('Maximum number of concurrently creating VMs'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
maxRemovingServices = gui.NumericField(
|
||||||
|
length=3,
|
||||||
|
label=_('Removal concurrency'),
|
||||||
|
defvalue='5',
|
||||||
|
minValue=1,
|
||||||
|
maxValue=65536,
|
||||||
|
order=51,
|
||||||
|
tooltip=_('Maximum number of concurrently removing VMs'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
|
||||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenNebula'), required=True, tab=gui.ADVANCED_TAB)
|
timeout = gui.NumericField(
|
||||||
|
length=3,
|
||||||
|
label=_('Timeout'),
|
||||||
|
defvalue='10',
|
||||||
|
order=90,
|
||||||
|
tooltip=_('Timeout in seconds of connection to OpenNebula'),
|
||||||
|
required=True,
|
||||||
|
tab=gui.ADVANCED_TAB,
|
||||||
|
)
|
||||||
|
|
||||||
# Own variables
|
# Own variables
|
||||||
_api: typing.Optional[on.client.OpenNebulaClient] = None
|
_api: typing.Optional[on.client.OpenNebulaClient] = None
|
||||||
@ -102,12 +158,16 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def endpoint(self) -> str:
|
def endpoint(self) -> str:
|
||||||
return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '', self.host.value, self.port.value)
|
return 'http{}://{}:{}/RPC2'.format(
|
||||||
|
's' if self.ssl.isTrue() else '', self.host.value, self.port.value
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self) -> on.client.OpenNebulaClient:
|
def api(self) -> on.client.OpenNebulaClient:
|
||||||
if self._api is None:
|
if self._api is None:
|
||||||
self._api = on.client.OpenNebulaClient(self.username.value, self.password.value, self.endpoint)
|
self._api = on.client.OpenNebulaClient(
|
||||||
|
self.username.value, self.password.value, self.endpoint
|
||||||
|
)
|
||||||
|
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
@ -128,16 +188,23 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.api.version[0] < '4':
|
if self.api.version[0] < '4':
|
||||||
return [False, 'OpenNebula version is not supported (required version 4.1 or newer)']
|
return [
|
||||||
|
False,
|
||||||
|
'OpenNebula version is not supported (required version 4.1 or newer)',
|
||||||
|
]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return [False, '{}'.format(e)]
|
return [False, '{}'.format(e)]
|
||||||
|
|
||||||
return [True, _('Opennebula test connection passed')]
|
return [True, _('Opennebula test connection passed')]
|
||||||
|
|
||||||
def getDatastores(self, datastoreType: int = 0) -> typing.Iterable[on.types.StorageType]:
|
def getDatastores(
|
||||||
|
self, datastoreType: int = 0
|
||||||
|
) -> typing.Iterable[on.types.StorageType]:
|
||||||
yield from on.storage.enumerateDatastores(self.api, datastoreType)
|
yield from on.storage.enumerateDatastores(self.api, datastoreType)
|
||||||
|
|
||||||
def getTemplates(self, force: bool = False) -> typing.Iterable[on.types.TemplateType]:
|
def getTemplates(
|
||||||
|
self, force: bool = False
|
||||||
|
) -> typing.Iterable[on.types.TemplateType]:
|
||||||
yield from on.template.getTemplates(self.api, force)
|
yield from on.template.getTemplates(self.api, force)
|
||||||
|
|
||||||
def makeTemplate(self, fromTemplateId: str, name, toDataStore: str) -> str:
|
def makeTemplate(self, fromTemplateId: str, name, toDataStore: str) -> str:
|
||||||
@ -234,7 +301,9 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
|||||||
'''
|
'''
|
||||||
on.vm.removeMachine(self.api, machineId)
|
on.vm.removeMachine(self.api, machineId)
|
||||||
|
|
||||||
def getNetInfo(self, machineId: str, networkId: typing.Optional[str] = None) -> typing.Tuple[str, str]:
|
def getNetInfo(
|
||||||
|
self, machineId: str, networkId: typing.Optional[str] = None
|
||||||
|
) -> typing.Tuple[str, str]:
|
||||||
'''
|
'''
|
||||||
Changes the mac address of first nic of the machine to the one specified
|
Changes the mac address of first nic of the machine to the one specified
|
||||||
'''
|
'''
|
||||||
@ -253,17 +322,14 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
|||||||
'secure_port': -1,
|
'secure_port': -1,
|
||||||
'monitors': 1,
|
'monitors': 1,
|
||||||
'cert_subject': '',
|
'cert_subject': '',
|
||||||
'ticket': {
|
'ticket': {'value': display['passwd'], 'expiry': ''},
|
||||||
'value': display['passwd'],
|
|
||||||
'expiry': ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def desktopLogin(self, machineId: str, username: str, password: str, domain: str):
|
def desktopLogin(self, machineId: str, username: str, password: str, domain: str) -> typing.Dict[str, typing.Any]:
|
||||||
'''
|
'''
|
||||||
Not provided by OpenNebula API right now
|
Not provided by OpenNebula API right now
|
||||||
'''
|
'''
|
||||||
return
|
return dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test(env: 'Environment', data: 'Module.ValuesType') -> typing.List[typing.Any]:
|
def test(env: 'Environment', data: 'Module.ValuesType') -> typing.List[typing.Any]:
|
||||||
|
@ -38,7 +38,7 @@ from uds.core.util.state import State
|
|||||||
|
|
||||||
# Not imported at runtime, just for type checking
|
# Not imported at runtime, just for type checking
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from . import service
|
from .service import LiveService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -48,21 +48,25 @@ class LivePublication(Publication):
|
|||||||
This class provides the publication of a oVirtLinkedService
|
This class provides the publication of a oVirtLinkedService
|
||||||
"""
|
"""
|
||||||
|
|
||||||
suggestedTime = 2 # : Suggested recheck time if publication is unfinished in seconds
|
suggestedTime = (
|
||||||
|
2 # : Suggested recheck time if publication is unfinished in seconds
|
||||||
|
)
|
||||||
|
|
||||||
_name: str = ''
|
_name: str = ''
|
||||||
_reason: str = ''
|
_reason: str = ''
|
||||||
_templateId: str = ''
|
_templateId: str = ''
|
||||||
_state: str = 'r'
|
_state: str = 'r'
|
||||||
|
|
||||||
def service(self) -> 'service.LiveService':
|
def service(self) -> 'LiveService':
|
||||||
return typing.cast('service.LiveService', super().service())
|
return typing.cast('LiveService', super().service())
|
||||||
|
|
||||||
def marshal(self) -> bytes:
|
def marshal(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
returns data from an instance of Sample Publication serialized
|
returns data from an instance of Sample Publication serialized
|
||||||
"""
|
"""
|
||||||
return '\t'.join(['v1', self._name, self._reason, self._templateId, self._state]).encode('utf8')
|
return '\t'.join(
|
||||||
|
['v1', self._name, self._reason, self._templateId, self._state]
|
||||||
|
).encode('utf8')
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -77,7 +81,9 @@ class LivePublication(Publication):
|
|||||||
"""
|
"""
|
||||||
Realizes the publication of the service
|
Realizes the publication of the service
|
||||||
"""
|
"""
|
||||||
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
|
self._name = self.service().sanitizeVmName(
|
||||||
|
'UDSP ' + self.dsName() + "-" + str(self.revision())
|
||||||
|
)
|
||||||
self._reason = '' # No error, no reason for it
|
self._reason = '' # No error, no reason for it
|
||||||
self._state = 'running'
|
self._state = 'running'
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ class LiveService(Service):
|
|||||||
"""
|
"""
|
||||||
Opennebula Live Service
|
Opennebula Live Service
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : Name to show the administrator. This string will be translated BEFORE
|
# : Name to show the administrator. This string will be translated BEFORE
|
||||||
# : sending it to administration interface, so don't forget to
|
# : sending it to administration interface, so don't forget to
|
||||||
# : mark it as _ (using ugettext_noop)
|
# : mark it as _ (using ugettext_noop)
|
||||||
@ -109,7 +110,7 @@ class LiveService(Service):
|
|||||||
label=_("Datastore"),
|
label=_("Datastore"),
|
||||||
order=100,
|
order=100,
|
||||||
tooltip=_('Service clones datastore'),
|
tooltip=_('Service clones datastore'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
template = gui.ChoiceField(
|
template = gui.ChoiceField(
|
||||||
@ -117,7 +118,7 @@ class LiveService(Service):
|
|||||||
order=110,
|
order=110,
|
||||||
tooltip=_('Service base template'),
|
tooltip=_('Service base template'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
baseName = gui.TextField(
|
baseName = gui.TextField(
|
||||||
@ -126,7 +127,7 @@ class LiveService(Service):
|
|||||||
order=111,
|
order=111,
|
||||||
tooltip=_('Base name for clones from this machine'),
|
tooltip=_('Base name for clones from this machine'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
lenName = gui.NumericField(
|
lenName = gui.NumericField(
|
||||||
@ -136,7 +137,7 @@ class LiveService(Service):
|
|||||||
order=112,
|
order=112,
|
||||||
tooltip=_('Size of numeric part for the names of these machines'),
|
tooltip=_('Size of numeric part for the names of these machines'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||||
@ -149,7 +150,9 @@ class LiveService(Service):
|
|||||||
if not values:
|
if not values:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.baseName.value = validators.validateHostname(self.baseName.value, maxLength=15-self.lenName.num(), asPattern=True)
|
self.baseName.value = validators.validateHostname(
|
||||||
|
self.baseName.value, maxLength=15 - self.lenName.num(), asPattern=True
|
||||||
|
)
|
||||||
|
|
||||||
def parent(self) -> 'OpenNebulaProvider':
|
def parent(self) -> 'OpenNebulaProvider':
|
||||||
return typing.cast('OpenNebulaProvider', super().parent())
|
return typing.cast('OpenNebulaProvider', super().parent())
|
||||||
@ -173,7 +176,9 @@ class LiveService(Service):
|
|||||||
return self.parent().sanitizeVmName(name)
|
return self.parent().sanitizeVmName(name)
|
||||||
|
|
||||||
def makeTemplate(self, templateName: str) -> str:
|
def makeTemplate(self, templateName: str) -> str:
|
||||||
return self.parent().makeTemplate(self.template.value, templateName, self.datastore.value)
|
return self.parent().makeTemplate(
|
||||||
|
self.template.value, templateName, self.datastore.value
|
||||||
|
)
|
||||||
|
|
||||||
def checkTemplatePublished(self, templateId: str) -> bool:
|
def checkTemplatePublished(self, templateId: str) -> bool:
|
||||||
return self.parent().checkTemplatePublished(templateId)
|
return self.parent().checkTemplatePublished(templateId)
|
||||||
@ -288,7 +293,9 @@ class LiveService(Service):
|
|||||||
"""
|
"""
|
||||||
self.parent().removeMachine(machineId)
|
self.parent().removeMachine(machineId)
|
||||||
|
|
||||||
def getNetInfo(self, machineId: str, networkId: typing.Optional[str] = None) -> typing.Tuple[str, str]:
|
def getNetInfo(
|
||||||
|
self, machineId: str, networkId: typing.Optional[str] = None
|
||||||
|
) -> typing.Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Changes the mac address of first nic of the machine to the one specified
|
Changes the mac address of first nic of the machine to the one specified
|
||||||
"""
|
"""
|
||||||
@ -309,5 +316,7 @@ class LiveService(Service):
|
|||||||
def getConsoleConnection(self, machineId: str) -> typing.Dict[str, typing.Any]:
|
def getConsoleConnection(self, machineId: str) -> typing.Dict[str, typing.Any]:
|
||||||
return self.parent().getConsoleConnection(machineId)
|
return self.parent().getConsoleConnection(machineId)
|
||||||
|
|
||||||
def desktopLogin(self, machineId: str, username: str, password: str, domain: str) -> typing.Dict[str, typing.Any]:
|
def desktopLogin(
|
||||||
|
self, machineId: str, username: str, password: str, domain: str
|
||||||
|
) -> typing.Dict[str, typing.Any]:
|
||||||
return self.parent().desktopLogin(machineId, username, password, domain)
|
return self.parent().desktopLogin(machineId, username, password, domain)
|
||||||
|
@ -64,6 +64,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
The logic for managing ovirt deployments (user machines in this case) is here.
|
The logic for managing ovirt deployments (user machines in this case) is here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_name: str = ''
|
_name: str = ''
|
||||||
_ip: str = ''
|
_ip: str = ''
|
||||||
_mac: str = ''
|
_mac: str = ''
|
||||||
@ -92,15 +93,17 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
# Serializable needed methods
|
# Serializable needed methods
|
||||||
def marshal(self) -> bytes:
|
def marshal(self) -> bytes:
|
||||||
return b'\1'.join([
|
return b'\1'.join(
|
||||||
|
[
|
||||||
b'v1',
|
b'v1',
|
||||||
self._name.encode('utf8'),
|
self._name.encode('utf8'),
|
||||||
self._ip.encode('utf8'),
|
self._ip.encode('utf8'),
|
||||||
self._mac.encode('utf8'),
|
self._mac.encode('utf8'),
|
||||||
self._vmid.encode('utf8'),
|
self._vmid.encode('utf8'),
|
||||||
self._reason.encode('utf8'),
|
self._reason.encode('utf8'),
|
||||||
pickle.dumps(self._queue, protocol=0)
|
pickle.dumps(self._queue, protocol=0),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
vals = data.split(b'\1')
|
vals = data.split(b'\1')
|
||||||
@ -115,7 +118,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
def getName(self) -> str:
|
def getName(self) -> str:
|
||||||
if self._name == '':
|
if self._name == '':
|
||||||
try:
|
try:
|
||||||
self._name = self.nameGenerator().get(self.service().getBaseName(), self.service().getLenName())
|
self._name = self.nameGenerator().get(
|
||||||
|
self.service().getBaseName(), self.service().getLenName()
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return NO_MORE_NAMES
|
return NO_MORE_NAMES
|
||||||
return self._name
|
return self._name
|
||||||
@ -188,7 +193,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
self._queue = [opCreate, opWait, opSuspend, opFinish]
|
self._queue = [opCreate, opWait, opSuspend, opFinish]
|
||||||
|
|
||||||
def __checkMachineState(self, chkState: str) -> str:
|
def __checkMachineState(self, chkState: str) -> str:
|
||||||
logger.debug('Checking that state of machine %s (%s) is %s', self._vmid, self._name, chkState)
|
logger.debug(
|
||||||
|
'Checking that state of machine %s (%s) is %s',
|
||||||
|
self._vmid,
|
||||||
|
self._name,
|
||||||
|
chkState,
|
||||||
|
)
|
||||||
status = self.service().getMachineState(self._vmid)
|
status = self.service().getMachineState(self._vmid)
|
||||||
|
|
||||||
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
||||||
@ -263,7 +273,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if op not in fncs:
|
if op not in fncs:
|
||||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at execution queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
fncs[op]()
|
fncs[op]()
|
||||||
|
|
||||||
@ -295,9 +307,13 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
templateId = self.publication().getTemplateId()
|
templateId = self.publication().getTemplateId()
|
||||||
name = self.getName()
|
name = self.getName()
|
||||||
if name == NO_MORE_NAMES:
|
if name == NO_MORE_NAMES:
|
||||||
raise Exception('No more names available for this service. (Increase digits for this service to fix)')
|
raise Exception(
|
||||||
|
'No more names available for this service. (Increase digits for this service to fix)'
|
||||||
|
)
|
||||||
|
|
||||||
name = self.service().sanitizeVmName(name) # OpenNebula don't let us to create machines with more than 15 chars!!!
|
name = self.service().sanitizeVmName(
|
||||||
|
name
|
||||||
|
) # OpenNebula don't let us to create machines with more than 15 chars!!!
|
||||||
|
|
||||||
self._vmid = self.service().deployFromTemplate(name, templateId)
|
self._vmid = self.service().deployFromTemplate(name, templateId)
|
||||||
if self._vmid is None:
|
if self._vmid is None:
|
||||||
@ -388,7 +404,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if op not in fncs:
|
if op not in fncs:
|
||||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
return self.__error(
|
||||||
|
'Unknown operation found at execution queue ({0})'.format(op)
|
||||||
|
)
|
||||||
|
|
||||||
state = fncs[op]()
|
state = fncs[op]()
|
||||||
if state == State.FINISHED:
|
if state == State.FINISHED:
|
||||||
@ -462,4 +480,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
|||||||
}.get(op, '????')
|
}.get(op, '????')
|
||||||
|
|
||||||
def __debug(self, txt: str) -> None:
|
def __debug(self, txt: str) -> None:
|
||||||
logger.debug('State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s', txt, self._name, self._ip, self._mac, self._vmid, [LiveDeployment.__op2str(op) for op in self._queue])
|
logger.debug(
|
||||||
|
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
|
||||||
|
txt,
|
||||||
|
self._name,
|
||||||
|
self._ip,
|
||||||
|
self._mac,
|
||||||
|
self._vmid,
|
||||||
|
[LiveDeployment.__op2str(op) for op in self._queue],
|
||||||
|
)
|
||||||
|
@ -39,6 +39,7 @@ from . import openstack
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def getApi(parameters: typing.Dict[str, str]) -> typing.Tuple[openstack.Client, bool]:
|
def getApi(parameters: typing.Dict[str, str]) -> typing.Tuple[openstack.Client, bool]:
|
||||||
from .provider_legacy import ProviderLegacy
|
from .provider_legacy import ProviderLegacy
|
||||||
from .provider import OpenStackProvider
|
from .provider import OpenStackProvider
|
||||||
@ -61,17 +62,26 @@ def getApi(parameters: typing.Dict[str, str]) -> typing.Tuple[openstack.Client,
|
|||||||
return (provider.api(parameters['project'], parameters['region']), useSubnetsName)
|
return (provider.api(parameters['project'], parameters['region']), useSubnetsName)
|
||||||
|
|
||||||
|
|
||||||
def getResources(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[str, typing.Any]]:
|
def getResources(
|
||||||
|
parameters: typing.Dict[str, str]
|
||||||
|
) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||||
'''
|
'''
|
||||||
This helper is designed as a callback for Project Selector
|
This helper is designed as a callback for Project Selector
|
||||||
'''
|
'''
|
||||||
api, nameFromSubnets = getApi(parameters)
|
api, nameFromSubnets = getApi(parameters)
|
||||||
|
|
||||||
zones = [gui.choiceItem(z, z) for z in api.listAvailabilityZones()]
|
zones = [gui.choiceItem(z, z) for z in api.listAvailabilityZones()]
|
||||||
networks = [gui.choiceItem(z['id'], z['name']) for z in api.listNetworks(nameFromSubnets=nameFromSubnets)]
|
networks = [
|
||||||
|
gui.choiceItem(z['id'], z['name'])
|
||||||
|
for z in api.listNetworks(nameFromSubnets=nameFromSubnets)
|
||||||
|
]
|
||||||
flavors = [gui.choiceItem(z['id'], z['name']) for z in api.listFlavors()]
|
flavors = [gui.choiceItem(z['id'], z['name']) for z in api.listFlavors()]
|
||||||
securityGroups = [gui.choiceItem(z['id'], z['name']) for z in api.listSecurityGroups()]
|
securityGroups = [
|
||||||
volumeTypes = [gui.choiceItem('-', _('None'))] + [gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes()]
|
gui.choiceItem(z['id'], z['name']) for z in api.listSecurityGroups()
|
||||||
|
]
|
||||||
|
volumeTypes = [gui.choiceItem('-', _('None'))] + [
|
||||||
|
gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes()
|
||||||
|
]
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
{'name': 'availabilityZone', 'values': zones},
|
{'name': 'availabilityZone', 'values': zones},
|
||||||
@ -83,14 +93,19 @@ def getResources(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[s
|
|||||||
logger.debug('Return data: %s', data)
|
logger.debug('Return data: %s', data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getVolumes(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[str, typing.Any]]:
|
|
||||||
|
def getVolumes(
|
||||||
|
parameters: typing.Dict[str, str]
|
||||||
|
) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||||
'''
|
'''
|
||||||
This helper is designed as a callback for Zone Selector
|
This helper is designed as a callback for Zone Selector
|
||||||
'''
|
'''
|
||||||
api, _ = getApi(parameters)
|
api, _ = getApi(parameters)
|
||||||
# Source volumes are all available for us
|
# Source volumes are all available for us
|
||||||
# volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']]
|
# volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']]
|
||||||
volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '']
|
volumes = [
|
||||||
|
gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != ''
|
||||||
|
]
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
{'name': 'volume', 'values': volumes},
|
{'name': 'volume', 'values': volumes},
|
||||||
|
@ -38,17 +38,45 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
(
|
(
|
||||||
ACTIVE, BUILDING, DELETED, ERROR,
|
ACTIVE,
|
||||||
HARD_REBOOT, MIGRATING, PASSWORD,
|
BUILDING,
|
||||||
PAUSED, REBOOT, REBUILD, RESCUED,
|
DELETED,
|
||||||
RESIZED, REVERT_RESIZE, SOFT_DELETED,
|
ERROR,
|
||||||
STOPPED, SUSPENDED, UNKNOWN, VERIFY_RESIZE, SHUTOFF
|
HARD_REBOOT,
|
||||||
|
MIGRATING,
|
||||||
|
PASSWORD,
|
||||||
|
PAUSED,
|
||||||
|
REBOOT,
|
||||||
|
REBUILD,
|
||||||
|
RESCUED,
|
||||||
|
RESIZED,
|
||||||
|
REVERT_RESIZE,
|
||||||
|
SOFT_DELETED,
|
||||||
|
STOPPED,
|
||||||
|
SUSPENDED,
|
||||||
|
UNKNOWN,
|
||||||
|
VERIFY_RESIZE,
|
||||||
|
SHUTOFF,
|
||||||
) = (
|
) = (
|
||||||
'ACTIVE', 'BUILDING', 'DELETED', 'ERROR',
|
'ACTIVE',
|
||||||
'HARD_REBOOT', 'MIGRATING', 'PASSWORD',
|
'BUILDING',
|
||||||
'PAUSED', 'REBOOT', 'REBUILD', 'RESCUED',
|
'DELETED',
|
||||||
'RESIZED', 'REVERT_RESIZE', 'SOFT_DELETED',
|
'ERROR',
|
||||||
'STOPPED', 'SUSPENDED', 'UNKNOWN', 'VERIFY_RESIZE', 'SHUTOFF'
|
'HARD_REBOOT',
|
||||||
|
'MIGRATING',
|
||||||
|
'PASSWORD',
|
||||||
|
'PAUSED',
|
||||||
|
'REBOOT',
|
||||||
|
'REBUILD',
|
||||||
|
'RESCUED',
|
||||||
|
'RESIZED',
|
||||||
|
'REVERT_RESIZE',
|
||||||
|
'SOFT_DELETED',
|
||||||
|
'STOPPED',
|
||||||
|
'SUSPENDED',
|
||||||
|
'UNKNOWN',
|
||||||
|
'VERIFY_RESIZE',
|
||||||
|
'SHUTOFF',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -281,7 +281,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
else:
|
else:
|
||||||
self._volume = 'volumev2'
|
self._volume = 'volumev2'
|
||||||
|
|
||||||
|
|
||||||
def ensureAuthenticated(self) -> None:
|
def ensureAuthenticated(self) -> None:
|
||||||
if (
|
if (
|
||||||
self._authenticated is False
|
self._authenticated is False
|
||||||
|
@ -208,7 +208,9 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
length=96,
|
length=96,
|
||||||
label=_('Proxy'),
|
label=_('Proxy'),
|
||||||
order=91,
|
order=91,
|
||||||
tooltip=_('Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'),
|
tooltip=_(
|
||||||
|
'Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
tab=gui.ADVANCED_TAB,
|
tab=gui.ADVANCED_TAB,
|
||||||
)
|
)
|
||||||
@ -233,9 +235,7 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
if self._api is None:
|
if self._api is None:
|
||||||
proxies = None
|
proxies = None
|
||||||
if self.httpsProxy.value.strip():
|
if self.httpsProxy.value.strip():
|
||||||
proxies = {
|
proxies = {'https': self.httpsProxy.value}
|
||||||
'https': self.httpsProxy.value
|
|
||||||
}
|
|
||||||
self._api = openstack.Client(
|
self._api = openstack.Client(
|
||||||
self.endpoint.value,
|
self.endpoint.value,
|
||||||
-1,
|
-1,
|
||||||
@ -247,7 +247,7 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
projectId=projectId,
|
projectId=projectId,
|
||||||
region=region,
|
region=region,
|
||||||
access=self.access.value,
|
access=self.access.value,
|
||||||
proxies=proxies
|
proxies=proxies,
|
||||||
)
|
)
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
|
@ -194,7 +194,9 @@ class ProviderLegacy(ServiceProvider):
|
|||||||
length=96,
|
length=96,
|
||||||
label=_('Proxy'),
|
label=_('Proxy'),
|
||||||
order=91,
|
order=91,
|
||||||
tooltip=_('Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'),
|
tooltip=_(
|
||||||
|
'Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'
|
||||||
|
),
|
||||||
required=False,
|
required=False,
|
||||||
tab=gui.ADVANCED_TAB,
|
tab=gui.ADVANCED_TAB,
|
||||||
)
|
)
|
||||||
|
@ -47,13 +47,16 @@ class LivePublication(Publication):
|
|||||||
"""
|
"""
|
||||||
This class provides the publication of a oVirtLinkedService
|
This class provides the publication of a oVirtLinkedService
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_name: str = ''
|
_name: str = ''
|
||||||
_reason: str = ''
|
_reason: str = ''
|
||||||
_templateId: str = ''
|
_templateId: str = ''
|
||||||
_state: str = 'r'
|
_state: str = 'r'
|
||||||
_destroyAfter: str = 'n'
|
_destroyAfter: str = 'n'
|
||||||
|
|
||||||
suggestedTime = 20 # : Suggested recheck time if publication is unfinished in seconds
|
suggestedTime = (
|
||||||
|
20 # : Suggested recheck time if publication is unfinished in seconds
|
||||||
|
)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""
|
"""
|
||||||
@ -78,7 +81,16 @@ class LivePublication(Publication):
|
|||||||
"""
|
"""
|
||||||
returns data from an instance of Sample Publication serialized
|
returns data from an instance of Sample Publication serialized
|
||||||
"""
|
"""
|
||||||
return '\t'.join(['v1', self._name, self._reason, self._templateId, self._state, self._destroyAfter]).encode('utf8')
|
return '\t'.join(
|
||||||
|
[
|
||||||
|
'v1',
|
||||||
|
self._name,
|
||||||
|
self._reason,
|
||||||
|
self._templateId,
|
||||||
|
self._state,
|
||||||
|
self._destroyAfter,
|
||||||
|
]
|
||||||
|
).encode('utf8')
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -86,13 +98,21 @@ class LivePublication(Publication):
|
|||||||
"""
|
"""
|
||||||
vals = data.decode('utf8').split('\t')
|
vals = data.decode('utf8').split('\t')
|
||||||
if vals[0] == 'v1':
|
if vals[0] == 'v1':
|
||||||
self._name, self._reason, self._templateId, self._state, self._destroyAfter = vals[1:]
|
(
|
||||||
|
self._name,
|
||||||
|
self._reason,
|
||||||
|
self._templateId,
|
||||||
|
self._state,
|
||||||
|
self._destroyAfter,
|
||||||
|
) = vals[1:]
|
||||||
|
|
||||||
def publish(self) -> str:
|
def publish(self) -> str:
|
||||||
"""
|
"""
|
||||||
Realizes the publication of the service
|
Realizes the publication of the service
|
||||||
"""
|
"""
|
||||||
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
|
self._name = self.service().sanitizeVmName(
|
||||||
|
'UDSP ' + self.dsName() + "-" + str(self.revision())
|
||||||
|
)
|
||||||
self._reason = '' # No error, no reason for it
|
self._reason = '' # No error, no reason for it
|
||||||
self._destroyAfter = 'n'
|
self._destroyAfter = 'n'
|
||||||
|
|
||||||
@ -118,7 +138,9 @@ class LivePublication(Publication):
|
|||||||
if self._state == 'available':
|
if self._state == 'available':
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
|
|
||||||
self._state = self.service().getTemplate(self._templateId)['status'] # For next check
|
self._state = self.service().getTemplate(self._templateId)[
|
||||||
|
'status'
|
||||||
|
] # For next check
|
||||||
|
|
||||||
if self._destroyAfter == 'y' and self._state == 'available':
|
if self._destroyAfter == 'y' and self._state == 'available':
|
||||||
return self.destroy()
|
return self.destroy()
|
||||||
|
@ -51,6 +51,7 @@ if typing.TYPE_CHECKING:
|
|||||||
from . import openstack
|
from . import openstack
|
||||||
from .provider import OpenStackProvider
|
from .provider import OpenStackProvider
|
||||||
from .provider_legacy import ProviderLegacy
|
from .provider_legacy import ProviderLegacy
|
||||||
|
|
||||||
Provider = typing.Union[OpenStackProvider, ProviderLegacy]
|
Provider = typing.Union[OpenStackProvider, ProviderLegacy]
|
||||||
|
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ class LiveService(Service):
|
|||||||
"""
|
"""
|
||||||
OpenStack Live Service
|
OpenStack Live Service
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : Name to show the administrator. This string will be translated BEFORE
|
# : Name to show the administrator. This string will be translated BEFORE
|
||||||
# : sending it to administration interface, so don't forget to
|
# : sending it to administration interface, so don't forget to
|
||||||
# : mark it as _ (using ugettext_noop)
|
# : mark it as _ (using ugettext_noop)
|
||||||
@ -104,18 +106,24 @@ class LiveService(Service):
|
|||||||
servicesTypeProvided = (serviceTypes.VDI,)
|
servicesTypeProvided = (serviceTypes.VDI,)
|
||||||
|
|
||||||
# Now the form part
|
# Now the form part
|
||||||
region = gui.ChoiceField(label=_('Region'), order=1, tooltip=_('Service region'), required=True, rdonly=True)
|
region = gui.ChoiceField(
|
||||||
|
label=_('Region'),
|
||||||
|
order=1,
|
||||||
|
tooltip=_('Service region'),
|
||||||
|
required=True,
|
||||||
|
rdonly=True,
|
||||||
|
)
|
||||||
project = gui.ChoiceField(
|
project = gui.ChoiceField(
|
||||||
label=_('Project'),
|
label=_('Project'),
|
||||||
order=2,
|
order=2,
|
||||||
fills={
|
fills={
|
||||||
'callbackName': 'osFillResources',
|
'callbackName': 'osFillResources',
|
||||||
'function': helpers.getResources,
|
'function': helpers.getResources,
|
||||||
'parameters' : ['ov', 'ev', 'project', 'region', 'legacy']
|
'parameters': ['ov', 'ev', 'project', 'region', 'legacy'],
|
||||||
},
|
},
|
||||||
tooltip=_('Project for this service'),
|
tooltip=_('Project for this service'),
|
||||||
required=True,
|
required=True,
|
||||||
rdonly=True
|
rdonly=True,
|
||||||
)
|
)
|
||||||
availabilityZone = gui.ChoiceField(
|
availabilityZone = gui.ChoiceField(
|
||||||
label=_('Availability Zones'),
|
label=_('Availability Zones'),
|
||||||
@ -123,18 +131,49 @@ class LiveService(Service):
|
|||||||
fills={
|
fills={
|
||||||
'callbackName': 'osFillVolumees',
|
'callbackName': 'osFillVolumees',
|
||||||
'function': helpers.getVolumes,
|
'function': helpers.getVolumes,
|
||||||
'parameters' : ['ov', 'ev', 'project', 'region', 'availabilityZone', 'legacy']
|
'parameters': [
|
||||||
|
'ov',
|
||||||
|
'ev',
|
||||||
|
'project',
|
||||||
|
'region',
|
||||||
|
'availabilityZone',
|
||||||
|
'legacy',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
tooltip=_('Service availability zones'),
|
tooltip=_('Service availability zones'),
|
||||||
required=True,
|
required=True,
|
||||||
rdonly=True
|
rdonly=True,
|
||||||
|
)
|
||||||
|
volume = gui.ChoiceField(
|
||||||
|
label=_('Volume'),
|
||||||
|
order=4,
|
||||||
|
tooltip=_('Base volume for service (restricted by availability zone)'),
|
||||||
|
required=True,
|
||||||
|
tab=_('Machine'),
|
||||||
)
|
)
|
||||||
volume = gui.ChoiceField(label=_('Volume'), order=4, tooltip=_('Base volume for service (restricted by availability zone)'), required=True, tab=_('Machine'))
|
|
||||||
# volumeType = gui.ChoiceField(label=_('Volume Type'), order=5, tooltip=_('Volume type for service'), required=True)
|
# volumeType = gui.ChoiceField(label=_('Volume Type'), order=5, tooltip=_('Volume type for service'), required=True)
|
||||||
network = gui.ChoiceField(label=_('Network'), order=6, tooltip=_('Network to attach to this service'), required=True, tab=_('Machine'))
|
network = gui.ChoiceField(
|
||||||
flavor = gui.ChoiceField(label=_('Flavor'), order=7, tooltip=_('Flavor for service'), required=True, tab=_('Machine'))
|
label=_('Network'),
|
||||||
|
order=6,
|
||||||
|
tooltip=_('Network to attach to this service'),
|
||||||
|
required=True,
|
||||||
|
tab=_('Machine'),
|
||||||
|
)
|
||||||
|
flavor = gui.ChoiceField(
|
||||||
|
label=_('Flavor'),
|
||||||
|
order=7,
|
||||||
|
tooltip=_('Flavor for service'),
|
||||||
|
required=True,
|
||||||
|
tab=_('Machine'),
|
||||||
|
)
|
||||||
|
|
||||||
securityGroups = gui.MultiChoiceField(label=_('Security Groups'), order=8, tooltip=_('Service security groups'), required=True, tab=_('Machine'))
|
securityGroups = gui.MultiChoiceField(
|
||||||
|
label=_('Security Groups'),
|
||||||
|
order=8,
|
||||||
|
tooltip=_('Service security groups'),
|
||||||
|
required=True,
|
||||||
|
tab=_('Machine'),
|
||||||
|
)
|
||||||
|
|
||||||
baseName = gui.TextField(
|
baseName = gui.TextField(
|
||||||
label=_('Machine Names'),
|
label=_('Machine Names'),
|
||||||
@ -142,7 +181,7 @@ class LiveService(Service):
|
|||||||
order=9,
|
order=9,
|
||||||
tooltip=_('Base name for clones from this machine'),
|
tooltip=_('Base name for clones from this machine'),
|
||||||
required=True,
|
required=True,
|
||||||
tab=_('Machine')
|
tab=_('Machine'),
|
||||||
)
|
)
|
||||||
|
|
||||||
lenName = gui.NumericField(
|
lenName = gui.NumericField(
|
||||||
@ -152,12 +191,14 @@ class LiveService(Service):
|
|||||||
order=10,
|
order=10,
|
||||||
tooltip=_('Size of numeric part for the names of these machines'),
|
tooltip=_('Size of numeric part for the names of these machines'),
|
||||||
required=True,
|
required=True,
|
||||||
tab=_('Machine')
|
tab=_('Machine'),
|
||||||
)
|
)
|
||||||
|
|
||||||
ov = gui.HiddenField(value=None)
|
ov = gui.HiddenField(value=None)
|
||||||
ev = gui.HiddenField(value=None)
|
ev = gui.HiddenField(value=None)
|
||||||
legacy = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
|
legacy = gui.HiddenField(
|
||||||
|
value=None
|
||||||
|
) # We need to keep the env so we can instantiate the Provider
|
||||||
|
|
||||||
_api: typing.Optional['openstack.Client'] = None
|
_api: typing.Optional['openstack.Client'] = None
|
||||||
|
|
||||||
@ -183,15 +224,26 @@ class LiveService(Service):
|
|||||||
"""
|
"""
|
||||||
api = self.parent().api()
|
api = self.parent().api()
|
||||||
|
|
||||||
if not self.parent().legacy and self.parent().region.value:
|
# Checks if legacy or current openstack provider
|
||||||
regions = [gui.choiceItem(self.parent().region.value, self.parent().region.value)]
|
parentCurrent = (
|
||||||
|
typing.cast('OpenStackProvider', self.parent())
|
||||||
|
if not self.parent().legacy
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if parentCurrent and parentCurrent.region.value:
|
||||||
|
regions = [
|
||||||
|
gui.choiceItem(parentCurrent.region.value, parentCurrent.region.value)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()]
|
regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()]
|
||||||
|
|
||||||
self.region.setValues(regions)
|
self.region.setValues(regions)
|
||||||
|
|
||||||
if not self.parent().legacy and self.parent().tenant.value:
|
if parentCurrent and parentCurrent.tenant.value:
|
||||||
tenants = [gui.choiceItem(self.parent().tenant.value, self.parent().tenant.value)]
|
tenants = [
|
||||||
|
gui.choiceItem(parentCurrent.tenant.value, parentCurrent.tenant.value)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
tenants = [gui.choiceItem(t['id'], t['name']) for t in api.listProjects()]
|
tenants = [gui.choiceItem(t['id'], t['name']) for t in api.listProjects()]
|
||||||
self.project.setValues(tenants)
|
self.project.setValues(tenants)
|
||||||
@ -206,7 +258,9 @@ class LiveService(Service):
|
|||||||
@property
|
@property
|
||||||
def api(self) -> 'openstack.Client':
|
def api(self) -> 'openstack.Client':
|
||||||
if not self._api:
|
if not self._api:
|
||||||
self._api = self.parent().api(projectId=self.project.value, region=self.region.value)
|
self._api = self.parent().api(
|
||||||
|
projectId=self.project.value, region=self.region.value
|
||||||
|
)
|
||||||
|
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
@ -219,7 +273,9 @@ class LiveService(Service):
|
|||||||
# raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')
|
# raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')
|
||||||
|
|
||||||
description = description or 'UDS Template snapshot'
|
description = description or 'UDS Template snapshot'
|
||||||
return self.api.createVolumeSnapshot(self.volume.value, templateName, description)
|
return self.api.createVolumeSnapshot(
|
||||||
|
self.volume.value, templateName, description
|
||||||
|
)
|
||||||
|
|
||||||
def getTemplate(self, snapshotId: str):
|
def getTemplate(self, snapshotId: str):
|
||||||
"""
|
"""
|
||||||
@ -247,7 +303,7 @@ class LiveService(Service):
|
|||||||
availabilityZone=self.availabilityZone.value,
|
availabilityZone=self.availabilityZone.value,
|
||||||
flavorId=self.flavor.value,
|
flavorId=self.flavor.value,
|
||||||
networkId=self.network.value,
|
networkId=self.network.value,
|
||||||
securityGroupsIdsList=self.securityGroups.value
|
securityGroupsIdsList=self.securityGroups.value,
|
||||||
)['id']
|
)['id']
|
||||||
|
|
||||||
def removeTemplate(self, templateId: str) -> None:
|
def removeTemplate(self, templateId: str) -> None:
|
||||||
@ -286,7 +342,12 @@ class LiveService(Service):
|
|||||||
"""
|
"""
|
||||||
server = self.api.getServer(machineId)
|
server = self.api.getServer(machineId)
|
||||||
if server['status'] in ('ERROR', 'DELETED'):
|
if server['status'] in ('ERROR', 'DELETED'):
|
||||||
logger.warning('Got server status %s for %s: %s', server['status'], machineId, server.get('fault'))
|
logger.warning(
|
||||||
|
'Got server status %s for %s: %s',
|
||||||
|
server['status'],
|
||||||
|
machineId,
|
||||||
|
server.get('fault'),
|
||||||
|
)
|
||||||
return server['status']
|
return server['status']
|
||||||
|
|
||||||
def startMachine(self, machineId: str) -> None:
|
def startMachine(self, machineId: str) -> None:
|
||||||
@ -362,7 +423,9 @@ class LiveService(Service):
|
|||||||
Gets the mac address of first nic of the machine
|
Gets the mac address of first nic of the machine
|
||||||
"""
|
"""
|
||||||
net = self.api.getServer(machineId)['addresses']
|
net = self.api.getServer(machineId)['addresses']
|
||||||
vals = next(iter(net.values()))[0] # Returns "any" mac address of any interface. We just need only one interface info
|
vals = next(iter(net.values()))[
|
||||||
|
0
|
||||||
|
] # Returns "any" mac address of any interface. We just need only one interface info
|
||||||
# vals = six.next(six.itervalues(net))[0]
|
# vals = six.next(six.itervalues(net))[0]
|
||||||
return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']
|
return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']
|
||||||
|
|
||||||
|
@ -81,7 +81,9 @@ class IPMachineDeployed(services.UserDeployment, AutoAttributes):
|
|||||||
res = dns.resolver.resolve(ip)
|
res = dns.resolver.resolve(ip)
|
||||||
ip = res[0].address
|
ip = res[0].address
|
||||||
except Exception:
|
except Exception:
|
||||||
self.service().parent().doLog(log.WARN, f'User service could not resolve Name {ip}.')
|
self.service().parent().doLog(
|
||||||
|
log.WARN, f'User service could not resolve Name {ip}.'
|
||||||
|
)
|
||||||
|
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
@ -148,11 +148,7 @@ class PhysicalMachinesProvider(services.ServiceProvider):
|
|||||||
config.read_string(self.config.value)
|
config.read_string(self.config.value)
|
||||||
for key in config['wol']:
|
for key in config['wol']:
|
||||||
if net.ipInNetwork(ip, key):
|
if net.ipInNetwork(ip, key):
|
||||||
return (
|
return config['wol'][key].replace('{MAC}', mac).replace('{IP}', ip)
|
||||||
config['wol'][key]
|
|
||||||
.replace('{MAC}', mac)
|
|
||||||
.replace('{IP}', ip)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Error parsing advanced configuration: %s', e)
|
logger.error('Error parsing advanced configuration: %s', e)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
# may be used to endorse or promote products derived from this software
|
# may be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
#
|
#
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"u
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
@ -73,10 +73,7 @@ class IPServiceBase(services.Service):
|
|||||||
|
|
||||||
def wakeup(self, ip: str, mac: typing.Optional[str]) -> None:
|
def wakeup(self, ip: str, mac: typing.Optional[str]) -> None:
|
||||||
if mac:
|
if mac:
|
||||||
wolurl = (
|
wolurl = self.parent().wolURL(ip, mac)
|
||||||
self.parent()
|
|
||||||
.wolURL(ip, mac)
|
|
||||||
)
|
|
||||||
if wolurl:
|
if wolurl:
|
||||||
logger.info('Launching WOL: %s', wolurl)
|
logger.info('Launching WOL: %s', wolurl)
|
||||||
try:
|
try:
|
||||||
|
@ -51,7 +51,13 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class IPSingleMachineService(IPServiceBase):
|
class IPSingleMachineService(IPServiceBase):
|
||||||
# Gui
|
# Gui
|
||||||
ip = gui.TextField(length=64, label=_('Machine IP'), order=1, tooltip=_('Machine IP'), required=True)
|
ip = gui.TextField(
|
||||||
|
length=64,
|
||||||
|
label=_('Machine IP'),
|
||||||
|
order=1,
|
||||||
|
tooltip=_('Machine IP'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Description of service
|
# Description of service
|
||||||
typeName = _('Static Single IP')
|
typeName = _('Static Single IP')
|
||||||
@ -60,7 +66,9 @@ class IPSingleMachineService(IPServiceBase):
|
|||||||
iconFile = 'machine.png'
|
iconFile = 'machine.png'
|
||||||
|
|
||||||
# Characteristics of service
|
# Characteristics of service
|
||||||
maxDeployed = -1 # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
|
maxDeployed = (
|
||||||
|
-1
|
||||||
|
) # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy
|
||||||
usesCache = False # Cache are running machine awaiting to be assigned
|
usesCache = False # Cache are running machine awaiting to be assigned
|
||||||
usesCache_L2 = False # L2 Cache are running machines in suspended state
|
usesCache_L2 = False # L2 Cache are running machines in suspended state
|
||||||
needsManager = False # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
|
needsManager = False # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent)
|
||||||
@ -75,7 +83,9 @@ class IPSingleMachineService(IPServiceBase):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not net.isValidHost(self.ip.value):
|
if not net.isValidHost(self.ip.value):
|
||||||
raise IPServiceBase.ValidationException(gettext('Invalid server used: "{}"'.format(self.ip.value)))
|
raise IPServiceBase.ValidationException(
|
||||||
|
gettext('Invalid server used: "{}"'.format(self.ip.value))
|
||||||
|
)
|
||||||
|
|
||||||
def getUnassignedMachine(self) -> typing.Optional[str]:
|
def getUnassignedMachine(self) -> typing.Optional[str]:
|
||||||
ip: typing.Optional[str] = None
|
ip: typing.Optional[str] = None
|
||||||
|
@ -13,8 +13,18 @@ conversors: typing.MutableMapping[typing.Type, typing.Callable] = {
|
|||||||
datetime.datetime: lambda x: datetime.datetime.fromtimestamp(int(x)),
|
datetime.datetime: lambda x: datetime.datetime.fromtimestamp(int(x)),
|
||||||
}
|
}
|
||||||
|
|
||||||
def convertFromDict(type: typing.Type[typing.Any], dictionary: typing.MutableMapping[str, typing.Any]) -> typing.Any:
|
|
||||||
return type(**{ k:conversors.get(type.__annotations__.get(k, str), lambda x: x)(dictionary.get(k, None)) for k in type._fields})
|
def convertFromDict(
|
||||||
|
type: typing.Type[typing.Any], dictionary: typing.MutableMapping[str, typing.Any]
|
||||||
|
) -> typing.Any:
|
||||||
|
return type(
|
||||||
|
**{
|
||||||
|
k: conversors.get(type.__annotations__.get(k, str), lambda x: x)(
|
||||||
|
dictionary.get(k, None)
|
||||||
|
)
|
||||||
|
for k in type._fields
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Cluster(typing.NamedTuple):
|
class Cluster(typing.NamedTuple):
|
||||||
@ -28,6 +38,7 @@ class Cluster(typing.NamedTuple):
|
|||||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Cluster':
|
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Cluster':
|
||||||
return convertFromDict(Cluster, dictionary)
|
return convertFromDict(Cluster, dictionary)
|
||||||
|
|
||||||
|
|
||||||
class Node(typing.NamedTuple):
|
class Node(typing.NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
online: bool
|
online: bool
|
||||||
@ -41,6 +52,7 @@ class Node(typing.NamedTuple):
|
|||||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Node':
|
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Node':
|
||||||
return convertFromDict(Node, dictionary)
|
return convertFromDict(Node, dictionary)
|
||||||
|
|
||||||
|
|
||||||
class NodeStats(typing.NamedTuple):
|
class NodeStats(typing.NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
status: str
|
status: str
|
||||||
@ -61,7 +73,20 @@ class NodeStats(typing.NamedTuple):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def empty():
|
def empty():
|
||||||
return NodeStats(name='', status='offline', uptime=0, disk=0, maxdisk=0, level='', id='', mem=1, maxmem=1, cpu=1, maxcpu=1)
|
return NodeStats(
|
||||||
|
name='',
|
||||||
|
status='offline',
|
||||||
|
uptime=0,
|
||||||
|
disk=0,
|
||||||
|
maxdisk=0,
|
||||||
|
level='',
|
||||||
|
id='',
|
||||||
|
mem=1,
|
||||||
|
maxmem=1,
|
||||||
|
cpu=1,
|
||||||
|
maxcpu=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClusterStatus(typing.NamedTuple):
|
class ClusterStatus(typing.NamedTuple):
|
||||||
cluster: typing.Optional[Cluster]
|
cluster: typing.Optional[Cluster]
|
||||||
@ -80,6 +105,7 @@ class ClusterStatus(typing.NamedTuple):
|
|||||||
|
|
||||||
return ClusterStatus(cluster=cluster, nodes=nodes)
|
return ClusterStatus(cluster=cluster, nodes=nodes)
|
||||||
|
|
||||||
|
|
||||||
class UPID(typing.NamedTuple):
|
class UPID(typing.NamedTuple):
|
||||||
node: str
|
node: str
|
||||||
pid: int
|
pid: int
|
||||||
@ -102,9 +128,10 @@ class UPID(typing.NamedTuple):
|
|||||||
type=d[5],
|
type=d[5],
|
||||||
vmid=int(d[6]),
|
vmid=int(d[6]),
|
||||||
user=d[7],
|
user=d[7],
|
||||||
upid=upid
|
upid=upid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskStatus(typing.NamedTuple):
|
class TaskStatus(typing.NamedTuple):
|
||||||
node: str
|
node: str
|
||||||
pid: int
|
pid: int
|
||||||
@ -133,6 +160,7 @@ class TaskStatus(typing.NamedTuple):
|
|||||||
def isErrored(self) -> bool:
|
def isErrored(self) -> bool:
|
||||||
return self.isFinished() and not self.isCompleted()
|
return self.isFinished() and not self.isCompleted()
|
||||||
|
|
||||||
|
|
||||||
class NetworkConfiguration(typing.NamedTuple):
|
class NetworkConfiguration(typing.NamedTuple):
|
||||||
type: str
|
type: str
|
||||||
mac: str
|
mac: str
|
||||||
@ -154,7 +182,9 @@ class VMInfo(typing.NamedTuple):
|
|||||||
template: bool
|
template: bool
|
||||||
|
|
||||||
cpus: typing.Optional[int]
|
cpus: typing.Optional[int]
|
||||||
lock: typing.Optional[str] # if suspended, lock == "suspended" & qmpstatus == "stopped"
|
lock: typing.Optional[
|
||||||
|
str
|
||||||
|
] # if suspended, lock == "suspended" & qmpstatus == "stopped"
|
||||||
disk: typing.Optional[int]
|
disk: typing.Optional[int]
|
||||||
maxdisk: typing.Optional[int]
|
maxdisk: typing.Optional[int]
|
||||||
mem: typing.Optional[int]
|
mem: typing.Optional[int]
|
||||||
@ -173,6 +203,7 @@ class VMInfo(typing.NamedTuple):
|
|||||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'VMInfo':
|
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'VMInfo':
|
||||||
return convertFromDict(VMInfo, dictionary)
|
return convertFromDict(VMInfo, dictionary)
|
||||||
|
|
||||||
|
|
||||||
class VMConfiguration(typing.NamedTuple):
|
class VMConfiguration(typing.NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
vga: str
|
vga: str
|
||||||
@ -185,7 +216,9 @@ class VMConfiguration(typing.NamedTuple):
|
|||||||
template: bool
|
template: bool
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'VMConfiguration':
|
def fromDict(
|
||||||
|
dictionary: typing.MutableMapping[str, typing.Any]
|
||||||
|
) -> 'VMConfiguration':
|
||||||
nets: typing.List[NetworkConfiguration] = []
|
nets: typing.List[NetworkConfiguration] = []
|
||||||
for k in dictionary.keys():
|
for k in dictionary.keys():
|
||||||
if k[:3] == 'net':
|
if k[:3] == 'net':
|
||||||
@ -194,11 +227,13 @@ class VMConfiguration(typing.NamedTuple):
|
|||||||
dictionary['networks'] = nets
|
dictionary['networks'] = nets
|
||||||
return convertFromDict(VMConfiguration, dictionary)
|
return convertFromDict(VMConfiguration, dictionary)
|
||||||
|
|
||||||
|
|
||||||
class VmCreationResult(typing.NamedTuple):
|
class VmCreationResult(typing.NamedTuple):
|
||||||
node: str
|
node: str
|
||||||
vmid: int
|
vmid: int
|
||||||
upid: UPID
|
upid: UPID
|
||||||
|
|
||||||
|
|
||||||
class StorageInfo(typing.NamedTuple):
|
class StorageInfo(typing.NamedTuple):
|
||||||
node: str
|
node: str
|
||||||
storage: str
|
storage: str
|
||||||
@ -212,11 +247,11 @@ class StorageInfo(typing.NamedTuple):
|
|||||||
total: int
|
total: int
|
||||||
used_fraction: float
|
used_fraction: float
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'StorageInfo':
|
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'StorageInfo':
|
||||||
return convertFromDict(StorageInfo, dictionary)
|
return convertFromDict(StorageInfo, dictionary)
|
||||||
|
|
||||||
|
|
||||||
class PoolInfo(typing.NamedTuple):
|
class PoolInfo(typing.NamedTuple):
|
||||||
poolid: str
|
poolid: str
|
||||||
comments: str
|
comments: str
|
||||||
|
@ -35,10 +35,12 @@ from django.utils.translation import ugettext as _
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||||
from .provider import ProxmoxProvider
|
from .provider import ProxmoxProvider
|
||||||
|
|
||||||
from uds.core.environment import Environment
|
from uds.core.environment import Environment
|
||||||
|
|
||||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||||
env = Environment(parameters['ev'])
|
env = Environment(parameters['ev'])
|
||||||
provider: ProxmoxProvider = ProxmoxProvider(env)
|
provider: ProxmoxProvider = ProxmoxProvider(env)
|
||||||
@ -54,19 +56,27 @@ def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.An
|
|||||||
|
|
||||||
res = []
|
res = []
|
||||||
# Get storages for that datacenter
|
# Get storages for that datacenter
|
||||||
for storage in sorted(provider.listStorages(vmInfo.node), key=lambda x: int(not x.shared)):
|
for storage in sorted(
|
||||||
|
provider.listStorages(vmInfo.node), key=lambda x: int(not x.shared)
|
||||||
|
):
|
||||||
if storage.type in ('lvm', 'iscsi', 'iscsidirect'):
|
if storage.type in ('lvm', 'iscsi', 'iscsidirect'):
|
||||||
continue
|
continue
|
||||||
space, free = storage.avail / 1024 / 1024 / 1024, (storage.avail - storage.used) / 1024 / 1024 / 1024
|
space, free = (
|
||||||
extra = _(' shared') if storage.shared else _(' (bound to {})').format(vmInfo.node)
|
storage.avail / 1024 / 1024 / 1024,
|
||||||
res.append({'id': storage.storage, 'text': "%s (%4.2f GB/%4.2f GB)%s" % (storage.storage, space, free, extra)})
|
(storage.avail - storage.used) / 1024 / 1024 / 1024,
|
||||||
|
)
|
||||||
data = [
|
extra = (
|
||||||
|
_(' shared') if storage.shared else _(' (bound to {})').format(vmInfo.node)
|
||||||
|
)
|
||||||
|
res.append(
|
||||||
{
|
{
|
||||||
'name': 'datastore',
|
'id': storage.storage,
|
||||||
'values': res
|
'text': "%s (%4.2f GB/%4.2f GB)%s"
|
||||||
|
% (storage.storage, space, free, extra),
|
||||||
}
|
}
|
||||||
]
|
)
|
||||||
|
|
||||||
|
data = [{'name': 'datastore', 'values': res}]
|
||||||
|
|
||||||
logger.debug('return data: %s', data)
|
logger.debug('return data: %s', data)
|
||||||
return data
|
return data
|
||||||
|
@ -52,6 +52,7 @@ logger = logging.getLogger(__name__)
|
|||||||
CACHE_TIME_FOR_SERVER = 1800
|
CACHE_TIME_FOR_SERVER = 1800
|
||||||
MAX_VM_ID = 999999999
|
MAX_VM_ID = 999999999
|
||||||
|
|
||||||
|
|
||||||
class ProxmoxProvider(
|
class ProxmoxProvider(
|
||||||
services.ServiceProvider
|
services.ServiceProvider
|
||||||
): # pylint: disable=too-many-public-methods
|
): # pylint: disable=too-many-public-methods
|
||||||
@ -223,7 +224,14 @@ class ProxmoxProvider(
|
|||||||
toPool: typing.Optional[str] = None,
|
toPool: typing.Optional[str] = None,
|
||||||
) -> client.types.VmCreationResult:
|
) -> client.types.VmCreationResult:
|
||||||
return self.__getApi().cloneVm(
|
return self.__getApi().cloneVm(
|
||||||
vmId, self.getNewVmId(), name, description, linkedClone, toNode, toStorage, toPool
|
vmId,
|
||||||
|
self.getNewVmId(),
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
linkedClone,
|
||||||
|
toNode,
|
||||||
|
toStorage,
|
||||||
|
toPool,
|
||||||
)
|
)
|
||||||
|
|
||||||
def startMachine(self, vmId: int) -> client.types.UPID:
|
def startMachine(self, vmId: int) -> client.types.UPID:
|
||||||
|
@ -45,6 +45,7 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ProxmoxPublication(services.Publication):
|
class ProxmoxPublication(services.Publication):
|
||||||
suggestedTime = 20
|
suggestedTime = 20
|
||||||
|
|
||||||
@ -74,7 +75,18 @@ class ProxmoxPublication(services.Publication):
|
|||||||
"""
|
"""
|
||||||
returns data from an instance of Sample Publication serialized
|
returns data from an instance of Sample Publication serialized
|
||||||
"""
|
"""
|
||||||
return '\t'.join(['v1', self._name, self._vm, self._task, self._state, self._operation, self._destroyAfter, self._reason]).encode('utf8')
|
return '\t'.join(
|
||||||
|
[
|
||||||
|
'v1',
|
||||||
|
self._name,
|
||||||
|
self._vm,
|
||||||
|
self._task,
|
||||||
|
self._state,
|
||||||
|
self._operation,
|
||||||
|
self._destroyAfter,
|
||||||
|
self._reason,
|
||||||
|
]
|
||||||
|
).encode('utf8')
|
||||||
|
|
||||||
def unmarshal(self, data: bytes) -> None:
|
def unmarshal(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -83,7 +95,15 @@ class ProxmoxPublication(services.Publication):
|
|||||||
logger.debug('Data: %s', data)
|
logger.debug('Data: %s', data)
|
||||||
vals = data.decode('utf8').split('\t')
|
vals = data.decode('utf8').split('\t')
|
||||||
if vals[0] == 'v1':
|
if vals[0] == 'v1':
|
||||||
self._name, self._vm, self._task, self._state, self._operation, self._destroyAfter, self._reason = vals[1:]
|
(
|
||||||
|
self._name,
|
||||||
|
self._vm,
|
||||||
|
self._task,
|
||||||
|
self._state,
|
||||||
|
self._operation,
|
||||||
|
self._destroyAfter,
|
||||||
|
self._reason,
|
||||||
|
) = vals[1:]
|
||||||
|
|
||||||
def publish(self) -> str:
|
def publish(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -91,8 +111,17 @@ class ProxmoxPublication(services.Publication):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# First we should create a full clone, so base machine do not get fullfilled with "garbage" delta disks...
|
# First we should create a full clone, so base machine do not get fullfilled with "garbage" delta disks...
|
||||||
self._name = 'UDS ' + _('Publication') + ' ' + self.dsName() + "-" + str(self.revision())
|
self._name = (
|
||||||
comments = _('UDS Publication for {0} created at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
|
'UDS '
|
||||||
|
+ _('Publication')
|
||||||
|
+ ' '
|
||||||
|
+ self.dsName()
|
||||||
|
+ "-"
|
||||||
|
+ str(self.revision())
|
||||||
|
)
|
||||||
|
comments = _('UDS Publication for {0} created at {1}').format(
|
||||||
|
self.dsName(), str(datetime.now()).split('.')[0]
|
||||||
|
)
|
||||||
task = self.service().cloneMachine(self._name, comments)
|
task = self.service().cloneMachine(self._name, comments)
|
||||||
self._vm = str(task.vmid)
|
self._vm = str(task.vmid)
|
||||||
self._task = ','.join((task.upid.node, task.upid.upid))
|
self._task = ','.join((task.upid.node, task.upid.upid))
|
||||||
@ -105,7 +134,9 @@ class ProxmoxPublication(services.Publication):
|
|||||||
self._reason = str(e)
|
self._reason = str(e)
|
||||||
return State.ERROR
|
return State.ERROR
|
||||||
|
|
||||||
def checkState(self) -> str: # pylint: disable = too-many-branches,too-many-return-statements
|
def checkState(
|
||||||
|
self,
|
||||||
|
) -> str: # pylint: disable = too-many-branches,too-many-return-statements
|
||||||
if self._state != State.RUNNING:
|
if self._state != State.RUNNING:
|
||||||
return self._state
|
return self._state
|
||||||
node, upid = self._task.split(',')
|
node, upid = self._task.split(',')
|
||||||
@ -129,7 +160,9 @@ class ProxmoxPublication(services.Publication):
|
|||||||
if self._operation == 'p': # not Destroying
|
if self._operation == 'p': # not Destroying
|
||||||
# Disable Protection (removal)
|
# Disable Protection (removal)
|
||||||
self.service().setProtection(int(self._vm), protection=False)
|
self.service().setProtection(int(self._vm), protection=False)
|
||||||
time.sleep(0.5) # Give some tome to proxmox. We have observed some concurrency issues
|
time.sleep(
|
||||||
|
0.5
|
||||||
|
) # Give some tome to proxmox. We have observed some concurrency issues
|
||||||
# And add it to HA if
|
# And add it to HA if
|
||||||
self.service().enableHA(int(self._vm))
|
self.service().enableHA(int(self._vm))
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
@ -147,7 +180,9 @@ class ProxmoxPublication(services.Publication):
|
|||||||
self._destroyAfter = ''
|
self._destroyAfter = ''
|
||||||
|
|
||||||
def destroy(self) -> str:
|
def destroy(self) -> str:
|
||||||
if self._state == State.RUNNING and self._destroyAfter is False: # If called destroy twice, will BREAK STOP publication
|
if (
|
||||||
|
self._state == State.RUNNING and self._destroyAfter is False
|
||||||
|
): # If called destroy twice, will BREAK STOP publication
|
||||||
self._destroyAfter = 'y'
|
self._destroyAfter = 'y'
|
||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
Proxmox Linked clones service. This is based on creating a template from selected vm, and then use it to
|
Proxmox Linked clones service. This is based on creating a template from selected vm, and then use it to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# : Name to show the administrator. This string will be translated BEFORE
|
# : Name to show the administrator. This string will be translated BEFORE
|
||||||
# : sending it to administration interface, so don't forget to
|
# : sending it to administration interface, so don't forget to
|
||||||
# : mark it as _ (using ugettext_noop)
|
# : mark it as _ (using ugettext_noop)
|
||||||
@ -112,14 +113,14 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
tooltip=_('Pool that will contain UDS created vms'),
|
tooltip=_('Pool that will contain UDS created vms'),
|
||||||
# tab=_('Machine'),
|
# tab=_('Machine'),
|
||||||
# required=True,
|
# required=True,
|
||||||
defvalue=''
|
defvalue='',
|
||||||
)
|
)
|
||||||
|
|
||||||
ha = gui.ChoiceField(
|
ha = gui.ChoiceField(
|
||||||
label=_('HA'),
|
label=_('HA'),
|
||||||
order=2,
|
order=2,
|
||||||
tooltip=_('Select if HA is enabled and HA group for machines of this service'),
|
tooltip=_('Select if HA is enabled and HA group for machines of this service'),
|
||||||
rdonly=True
|
rdonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
guestShutdown = gui.CheckBoxField(
|
guestShutdown = gui.CheckBoxField(
|
||||||
@ -137,11 +138,11 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
fills={
|
fills={
|
||||||
'callbackName': 'pmFillResourcesFromMachine',
|
'callbackName': 'pmFillResourcesFromMachine',
|
||||||
'function': helpers.getStorage,
|
'function': helpers.getStorage,
|
||||||
'parameters': ['machine', 'ov', 'ev']
|
'parameters': ['machine', 'ov', 'ev'],
|
||||||
},
|
},
|
||||||
tooltip=_('Service base machine'),
|
tooltip=_('Service base machine'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
datastore = gui.ChoiceField(
|
datastore = gui.ChoiceField(
|
||||||
@ -150,7 +151,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=111,
|
order=111,
|
||||||
tooltip=_('Storage for publications & machines.'),
|
tooltip=_('Storage for publications & machines.'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
baseName = gui.TextField(
|
baseName = gui.TextField(
|
||||||
@ -159,7 +160,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=115,
|
order=115,
|
||||||
tooltip=_('Base name for clones from this machine'),
|
tooltip=_('Base name for clones from this machine'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
lenName = gui.NumericField(
|
lenName = gui.NumericField(
|
||||||
@ -169,15 +170,19 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
order=116,
|
order=116,
|
||||||
tooltip=_('Size of numeric part for the names of these machines'),
|
tooltip=_('Size of numeric part for the names of these machines'),
|
||||||
tab=_('Machine'),
|
tab=_('Machine'),
|
||||||
required=True
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
ov = gui.HiddenField(value=None)
|
ov = gui.HiddenField(value=None)
|
||||||
ev = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
|
ev = gui.HiddenField(
|
||||||
|
value=None
|
||||||
|
) # We need to keep the env so we can instantiate the Provider
|
||||||
|
|
||||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||||
if values:
|
if values:
|
||||||
self.baseName.value = validators.validateHostname(self.baseName.value, 15, asPattern=True)
|
self.baseName.value = validators.validateHostname(
|
||||||
|
self.baseName.value, 15, asPattern=True
|
||||||
|
)
|
||||||
# if int(self.memory.value) < 128:
|
# if int(self.memory.value) < 128:
|
||||||
# raise Service.ValidationException(_('The minimum allowed memory is 128 Mb'))
|
# raise Service.ValidationException(_('The minimum allowed memory is 128 Mb'))
|
||||||
|
|
||||||
@ -190,16 +195,23 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
|
|
||||||
# This is not the same case, values is not the "value" of the field, but
|
# This is not the same case, values is not the "value" of the field, but
|
||||||
# the list of values shown because this is a "ChoiceField"
|
# the list of values shown because this is a "ChoiceField"
|
||||||
self.machine.setValues([gui.choiceItem(str(m.vmid), '{}\\{} ({})'.format(m.node, m.name or m.vmid, m.vmid)) for m in self.parent().listMachines() if m.name and m.name[:3] != 'UDS'])
|
self.machine.setValues(
|
||||||
self.pool.setValues([gui.choiceItem('', _('None'))] + [gui.choiceItem(p.poolid, p.poolid) for p in self.parent().listPools()])
|
|
||||||
self.ha.setValues(
|
|
||||||
[
|
[
|
||||||
gui.choiceItem('', _('Enabled')), gui.choiceItem('__', _('Disabled'))
|
gui.choiceItem(
|
||||||
] +
|
str(m.vmid), '{}\\{} ({})'.format(m.node, m.name or m.vmid, m.vmid)
|
||||||
[
|
)
|
||||||
gui.choiceItem(group, group) for group in self.parent().listHaGroups()
|
for m in self.parent().listMachines()
|
||||||
|
if m.name and m.name[:3] != 'UDS'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
self.pool.setValues(
|
||||||
|
[gui.choiceItem('', _('None'))]
|
||||||
|
+ [gui.choiceItem(p.poolid, p.poolid) for p in self.parent().listPools()]
|
||||||
|
)
|
||||||
|
self.ha.setValues(
|
||||||
|
[gui.choiceItem('', _('Enabled')), gui.choiceItem('__', _('Disabled'))]
|
||||||
|
+ [gui.choiceItem(group, group) for group in self.parent().listHaGroups()]
|
||||||
|
)
|
||||||
|
|
||||||
def parent(self) -> 'ProxmoxProvider':
|
def parent(self) -> 'ProxmoxProvider':
|
||||||
return typing.cast('ProxmoxProvider', super().parent())
|
return typing.cast('ProxmoxProvider', super().parent())
|
||||||
@ -213,7 +225,9 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
def makeTemplate(self, vmId: int) -> None:
|
def makeTemplate(self, vmId: int) -> None:
|
||||||
self.parent().makeTemplate(vmId)
|
self.parent().makeTemplate(vmId)
|
||||||
|
|
||||||
def cloneMachine(self, name: str, description: str, vmId: int = -1) -> 'client.types.VmCreationResult':
|
def cloneMachine(
|
||||||
|
self, name: str, description: str, vmId: int = -1
|
||||||
|
) -> 'client.types.VmCreationResult':
|
||||||
name = self.sanitizeVmName(name)
|
name = self.sanitizeVmName(name)
|
||||||
pool = self.pool.value or None
|
pool = self.pool.value or None
|
||||||
if vmId == -1: # vmId == -1 if cloning for template
|
if vmId == -1: # vmId == -1 if cloning for template
|
||||||
@ -223,7 +237,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
description,
|
description,
|
||||||
linkedClone=False,
|
linkedClone=False,
|
||||||
toStorage=self.datastore.value,
|
toStorage=self.datastore.value,
|
||||||
toPool=pool
|
toPool=pool,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.parent().cloneMachine(
|
return self.parent().cloneMachine(
|
||||||
@ -232,7 +246,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
description,
|
description,
|
||||||
linkedClone=True,
|
linkedClone=True,
|
||||||
toStorage=self.datastore.value,
|
toStorage=self.datastore.value,
|
||||||
toPool=pool
|
toPool=pool,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getMachineInfo(self, vmId: int) -> 'client.types.VMInfo':
|
def getMachineInfo(self, vmId: int) -> 'client.types.VMInfo':
|
||||||
@ -276,7 +290,9 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
return
|
return
|
||||||
self.parent().disableHA(vmId)
|
self.parent().disableHA(vmId)
|
||||||
|
|
||||||
def setProtection(self, vmId: int, node: typing.Optional[str] = None, protection: bool=False) -> None:
|
def setProtection(
|
||||||
|
self, vmId: int, node: typing.Optional[str] = None, protection: bool = False
|
||||||
|
) -> None:
|
||||||
self.parent().setProtection(vmId, node, protection)
|
self.parent().setProtection(vmId, node, protection)
|
||||||
|
|
||||||
def getBaseName(self) -> str:
|
def getBaseName(self) -> str:
|
||||||
@ -291,5 +307,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
|||||||
def tryGracelyShutdown(self) -> bool:
|
def tryGracelyShutdown(self) -> bool:
|
||||||
return self.guestShutdown.isTrue()
|
return self.guestShutdown.isTrue()
|
||||||
|
|
||||||
def getConsoleConnection(self, machineId: str) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
def getConsoleConnection(
|
||||||
|
self, machineId: str
|
||||||
|
) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
|
||||||
return self.parent().getConsoleConnection(machineId)
|
return self.parent().getConsoleConnection(machineId)
|
||||||
|
Reference in New Issue
Block a user