forked from shaba/openuds
Formating & fixing type checkings
This commit is contained in:
parent
8285e2daad
commit
afcbd058d1
@ -49,6 +49,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
lock = threading.Lock()
|
||||
|
||||
|
||||
class Client:
|
||||
"""
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
cached_api: typing.ClassVar[typing.Optional[ovirt.Connection]] = 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_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
|
||||
_username: str
|
||||
@ -79,7 +83,9 @@ class Client:
|
||||
Returns:
|
||||
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:
|
||||
"""
|
||||
@ -102,7 +108,13 @@ class Client:
|
||||
pass
|
||||
try:
|
||||
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
|
||||
except:
|
||||
@ -111,7 +123,14 @@ class Client:
|
||||
Client.cached_api_key = None
|
||||
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._username = username
|
||||
self._password = password
|
||||
@ -135,7 +154,9 @@ class Client:
|
||||
"""
|
||||
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
|
||||
|
||||
@ -161,7 +182,7 @@ class Client:
|
||||
|
||||
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)
|
||||
|
||||
@ -172,7 +193,14 @@ class Client:
|
||||
pair = [vm.usb.enabled, vm.usb.type.value]
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
@ -181,7 +209,9 @@ class Client:
|
||||
finally:
|
||||
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
|
||||
|
||||
@ -208,7 +238,7 @@ class Client:
|
||||
|
||||
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]] = []
|
||||
|
||||
@ -216,7 +246,11 @@ class Client:
|
||||
for cluster in clusters:
|
||||
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
|
||||
clKey = self.__getKey('o-cluster' + cluster.id)
|
||||
@ -232,7 +266,9 @@ class Client:
|
||||
finally:
|
||||
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
|
||||
|
||||
@ -259,7 +295,7 @@ class Client:
|
||||
|
||||
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
|
||||
|
||||
@ -272,7 +308,9 @@ class Client:
|
||||
finally:
|
||||
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
|
||||
|
||||
@ -308,26 +346,35 @@ class Client:
|
||||
|
||||
api = self.__getApi()
|
||||
|
||||
datacenter_service = api.system_service().data_centers_service().service(datacenterId)
|
||||
d = datacenter_service.get()
|
||||
datacenter_service = (
|
||||
api.system_service().data_centers_service().service(datacenterId)
|
||||
)
|
||||
d: typing.Any = datacenter_service.get() # type: ignore
|
||||
|
||||
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:
|
||||
active = dd.status.value
|
||||
except Exception:
|
||||
active = 'inactive'
|
||||
|
||||
storage.append({'id': dd.id, 'name': dd.name, 'type': dd.type.value,
|
||||
'available': dd.available, 'used': dd.used,
|
||||
'active': active == 'active'})
|
||||
storage.append(
|
||||
{
|
||||
'id': dd.id,
|
||||
'name': dd.name,
|
||||
'type': dd.type.value,
|
||||
'available': dd.available,
|
||||
'used': dd.used,
|
||||
'active': active == 'active',
|
||||
}
|
||||
)
|
||||
|
||||
res = {
|
||||
'name': d.name,
|
||||
'id': d.id,
|
||||
'storage_type': d.local and 'local' or 'shared',
|
||||
'description': d.description,
|
||||
'storage': storage
|
||||
'storage': storage,
|
||||
}
|
||||
|
||||
self._cache.put(dcKey, res, Client.CACHE_TIME_HIGH)
|
||||
@ -335,7 +382,9 @@ class Client:
|
||||
finally:
|
||||
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
|
||||
|
||||
@ -366,14 +415,14 @@ class Client:
|
||||
|
||||
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 = {
|
||||
'id': dd.id,
|
||||
'name': dd.name,
|
||||
'type': dd.type.value,
|
||||
'available': dd.available,
|
||||
'used': dd.used
|
||||
'used': dd.used,
|
||||
}
|
||||
|
||||
self._cache.put(sdKey, res, Client.CACHE_TIME_LOW)
|
||||
@ -388,7 +437,7 @@ class Client:
|
||||
machineId: str,
|
||||
clusterId: str,
|
||||
storageId: str,
|
||||
displayType: str
|
||||
displayType: str,
|
||||
) -> str:
|
||||
"""
|
||||
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
|
||||
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:
|
||||
lock.acquire(True)
|
||||
@ -416,8 +473,8 @@ class Client:
|
||||
|
||||
vms = api.system_service().vms_service().service(machineId)
|
||||
|
||||
cluster = api.system_service().clusters_service().service(clusterId).get()
|
||||
vm = vms.get()
|
||||
cluster: typing.Any = api.system_service().clusters_service().service(clusterId).get() # type: ignore
|
||||
vm: typing.Any = vms.get() # type: ignore
|
||||
|
||||
if vm is None:
|
||||
raise Exception('Machine not found')
|
||||
@ -439,15 +496,12 @@ class Client:
|
||||
tcluster = ovirt.types.Cluster(id=cluster.id)
|
||||
|
||||
template = ovirt.types.Template(
|
||||
name=name,
|
||||
vm=tvm,
|
||||
cluster=tcluster,
|
||||
description=comments
|
||||
name=name, vm=tvm, cluster=tcluster, description=comments
|
||||
)
|
||||
|
||||
# display=display)
|
||||
|
||||
return api.system_service().templates_service().add(template).id
|
||||
return api.system_service().templates_service().add(template).id # type: ignore
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
@ -469,7 +523,9 @@ class Client:
|
||||
api = self.__getApi()
|
||||
|
||||
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:
|
||||
return 'removed'
|
||||
@ -490,7 +546,7 @@ class Client:
|
||||
displayType: str,
|
||||
usbType: str,
|
||||
memoryMB: int,
|
||||
guaranteedMB: int
|
||||
guaranteedMB: int,
|
||||
) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
@ -507,8 +563,16 @@ class Client:
|
||||
Returns:
|
||||
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',
|
||||
name, templateId, clusterId, displayType, usbType, memoryMB, guaranteedMB)
|
||||
logger.debug(
|
||||
'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:
|
||||
lock.acquire(True)
|
||||
|
||||
@ -519,18 +583,28 @@ class Client:
|
||||
cluster = ovirt.types.Cluster(id=clusterId)
|
||||
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)
|
||||
else:
|
||||
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(
|
||||
name=name, cluster=cluster, template=template, description=comments,
|
||||
type=ovirt.types.VmType.DESKTOP, memory=memoryMB * 1024 * 1024, memory_policy=memoryPolicy,
|
||||
usb=usb) # display=display,
|
||||
name=name,
|
||||
cluster=cluster,
|
||||
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:
|
||||
lock.release()
|
||||
@ -546,7 +620,7 @@ class Client:
|
||||
|
||||
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
|
||||
finally:
|
||||
lock.release()
|
||||
@ -573,12 +647,12 @@ class Client:
|
||||
api = self.__getApi()
|
||||
|
||||
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 vm.status.value
|
||||
return vm.status.value # type: ignore
|
||||
except Exception: # machine not found
|
||||
return 'unknown'
|
||||
|
||||
@ -601,7 +675,9 @@ class Client:
|
||||
|
||||
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:
|
||||
raise Exception('Machine not found')
|
||||
@ -625,7 +701,9 @@ class Client:
|
||||
|
||||
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:
|
||||
raise Exception('Machine not found')
|
||||
@ -649,7 +727,9 @@ class Client:
|
||||
|
||||
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:
|
||||
raise Exception('Machine not found')
|
||||
@ -673,7 +753,9 @@ class Client:
|
||||
|
||||
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:
|
||||
raise Exception('Machine not found')
|
||||
@ -692,12 +774,16 @@ class Client:
|
||||
|
||||
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:
|
||||
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
|
||||
nicService = vmService.nics_service().service(nic.id)
|
||||
nicService.update(nic)
|
||||
@ -715,13 +801,15 @@ class Client:
|
||||
|
||||
api = self.__getApi()
|
||||
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)
|
||||
vms.update(vmu)
|
||||
finally:
|
||||
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
|
||||
"""
|
||||
@ -729,7 +817,9 @@ class Client:
|
||||
lock.acquire(True)
|
||||
api = self.__getApi()
|
||||
|
||||
vmService = api.system_service().vms_service().service(machineId)
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
)
|
||||
vm = vmService.get()
|
||||
|
||||
if vm is None:
|
||||
@ -743,8 +833,17 @@ class Client:
|
||||
if display.certificate is not None:
|
||||
cert_subject = display.certificate.subject
|
||||
else:
|
||||
for i in api.system_service().hosts_service().list():
|
||||
for k in api.system_service().hosts_service().service(i.id).nics_service().list():
|
||||
for i in typing.cast(
|
||||
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:
|
||||
cert_subject = i.certificate.subject
|
||||
break
|
||||
@ -759,10 +858,7 @@ class Client:
|
||||
'secure_port': display.secure_port,
|
||||
'monitors': display.monitors,
|
||||
'cert_subject': cert_subject,
|
||||
'ticket': {
|
||||
'value': ticket.value,
|
||||
'expiry': ticket.expiry
|
||||
}
|
||||
'ticket': {'value': ticket.value, 'expiry': ticket.expiry},
|
||||
}
|
||||
|
||||
except Exception:
|
||||
|
@ -11,7 +11,7 @@
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# 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
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
@ -49,7 +49,18 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
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'
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
# : Recheck every six seconds by default (for task methods)
|
||||
suggestedTime = 6
|
||||
|
||||
@ -100,15 +112,17 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
||||
"""
|
||||
Does nothing right here, we will use environment storage in this sample
|
||||
"""
|
||||
return b'\1'.join([
|
||||
return b'\1'.join(
|
||||
[
|
||||
b'v1',
|
||||
self._name.encode('utf8'),
|
||||
self._ip.encode('utf8'),
|
||||
self._mac.encode('utf8'),
|
||||
self._vmid.encode('utf8'),
|
||||
self._reason.encode('utf8'),
|
||||
pickle.dumps(self._queue, protocol=0)
|
||||
])
|
||||
pickle.dumps(self._queue, protocol=0),
|
||||
]
|
||||
)
|
||||
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
@ -126,7 +140,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
||||
def getName(self) -> str:
|
||||
if self._name == '':
|
||||
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:
|
||||
return NO_MORE_NAMES
|
||||
return self._name
|
||||
@ -207,7 +223,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
||||
if 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)
|
||||
|
||||
def desktopLogin(self, username: str, password: str, domain: str = '') -> None:
|
||||
@ -215,7 +233,9 @@ class OVirtLinkedDeployment(services.UserDeployment):
|
||||
if sys.platform == 'win32':
|
||||
from uds import operations
|
||||
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
|
||||
# operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
||||
dbService = self.dbservice()
|
||||
@ -254,8 +274,15 @@ if sys.platform == 'win32':
|
||||
else:
|
||||
self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
|
||||
|
||||
def __checkMachineState(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)
|
||||
def __checkMachineState(
|
||||
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)
|
||||
|
||||
# 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,
|
||||
opWait: self.__wait,
|
||||
opRemove: self.__remove,
|
||||
opChangeMac: self.__changeMac
|
||||
opChangeMac: self.__changeMac,
|
||||
}
|
||||
|
||||
try:
|
||||
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, 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()
|
||||
|
||||
@ -368,9 +397,13 @@ if sys.platform == 'win32':
|
||||
templateId = self.publication().getTemplateId()
|
||||
name = self.getName()
|
||||
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'
|
||||
|
||||
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
|
||||
@ -409,7 +442,9 @@ if sys.platform == 'win32':
|
||||
return State.RUNNING
|
||||
|
||||
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)
|
||||
|
||||
return State.RUNNING
|
||||
@ -427,7 +462,9 @@ if sys.platform == 'win32':
|
||||
return State.RUNNING
|
||||
|
||||
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:
|
||||
self.service().stopMachine(self._vmid)
|
||||
|
||||
@ -446,7 +483,9 @@ if sys.platform == 'win32':
|
||||
return State.RUNNING
|
||||
|
||||
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:
|
||||
self.service().suspendMachine(self._vmid)
|
||||
|
||||
@ -522,14 +561,18 @@ if sys.platform == 'win32':
|
||||
opStop: self.__checkStop,
|
||||
opSuspend: self.__checkSuspend,
|
||||
opRemove: self.__checkRemoved,
|
||||
opChangeMac: self.__checkMac
|
||||
opChangeMac: self.__checkMac,
|
||||
}
|
||||
|
||||
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:
|
||||
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()
|
||||
if state == State.FINISHED:
|
||||
@ -613,8 +656,16 @@ if sys.platform == 'win32':
|
||||
opError: 'error',
|
||||
opFinish: 'finish',
|
||||
opRetry: 'retry',
|
||||
opChangeMac: 'changing mac'
|
||||
opChangeMac: 'changing mac',
|
||||
}.get(op, '????')
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
from .provider import OVirtProvider
|
||||
from uds.core.environment import Environment
|
||||
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider: 'OVirtProvider' = OVirtProvider(env)
|
||||
@ -31,15 +33,23 @@ def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.
|
||||
# Get storages for that datacenter
|
||||
for storage in provider.getDatacenterInfo(ci['datacenter_id'])['storage']:
|
||||
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)')})
|
||||
data = [
|
||||
res.append(
|
||||
{
|
||||
'name': 'datastore',
|
||||
'values': res
|
||||
'id': storage['id'],
|
||||
'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)
|
||||
return data
|
||||
|
@ -58,7 +58,9 @@ class OVirtDeferredRemoval(jobs.Job):
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
try:
|
||||
# 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:
|
||||
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:
|
||||
logger.warning('Exception got queuing for Removal: %s', e)
|
||||
@ -84,7 +90,9 @@ class OVirtDeferredRemoval(jobs.Job):
|
||||
|
||||
provider: Provider
|
||||
# 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)
|
||||
|
||||
storage = provider.getEnvironment().storage
|
||||
|
@ -52,7 +52,9 @@ logger = logging.getLogger(__name__)
|
||||
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
|
||||
|
||||
@ -69,6 +71,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
we MUST register it at package __init__.
|
||||
|
||||
"""
|
||||
|
||||
# : What kind of services we offer, this are classes inherited from Service
|
||||
offers = [OVirtLinkedService]
|
||||
# : 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=[
|
||||
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)
|
||||
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)
|
||||
host = gui.TextField(
|
||||
length=64,
|
||||
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)
|
||||
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)
|
||||
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,
|
||||
)
|
||||
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)
|
||||
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)
|
||||
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,
|
||||
)
|
||||
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
|
||||
_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)
|
||||
"""
|
||||
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
|
||||
|
||||
@ -164,7 +228,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
"""
|
||||
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.
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
@ -200,7 +268,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
|
||||
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
|
||||
|
||||
@ -218,7 +288,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
"""
|
||||
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
|
||||
|
||||
@ -246,7 +318,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
"""
|
||||
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
|
||||
|
||||
@ -275,7 +349,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
machineId: str,
|
||||
clusterId: str,
|
||||
storageId: str,
|
||||
displayType: str
|
||||
displayType: str,
|
||||
) -> str:
|
||||
"""
|
||||
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
|
||||
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:
|
||||
"""
|
||||
@ -341,7 +417,7 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
displayType: str,
|
||||
usbType: str,
|
||||
memoryMB: int,
|
||||
guaranteedMB: int
|
||||
guaranteedMB: int,
|
||||
) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
@ -358,7 +434,16 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
Returns:
|
||||
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:
|
||||
"""
|
||||
@ -418,7 +503,9 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
|
||||
def getMacRange(self) -> str:
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
|
@ -50,7 +50,9 @@ class OVirtPublication(Publication):
|
||||
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
|
||||
_reason: str
|
||||
_destroyAfter: str
|
||||
@ -80,7 +82,16 @@ class OVirtPublication(Publication):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
@ -89,14 +100,24 @@ class OVirtPublication(Publication):
|
||||
logger.debug('Data: %s', data)
|
||||
vals = data.decode('utf8').split('\t')
|
||||
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:
|
||||
"""
|
||||
Realizes the publication of the service
|
||||
"""
|
||||
self._name = self.service().sanitizeVmName('UDSP ' + self.dsName() + "-" + str(self.revision()))
|
||||
comments = _('UDS pub for {0} at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
|
||||
self._name = self.service().sanitizeVmName(
|
||||
'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._destroyAfter = 'f'
|
||||
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
|
||||
"""
|
||||
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using ugettext_noop)
|
||||
@ -113,9 +114,10 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
fills={
|
||||
'callbackName': 'ovFillResourcesFromCluster',
|
||||
'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(
|
||||
@ -123,7 +125,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
rdonly=False,
|
||||
order=101,
|
||||
tooltip=_('Datastore domain where to publish and put incrementals'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
minSpaceGB = gui.NumericField(
|
||||
@ -133,7 +135,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
minValue=0,
|
||||
order=102,
|
||||
tooltip=_('Minimal free space in GB'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
machine = gui.ChoiceField(
|
||||
@ -141,7 +143,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=110,
|
||||
tooltip=_('Service base machine'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
memory = gui.NumericField(
|
||||
@ -153,7 +155,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=111,
|
||||
tooltip=_('Memory assigned to machines'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
memoryGuaranteed = gui.NumericField(
|
||||
@ -165,7 +167,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=112,
|
||||
tooltip=_('Physical memory guaranteed to machines'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
usb = gui.ChoiceField(
|
||||
@ -179,7 +181,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
# gui.choiceItem('legacy', 'legacy (deprecated)'),
|
||||
],
|
||||
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(
|
||||
@ -187,12 +189,9 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
rdonly=False,
|
||||
order=114,
|
||||
tooltip=_('Display type (only for administration purposes)'),
|
||||
values=[
|
||||
gui.choiceItem('spice', 'Spice'),
|
||||
gui.choiceItem('vnc', 'Vnc')
|
||||
],
|
||||
values=[gui.choiceItem('spice', 'Spice'), gui.choiceItem('vnc', 'Vnc')],
|
||||
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(
|
||||
label=_('Machine Names'),
|
||||
@ -200,7 +199,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=115,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
lenName = gui.NumericField(
|
||||
@ -210,11 +209,13 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=116,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
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:
|
||||
"""
|
||||
@ -226,7 +227,9 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
if values:
|
||||
tools.checkValidBasename(self.baseName.value, self.lenName.num())
|
||||
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):
|
||||
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)
|
||||
availableGB = info['available'] / (1024 * 1024 * 1024)
|
||||
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:
|
||||
"""
|
||||
@ -301,7 +308,14 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
# Get storages for that datacenter
|
||||
|
||||
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:
|
||||
"""
|
||||
@ -333,8 +347,16 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
logger.debug('Deploying from template %s machine %s', templateId, name)
|
||||
self.datastoreHasSpace()
|
||||
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
|
||||
self.display.value, self.usb.value, int(self.memory.value), int(self.memoryGuaranteed.value))
|
||||
return self.parent().deployFromTemplate(
|
||||
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:
|
||||
"""
|
||||
@ -440,5 +462,7 @@ class OVirtLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
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)
|
||||
|
@ -32,5 +32,5 @@ from .provider import OGProvider
|
||||
from .jobs import OpenGnsysMaintainer
|
||||
|
||||
# Scheduled task to do clean processes
|
||||
for cls in (OpenGnsysMaintainer, ):
|
||||
for cls in (OpenGnsysMaintainer,):
|
||||
managers.taskManager().registerJob(cls)
|
||||
|
@ -48,6 +48,7 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenGnsysMaintainer(jobs.Job):
|
||||
frecuency = 60 * 60 * 4 # Once every 4 hours
|
||||
friendly_name = 'OpenGnsys cache renewal job'
|
||||
@ -57,18 +58,29 @@ class OpenGnsysMaintainer(jobs.Job):
|
||||
|
||||
# Look for Providers of type VMWareVCServiceProvider
|
||||
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)
|
||||
|
||||
# Locate all services inside the provider
|
||||
service: models.Service
|
||||
for service in provider.services.all():
|
||||
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
|
||||
userService: models.UserService
|
||||
for userService in models.UserService.objects.filter(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)
|
||||
for userService in models.UserService.objects.filter(
|
||||
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()
|
||||
|
||||
logger.debug('OpenGnsys job finished')
|
||||
|
@ -140,7 +140,9 @@ class OGService(Service):
|
||||
label=_('Start if unavailable'),
|
||||
defvalue=gui.TRUE,
|
||||
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)
|
||||
@ -180,7 +182,7 @@ class OGService(Service):
|
||||
machineId,
|
||||
self.getLoginNotifyURL(uuid, token),
|
||||
self.getLogoutNotifyURL(uuid, token),
|
||||
self.getReleaseURL(uuid, token)
|
||||
self.getReleaseURL(uuid, token),
|
||||
)
|
||||
|
||||
def notifyDeadline(
|
||||
@ -191,13 +193,13 @@ class OGService(Service):
|
||||
def powerOn(self, machineId: str) -> typing.Any:
|
||||
return self.parent().powerOn(machineId, self.image.value)
|
||||
|
||||
def _notifyURL(self, uuid: str, token:str, message: str) -> str:
|
||||
def _notifyURL(self, uuid: str, token: str, message: str) -> str:
|
||||
# The URL is "GET messages URL".
|
||||
return '{accessURL}uds/ognotify/{message}/{token}/{uuid}'.format(
|
||||
accessURL=self.parent().getUDSServerAccessUrl(),
|
||||
uuid=uuid,
|
||||
token=token,
|
||||
message=message
|
||||
message=message,
|
||||
)
|
||||
|
||||
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'
|
||||
|
||||
|
||||
class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
# : Recheck every six seconds by default (for task methods)
|
||||
suggestedTime = 6
|
||||
@ -65,7 +66,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
_reason: str = ''
|
||||
_queue: typing.List[int]
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self._name = ''
|
||||
self._ip = ''
|
||||
@ -85,15 +85,17 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self) -> bytes:
|
||||
return b'\1'.join([
|
||||
return b'\1'.join(
|
||||
[
|
||||
b'v1',
|
||||
self._name.encode('utf8'),
|
||||
self._ip.encode('utf8'),
|
||||
self._mac.encode('utf8'),
|
||||
self._vmid.encode('utf8'),
|
||||
self._reason.encode('utf8'),
|
||||
pickle.dumps(self._queue, protocol=0)
|
||||
])
|
||||
pickle.dumps(self._queue, protocol=0),
|
||||
]
|
||||
)
|
||||
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
vals = data.split(b'\1')
|
||||
@ -108,7 +110,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
def getName(self) -> str:
|
||||
if self._name == '':
|
||||
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:
|
||||
return NO_MORE_NAMES
|
||||
return self._name
|
||||
@ -179,11 +183,19 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
self._queue = [opCreate, opStart, opWait, opShutdown, opFinish]
|
||||
|
||||
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)
|
||||
|
||||
# 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')
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
@ -293,9 +307,13 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
templateId = self.publication().getTemplateId()
|
||||
name = self.getName()
|
||||
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)
|
||||
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)
|
||||
|
||||
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()
|
||||
if state == State.FINISHED:
|
||||
@ -476,4 +496,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
}.get(op, '????')
|
||||
|
||||
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__)
|
||||
|
||||
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
|
||||
"""
|
||||
|
@ -45,13 +45,18 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
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():
|
||||
if t.name[:4] != 'UDSP':
|
||||
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
|
||||
the creating machine
|
||||
@ -92,7 +97,11 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
||||
try:
|
||||
imgId = imgs[imgName.strip()]
|
||||
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:
|
||||
fromId = True
|
||||
node = imgIds[0].childNodes[0]
|
||||
@ -105,7 +114,9 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
||||
|
||||
# Now clone the image
|
||||
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
|
||||
if fromId is True:
|
||||
node.data = str(newId)
|
||||
@ -120,7 +131,9 @@ def create(api: 'client.OpenNebulaClient', fromTemplateId: str, name: str, toDat
|
||||
except Exception as e:
|
||||
logger.exception('Creating template on OpenNebula')
|
||||
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:
|
||||
pass
|
||||
raise e
|
||||
@ -165,6 +178,7 @@ def remove(api: 'client.OpenNebulaClient', templateId: str) -> None:
|
||||
except Exception:
|
||||
logger.error('Removing template on OpenNebula')
|
||||
|
||||
|
||||
def deployFrom(api: 'client.OpenNebulaClient', templateId: str, name: str) -> str:
|
||||
"""
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
def checkPublished(api: 'client.OpenNebulaClient', templateId):
|
||||
"""
|
||||
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):
|
||||
return False
|
||||
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
|
||||
api.makePersistentImage(imgId, False)
|
||||
|
@ -32,6 +32,7 @@
|
||||
import enum
|
||||
import typing
|
||||
|
||||
|
||||
class VmState(enum.Enum): # pylint: disable=too-few-public-methods
|
||||
INIT = 0
|
||||
PENDING = 1
|
||||
|
@ -58,7 +58,9 @@ def getMachineState(api: 'client.OpenNebulaClient', machineId: str) -> types.VmS
|
||||
try:
|
||||
return api.getVMState(machineId)
|
||||
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
|
||||
|
||||
@ -70,7 +72,9 @@ def getMachineSubstate(api: 'client.OpenNebulaClient', machineId: str) -> int:
|
||||
try:
|
||||
return api.getVMSubstate(machineId)
|
||||
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
|
||||
|
||||
@ -122,6 +126,7 @@ def suspendMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||
except Exception as e:
|
||||
logger.error('Error suspending %s on OpenNebula: %s', machineId, e)
|
||||
|
||||
|
||||
def shutdownMachine(api: 'client.OpenNebulaClient', machineId: str) -> None:
|
||||
'''
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
Machines starting with UDS are filtered out
|
||||
@ -189,7 +196,11 @@ def enumerateMachines(api: 'client.OpenNebulaClient') -> typing.Iterable[types.V
|
||||
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
|
||||
'''
|
||||
@ -204,7 +215,9 @@ def getNetInfo(api: 'client.OpenNebulaClient', machineId: str, networkId: typing
|
||||
node = nic
|
||||
break
|
||||
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())
|
||||
|
||||
@ -217,10 +230,14 @@ def getNetInfo(api: 'client.OpenNebulaClient', machineId: str, networkId: typing
|
||||
|
||||
return (node.getElementsByTagName('MAC')[0].childNodes[0].data, ip)
|
||||
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
|
||||
SPICE connections should check that 'type' is 'SPICE'
|
||||
@ -236,17 +253,18 @@ def getDisplayConnection(api: 'client.OpenNebulaClient', machineId: str) -> typi
|
||||
except Exception:
|
||||
passwd = ''
|
||||
|
||||
host = md.getElementsByTagName('HISTORY_RECORDS')[0].lastChild.getElementsByTagName('HOSTNAME')[0].childNodes[0].data
|
||||
return {
|
||||
'type': type_,
|
||||
'host': host,
|
||||
'port': int(port),
|
||||
'passwd': passwd
|
||||
}
|
||||
host = (
|
||||
md.getElementsByTagName('HISTORY_RECORDS')[0]
|
||||
.lastChild.getElementsByTagName('HOSTNAME')[0]
|
||||
.childNodes[0]
|
||||
.data
|
||||
)
|
||||
return {'type': type_, 'host': host, 'port': int(port), 'passwd': passwd}
|
||||
|
||||
except Exception:
|
||||
return None # No SPICE connection
|
||||
|
||||
|
||||
# Sample NIC Content (there will be as much as nics)
|
||||
# <NIC>
|
||||
# <BRIDGE><![CDATA[br0]]></BRIDGE>
|
||||
|
@ -74,16 +74,72 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
||||
# but used for sample purposes
|
||||
# If we don't indicate an order, the output order of fields will be
|
||||
# "random"
|
||||
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('OpenNebula Host'), required=True)
|
||||
port = gui.NumericField(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)
|
||||
host = gui.TextField(
|
||||
length=64, label=_('Host'), order=1, tooltip=_('OpenNebula Host'), required=True
|
||||
)
|
||||
port = gui.NumericField(
|
||||
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)
|
||||
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)
|
||||
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,
|
||||
)
|
||||
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
|
||||
_api: typing.Optional[on.client.OpenNebulaClient] = None
|
||||
@ -102,12 +158,16 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
||||
|
||||
@property
|
||||
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
|
||||
def api(self) -> on.client.OpenNebulaClient:
|
||||
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
|
||||
|
||||
@ -128,16 +188,23 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
||||
|
||||
try:
|
||||
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:
|
||||
return [False, '{}'.format(e)]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
'''
|
||||
@ -250,20 +319,17 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
|
||||
'type': display['type'],
|
||||
'address': display['host'],
|
||||
'port': display['port'],
|
||||
'secure_port':-1,
|
||||
'secure_port': -1,
|
||||
'monitors': 1,
|
||||
'cert_subject': '',
|
||||
'ticket': {
|
||||
'value': display['passwd'],
|
||||
'expiry': ''
|
||||
}
|
||||
'ticket': {'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
|
||||
'''
|
||||
return
|
||||
return dict()
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import service
|
||||
from .service import LiveService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -48,21 +48,25 @@ class LivePublication(Publication):
|
||||
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 = ''
|
||||
_reason: str = ''
|
||||
_templateId: str = ''
|
||||
_state: str = 'r'
|
||||
|
||||
def service(self) -> 'service.LiveService':
|
||||
return typing.cast('service.LiveService', super().service())
|
||||
def service(self) -> 'LiveService':
|
||||
return typing.cast('LiveService', super().service())
|
||||
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
@ -77,7 +81,9 @@ class LivePublication(Publication):
|
||||
"""
|
||||
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._state = 'running'
|
||||
|
||||
|
@ -55,6 +55,7 @@ class LiveService(Service):
|
||||
"""
|
||||
Opennebula Live Service
|
||||
"""
|
||||
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using ugettext_noop)
|
||||
@ -109,7 +110,7 @@ class LiveService(Service):
|
||||
label=_("Datastore"),
|
||||
order=100,
|
||||
tooltip=_('Service clones datastore'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
template = gui.ChoiceField(
|
||||
@ -117,7 +118,7 @@ class LiveService(Service):
|
||||
order=110,
|
||||
tooltip=_('Service base template'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
baseName = gui.TextField(
|
||||
@ -126,7 +127,7 @@ class LiveService(Service):
|
||||
order=111,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
lenName = gui.NumericField(
|
||||
@ -136,7 +137,7 @@ class LiveService(Service):
|
||||
order=112,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
@ -149,7 +150,9 @@ class LiveService(Service):
|
||||
if not values:
|
||||
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':
|
||||
return typing.cast('OpenNebulaProvider', super().parent())
|
||||
@ -173,7 +176,9 @@ class LiveService(Service):
|
||||
return self.parent().sanitizeVmName(name)
|
||||
|
||||
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:
|
||||
return self.parent().checkTemplatePublished(templateId)
|
||||
@ -288,7 +293,9 @@ class LiveService(Service):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@ -309,5 +316,7 @@ class LiveService(Service):
|
||||
def getConsoleConnection(self, machineId: str) -> typing.Dict[str, typing.Any]:
|
||||
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)
|
||||
|
@ -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.
|
||||
"""
|
||||
|
||||
_name: str = ''
|
||||
_ip: str = ''
|
||||
_mac: str = ''
|
||||
@ -92,15 +93,17 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self) -> bytes:
|
||||
return b'\1'.join([
|
||||
return b'\1'.join(
|
||||
[
|
||||
b'v1',
|
||||
self._name.encode('utf8'),
|
||||
self._ip.encode('utf8'),
|
||||
self._mac.encode('utf8'),
|
||||
self._vmid.encode('utf8'),
|
||||
self._reason.encode('utf8'),
|
||||
pickle.dumps(self._queue, protocol=0)
|
||||
])
|
||||
pickle.dumps(self._queue, protocol=0),
|
||||
]
|
||||
)
|
||||
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
vals = data.split(b'\1')
|
||||
@ -115,7 +118,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
def getName(self) -> str:
|
||||
if self._name == '':
|
||||
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:
|
||||
return NO_MORE_NAMES
|
||||
return self._name
|
||||
@ -188,7 +193,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
self._queue = [opCreate, opWait, opSuspend, opFinish]
|
||||
|
||||
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)
|
||||
|
||||
# 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:
|
||||
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]()
|
||||
|
||||
@ -295,9 +307,13 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
templateId = self.publication().getTemplateId()
|
||||
name = self.getName()
|
||||
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)
|
||||
if self._vmid is None:
|
||||
@ -388,7 +404,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
try:
|
||||
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]()
|
||||
if state == State.FINISHED:
|
||||
@ -462,4 +480,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
}.get(op, '????')
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
def getApi(parameters: typing.Dict[str, str]) -> typing.Tuple[openstack.Client, bool]:
|
||||
from .provider_legacy import ProviderLegacy
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
'''
|
||||
api, nameFromSubnets = getApi(parameters)
|
||||
|
||||
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()]
|
||||
securityGroups = [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()]
|
||||
securityGroups = [
|
||||
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 = [
|
||||
{'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)
|
||||
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
|
||||
'''
|
||||
api, _ = getApi(parameters)
|
||||
# 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'] != '']
|
||||
volumes = [
|
||||
gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != ''
|
||||
]
|
||||
|
||||
data = [
|
||||
{'name': 'volume', 'values': volumes},
|
||||
|
@ -38,17 +38,45 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
(
|
||||
ACTIVE, BUILDING, DELETED, ERROR,
|
||||
HARD_REBOOT, MIGRATING, PASSWORD,
|
||||
PAUSED, REBOOT, REBUILD, RESCUED,
|
||||
RESIZED, REVERT_RESIZE, SOFT_DELETED,
|
||||
STOPPED, SUSPENDED, UNKNOWN, VERIFY_RESIZE, SHUTOFF
|
||||
ACTIVE,
|
||||
BUILDING,
|
||||
DELETED,
|
||||
ERROR,
|
||||
HARD_REBOOT,
|
||||
MIGRATING,
|
||||
PASSWORD,
|
||||
PAUSED,
|
||||
REBOOT,
|
||||
REBUILD,
|
||||
RESCUED,
|
||||
RESIZED,
|
||||
REVERT_RESIZE,
|
||||
SOFT_DELETED,
|
||||
STOPPED,
|
||||
SUSPENDED,
|
||||
UNKNOWN,
|
||||
VERIFY_RESIZE,
|
||||
SHUTOFF,
|
||||
) = (
|
||||
'ACTIVE', 'BUILDING', 'DELETED', 'ERROR',
|
||||
'HARD_REBOOT', 'MIGRATING', 'PASSWORD',
|
||||
'PAUSED', 'REBOOT', 'REBUILD', 'RESCUED',
|
||||
'RESIZED', 'REVERT_RESIZE', 'SOFT_DELETED',
|
||||
'STOPPED', 'SUSPENDED', 'UNKNOWN', 'VERIFY_RESIZE', 'SHUTOFF'
|
||||
'ACTIVE',
|
||||
'BUILDING',
|
||||
'DELETED',
|
||||
'ERROR',
|
||||
'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:
|
||||
self._volume = 'volumev2'
|
||||
|
||||
|
||||
def ensureAuthenticated(self) -> None:
|
||||
if (
|
||||
self._authenticated is False
|
||||
|
@ -208,7 +208,9 @@ class OpenStackProvider(ServiceProvider):
|
||||
length=96,
|
||||
label=_('Proxy'),
|
||||
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,
|
||||
tab=gui.ADVANCED_TAB,
|
||||
)
|
||||
@ -233,9 +235,7 @@ class OpenStackProvider(ServiceProvider):
|
||||
if self._api is None:
|
||||
proxies = None
|
||||
if self.httpsProxy.value.strip():
|
||||
proxies = {
|
||||
'https': self.httpsProxy.value
|
||||
}
|
||||
proxies = {'https': self.httpsProxy.value}
|
||||
self._api = openstack.Client(
|
||||
self.endpoint.value,
|
||||
-1,
|
||||
@ -247,7 +247,7 @@ class OpenStackProvider(ServiceProvider):
|
||||
projectId=projectId,
|
||||
region=region,
|
||||
access=self.access.value,
|
||||
proxies=proxies
|
||||
proxies=proxies,
|
||||
)
|
||||
return self._api
|
||||
|
||||
|
@ -194,7 +194,9 @@ class ProviderLegacy(ServiceProvider):
|
||||
length=96,
|
||||
label=_('Proxy'),
|
||||
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,
|
||||
tab=gui.ADVANCED_TAB,
|
||||
)
|
||||
|
@ -47,13 +47,16 @@ class LivePublication(Publication):
|
||||
"""
|
||||
This class provides the publication of a oVirtLinkedService
|
||||
"""
|
||||
|
||||
_name: str = ''
|
||||
_reason: str = ''
|
||||
_templateId: str = ''
|
||||
_state: str = 'r'
|
||||
_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):
|
||||
"""
|
||||
@ -78,7 +81,16 @@ class LivePublication(Publication):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
@ -86,13 +98,21 @@ class LivePublication(Publication):
|
||||
"""
|
||||
vals = data.decode('utf8').split('\t')
|
||||
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:
|
||||
"""
|
||||
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._destroyAfter = 'n'
|
||||
|
||||
@ -118,7 +138,9 @@ class LivePublication(Publication):
|
||||
if self._state == 'available':
|
||||
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':
|
||||
return self.destroy()
|
||||
|
@ -51,6 +51,7 @@ if typing.TYPE_CHECKING:
|
||||
from . import openstack
|
||||
from .provider import OpenStackProvider
|
||||
from .provider_legacy import ProviderLegacy
|
||||
|
||||
Provider = typing.Union[OpenStackProvider, ProviderLegacy]
|
||||
|
||||
|
||||
@ -58,6 +59,7 @@ class LiveService(Service):
|
||||
"""
|
||||
OpenStack Live Service
|
||||
"""
|
||||
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using ugettext_noop)
|
||||
@ -104,37 +106,74 @@ class LiveService(Service):
|
||||
servicesTypeProvided = (serviceTypes.VDI,)
|
||||
|
||||
# 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(
|
||||
label=_('Project'),
|
||||
order=2,
|
||||
fills={
|
||||
'callbackName' : 'osFillResources',
|
||||
'function' : helpers.getResources,
|
||||
'parameters' : ['ov', 'ev', 'project', 'region', 'legacy']
|
||||
'callbackName': 'osFillResources',
|
||||
'function': helpers.getResources,
|
||||
'parameters': ['ov', 'ev', 'project', 'region', 'legacy'],
|
||||
},
|
||||
tooltip=_('Project for this service'),
|
||||
required=True,
|
||||
rdonly=True
|
||||
rdonly=True,
|
||||
)
|
||||
availabilityZone = gui.ChoiceField(
|
||||
label=_('Availability Zones'),
|
||||
order=3,
|
||||
fills={
|
||||
'callbackName' : 'osFillVolumees',
|
||||
'function' : helpers.getVolumes,
|
||||
'parameters' : ['ov', 'ev', 'project', 'region', 'availabilityZone', 'legacy']
|
||||
'callbackName': 'osFillVolumees',
|
||||
'function': helpers.getVolumes,
|
||||
'parameters': [
|
||||
'ov',
|
||||
'ev',
|
||||
'project',
|
||||
'region',
|
||||
'availabilityZone',
|
||||
'legacy',
|
||||
],
|
||||
},
|
||||
tooltip=_('Service availability zones'),
|
||||
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)
|
||||
network = gui.ChoiceField(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'))
|
||||
network = gui.ChoiceField(
|
||||
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(
|
||||
label=_('Machine Names'),
|
||||
@ -142,7 +181,7 @@ class LiveService(Service):
|
||||
order=9,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
required=True,
|
||||
tab=_('Machine')
|
||||
tab=_('Machine'),
|
||||
)
|
||||
|
||||
lenName = gui.NumericField(
|
||||
@ -152,12 +191,14 @@ class LiveService(Service):
|
||||
order=10,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
required=True,
|
||||
tab=_('Machine')
|
||||
tab=_('Machine'),
|
||||
)
|
||||
|
||||
ov = 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
|
||||
|
||||
@ -183,15 +224,26 @@ class LiveService(Service):
|
||||
"""
|
||||
api = self.parent().api()
|
||||
|
||||
if not self.parent().legacy and self.parent().region.value:
|
||||
regions = [gui.choiceItem(self.parent().region.value, self.parent().region.value)]
|
||||
# Checks if legacy or current openstack provider
|
||||
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:
|
||||
regions = [gui.choiceItem(r['id'], r['id']) for r in api.listRegions()]
|
||||
|
||||
self.region.setValues(regions)
|
||||
|
||||
if not self.parent().legacy and self.parent().tenant.value:
|
||||
tenants = [gui.choiceItem(self.parent().tenant.value, self.parent().tenant.value)]
|
||||
if parentCurrent and parentCurrent.tenant.value:
|
||||
tenants = [
|
||||
gui.choiceItem(parentCurrent.tenant.value, parentCurrent.tenant.value)
|
||||
]
|
||||
else:
|
||||
tenants = [gui.choiceItem(t['id'], t['name']) for t in api.listProjects()]
|
||||
self.project.setValues(tenants)
|
||||
@ -206,7 +258,9 @@ class LiveService(Service):
|
||||
@property
|
||||
def api(self) -> 'openstack.Client':
|
||||
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
|
||||
|
||||
@ -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')
|
||||
|
||||
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):
|
||||
"""
|
||||
@ -247,7 +303,7 @@ class LiveService(Service):
|
||||
availabilityZone=self.availabilityZone.value,
|
||||
flavorId=self.flavor.value,
|
||||
networkId=self.network.value,
|
||||
securityGroupsIdsList=self.securityGroups.value
|
||||
securityGroupsIdsList=self.securityGroups.value,
|
||||
)['id']
|
||||
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
@ -286,7 +342,12 @@ class LiveService(Service):
|
||||
"""
|
||||
server = self.api.getServer(machineId)
|
||||
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']
|
||||
|
||||
def startMachine(self, machineId: str) -> None:
|
||||
@ -362,7 +423,9 @@ class LiveService(Service):
|
||||
Gets the mac address of first nic of the machine
|
||||
"""
|
||||
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]
|
||||
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)
|
||||
ip = res[0].address
|
||||
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
|
||||
|
||||
|
@ -148,11 +148,7 @@ class PhysicalMachinesProvider(services.ServiceProvider):
|
||||
config.read_string(self.config.value)
|
||||
for key in config['wol']:
|
||||
if net.ipInNetwork(ip, key):
|
||||
return (
|
||||
config['wol'][key]
|
||||
.replace('{MAC}', mac)
|
||||
.replace('{IP}', ip)
|
||||
)
|
||||
return config['wol'][key].replace('{MAC}', mac).replace('{IP}', ip)
|
||||
|
||||
except Exception as 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
|
||||
# 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
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# 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:
|
||||
if mac:
|
||||
wolurl = (
|
||||
self.parent()
|
||||
.wolURL(ip, mac)
|
||||
)
|
||||
wolurl = self.parent().wolURL(ip, mac)
|
||||
if wolurl:
|
||||
logger.info('Launching WOL: %s', wolurl)
|
||||
try:
|
||||
|
@ -51,7 +51,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class IPSingleMachineService(IPServiceBase):
|
||||
# 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
|
||||
typeName = _('Static Single IP')
|
||||
@ -60,7 +66,9 @@ class IPSingleMachineService(IPServiceBase):
|
||||
iconFile = 'machine.png'
|
||||
|
||||
# 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_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)
|
||||
@ -75,7 +83,9 @@ class IPSingleMachineService(IPServiceBase):
|
||||
return
|
||||
|
||||
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]:
|
||||
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)),
|
||||
}
|
||||
|
||||
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):
|
||||
@ -28,6 +38,7 @@ class Cluster(typing.NamedTuple):
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Cluster':
|
||||
return convertFromDict(Cluster, dictionary)
|
||||
|
||||
|
||||
class Node(typing.NamedTuple):
|
||||
name: str
|
||||
online: bool
|
||||
@ -41,6 +52,7 @@ class Node(typing.NamedTuple):
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'Node':
|
||||
return convertFromDict(Node, dictionary)
|
||||
|
||||
|
||||
class NodeStats(typing.NamedTuple):
|
||||
name: str
|
||||
status: str
|
||||
@ -61,7 +73,20 @@ class NodeStats(typing.NamedTuple):
|
||||
|
||||
@staticmethod
|
||||
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):
|
||||
cluster: typing.Optional[Cluster]
|
||||
@ -80,6 +105,7 @@ class ClusterStatus(typing.NamedTuple):
|
||||
|
||||
return ClusterStatus(cluster=cluster, nodes=nodes)
|
||||
|
||||
|
||||
class UPID(typing.NamedTuple):
|
||||
node: str
|
||||
pid: int
|
||||
@ -92,7 +118,7 @@ class UPID(typing.NamedTuple):
|
||||
|
||||
@staticmethod
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'UPID':
|
||||
upid=dictionary['data']
|
||||
upid = dictionary['data']
|
||||
d = upid.split(':')
|
||||
return UPID(
|
||||
node=d[1],
|
||||
@ -102,9 +128,10 @@ class UPID(typing.NamedTuple):
|
||||
type=d[5],
|
||||
vmid=int(d[6]),
|
||||
user=d[7],
|
||||
upid=upid
|
||||
upid=upid,
|
||||
)
|
||||
|
||||
|
||||
class TaskStatus(typing.NamedTuple):
|
||||
node: str
|
||||
pid: int
|
||||
@ -133,6 +160,7 @@ class TaskStatus(typing.NamedTuple):
|
||||
def isErrored(self) -> bool:
|
||||
return self.isFinished() and not self.isCompleted()
|
||||
|
||||
|
||||
class NetworkConfiguration(typing.NamedTuple):
|
||||
type: str
|
||||
mac: str
|
||||
@ -154,7 +182,9 @@ class VMInfo(typing.NamedTuple):
|
||||
template: bool
|
||||
|
||||
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]
|
||||
maxdisk: typing.Optional[int]
|
||||
mem: typing.Optional[int]
|
||||
@ -173,6 +203,7 @@ class VMInfo(typing.NamedTuple):
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'VMInfo':
|
||||
return convertFromDict(VMInfo, dictionary)
|
||||
|
||||
|
||||
class VMConfiguration(typing.NamedTuple):
|
||||
name: str
|
||||
vga: str
|
||||
@ -185,7 +216,9 @@ class VMConfiguration(typing.NamedTuple):
|
||||
template: bool
|
||||
|
||||
@staticmethod
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'VMConfiguration':
|
||||
def fromDict(
|
||||
dictionary: typing.MutableMapping[str, typing.Any]
|
||||
) -> 'VMConfiguration':
|
||||
nets: typing.List[NetworkConfiguration] = []
|
||||
for k in dictionary.keys():
|
||||
if k[:3] == 'net':
|
||||
@ -194,11 +227,13 @@ class VMConfiguration(typing.NamedTuple):
|
||||
dictionary['networks'] = nets
|
||||
return convertFromDict(VMConfiguration, dictionary)
|
||||
|
||||
|
||||
class VmCreationResult(typing.NamedTuple):
|
||||
node: str
|
||||
vmid: int
|
||||
upid: UPID
|
||||
|
||||
|
||||
class StorageInfo(typing.NamedTuple):
|
||||
node: str
|
||||
storage: str
|
||||
@ -212,11 +247,11 @@ class StorageInfo(typing.NamedTuple):
|
||||
total: int
|
||||
used_fraction: float
|
||||
|
||||
|
||||
@staticmethod
|
||||
def fromDict(dictionary: typing.MutableMapping[str, typing.Any]) -> 'StorageInfo':
|
||||
return convertFromDict(StorageInfo, dictionary)
|
||||
|
||||
|
||||
class PoolInfo(typing.NamedTuple):
|
||||
poolid: str
|
||||
comments: str
|
||||
|
@ -35,10 +35,12 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
from .provider import ProxmoxProvider
|
||||
|
||||
from uds.core.environment import Environment
|
||||
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider: ProxmoxProvider = ProxmoxProvider(env)
|
||||
@ -54,19 +56,27 @@ def getStorage(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.An
|
||||
|
||||
res = []
|
||||
# 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'):
|
||||
continue
|
||||
space, free = storage.avail / 1024 / 1024 / 1024, (storage.avail - storage.used) / 1024 / 1024 / 1024
|
||||
extra = _(' shared') if storage.shared else _(' (bound to {})').format(vmInfo.node)
|
||||
res.append({'id': storage.storage, 'text': "%s (%4.2f GB/%4.2f GB)%s" % (storage.storage, space, free, extra)})
|
||||
|
||||
data = [
|
||||
space, free = (
|
||||
storage.avail / 1024 / 1024 / 1024,
|
||||
(storage.avail - storage.used) / 1024 / 1024 / 1024,
|
||||
)
|
||||
extra = (
|
||||
_(' shared') if storage.shared else _(' (bound to {})').format(vmInfo.node)
|
||||
)
|
||||
res.append(
|
||||
{
|
||||
'name': 'datastore',
|
||||
'values': res
|
||||
'id': storage.storage,
|
||||
'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)
|
||||
return data
|
||||
|
@ -52,6 +52,7 @@ logger = logging.getLogger(__name__)
|
||||
CACHE_TIME_FOR_SERVER = 1800
|
||||
MAX_VM_ID = 999999999
|
||||
|
||||
|
||||
class ProxmoxProvider(
|
||||
services.ServiceProvider
|
||||
): # pylint: disable=too-many-public-methods
|
||||
@ -223,7 +224,14 @@ class ProxmoxProvider(
|
||||
toPool: typing.Optional[str] = None,
|
||||
) -> client.types.VmCreationResult:
|
||||
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:
|
||||
|
@ -45,6 +45,7 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProxmoxPublication(services.Publication):
|
||||
suggestedTime = 20
|
||||
|
||||
@ -74,7 +75,18 @@ class ProxmoxPublication(services.Publication):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
@ -83,7 +95,15 @@ class ProxmoxPublication(services.Publication):
|
||||
logger.debug('Data: %s', data)
|
||||
vals = data.decode('utf8').split('\t')
|
||||
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:
|
||||
"""
|
||||
@ -91,8 +111,17 @@ class ProxmoxPublication(services.Publication):
|
||||
"""
|
||||
try:
|
||||
# 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())
|
||||
comments = _('UDS Publication for {0} created at {1}').format(self.dsName(), str(datetime.now()).split('.')[0])
|
||||
self._name = (
|
||||
'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)
|
||||
self._vm = str(task.vmid)
|
||||
self._task = ','.join((task.upid.node, task.upid.upid))
|
||||
@ -105,7 +134,9 @@ class ProxmoxPublication(services.Publication):
|
||||
self._reason = str(e)
|
||||
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:
|
||||
return self._state
|
||||
node, upid = self._task.split(',')
|
||||
@ -129,7 +160,9 @@ class ProxmoxPublication(services.Publication):
|
||||
if self._operation == 'p': # not Destroying
|
||||
# Disable Protection (removal)
|
||||
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
|
||||
self.service().enableHA(int(self._vm))
|
||||
time.sleep(0.5)
|
||||
@ -147,7 +180,9 @@ class ProxmoxPublication(services.Publication):
|
||||
self._destroyAfter = ''
|
||||
|
||||
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'
|
||||
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
|
||||
"""
|
||||
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : 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'),
|
||||
# tab=_('Machine'),
|
||||
# required=True,
|
||||
defvalue=''
|
||||
defvalue='',
|
||||
)
|
||||
|
||||
ha = gui.ChoiceField(
|
||||
label=_('HA'),
|
||||
order=2,
|
||||
tooltip=_('Select if HA is enabled and HA group for machines of this service'),
|
||||
rdonly=True
|
||||
rdonly=True,
|
||||
)
|
||||
|
||||
guestShutdown = gui.CheckBoxField(
|
||||
@ -137,11 +138,11 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
fills={
|
||||
'callbackName': 'pmFillResourcesFromMachine',
|
||||
'function': helpers.getStorage,
|
||||
'parameters': ['machine', 'ov', 'ev']
|
||||
'parameters': ['machine', 'ov', 'ev'],
|
||||
},
|
||||
tooltip=_('Service base machine'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
datastore = gui.ChoiceField(
|
||||
@ -150,7 +151,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=111,
|
||||
tooltip=_('Storage for publications & machines.'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
baseName = gui.TextField(
|
||||
@ -159,7 +160,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=115,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
lenName = gui.NumericField(
|
||||
@ -169,15 +170,19 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
order=116,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
tab=_('Machine'),
|
||||
required=True
|
||||
required=True,
|
||||
)
|
||||
|
||||
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:
|
||||
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:
|
||||
# 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
|
||||
# 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.pool.setValues([gui.choiceItem('', _('None'))] + [gui.choiceItem(p.poolid, p.poolid) for p in self.parent().listPools()])
|
||||
self.ha.setValues(
|
||||
self.machine.setValues(
|
||||
[
|
||||
gui.choiceItem('', _('Enabled')), gui.choiceItem('__', _('Disabled'))
|
||||
] +
|
||||
[
|
||||
gui.choiceItem(group, group) for group in self.parent().listHaGroups()
|
||||
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.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':
|
||||
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:
|
||||
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)
|
||||
pool = self.pool.value or None
|
||||
if vmId == -1: # vmId == -1 if cloning for template
|
||||
@ -223,7 +237,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
description,
|
||||
linkedClone=False,
|
||||
toStorage=self.datastore.value,
|
||||
toPool=pool
|
||||
toPool=pool,
|
||||
)
|
||||
|
||||
return self.parent().cloneMachine(
|
||||
@ -232,7 +246,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
description,
|
||||
linkedClone=True,
|
||||
toStorage=self.datastore.value,
|
||||
toPool=pool
|
||||
toPool=pool,
|
||||
)
|
||||
|
||||
def getMachineInfo(self, vmId: int) -> 'client.types.VMInfo':
|
||||
@ -245,7 +259,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
def getTaskInfo(self, node: str, upid: str) -> 'client.types.TaskStatus':
|
||||
return self.parent().getTaskInfo(node, upid)
|
||||
|
||||
def startMachine(self,vmId: int) -> 'client.types.UPID':
|
||||
def startMachine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().startMachine(vmId)
|
||||
|
||||
def stopMachine(self, vmId: int) -> 'client.types.UPID':
|
||||
@ -276,7 +290,9 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
return
|
||||
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)
|
||||
|
||||
def getBaseName(self) -> str:
|
||||
@ -291,5 +307,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
|
||||
def tryGracelyShutdown(self) -> bool:
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user