Formating & fixing type checkings

This commit is contained in:
Adolfo Gómez García 2021-08-14 15:47:21 +02:00
parent 8285e2daad
commit afcbd058d1
36 changed files with 1136 additions and 409 deletions

View File

@ -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)
@ -382,14 +431,14 @@ class Client:
lock.release()
def makeTemplate(
self,
name: str,
comments: str,
machineId: str,
clusterId: str,
storageId: str,
displayType: str
) -> str:
self,
name: str,
comments: str,
machineId: str,
clusterId: str,
storageId: str,
displayType: str,
) -> str:
"""
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
the creating machine
@ -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')
@ -432,22 +489,19 @@ class Client:
# dsks = []
# for dsk in vms.disk_attachments_service().list():
# dsks = None
# dsks.append(params.Disk(id=dsk.get_id(), storage_domains=sd, alias=dsk.get_alias()))
# dsks.append(dsk)
# dsks.append(params.Disk(id=dsk.get_id(), storage_domains=sd, alias=dsk.get_alias()))
# dsks.append(dsk)
tvm = ovirt.types.Vm(id=vm.id)
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'
@ -482,16 +538,16 @@ class Client:
lock.release()
def deployFromTemplate(
self,
name: str,
comments: str,
templateId: str,
clusterId: str,
displayType: str,
usbType: str,
memoryMB: int,
guaranteedMB: int
) -> str:
self,
name: str,
comments: str,
templateId: str,
clusterId: str,
displayType: str,
usbType: str,
memoryMB: 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:

View File

@ -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([
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)
])
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),
]
)
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],
)

View File

@ -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 = [
{
'name': 'datastore',
'values': res
}
]
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 = [{'name': 'datastore', 'values': res}]
logger.debug('return data: %s', data)
return data

View File

@ -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

View File

@ -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
@ -269,14 +343,14 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
return self.__getApi().getStorageInfo(storageId, force)
def makeTemplate(
self,
name: str,
comments: str,
machineId: str,
clusterId: str,
storageId: str,
displayType: str
) -> str:
self,
name: str,
comments: str,
machineId: str,
clusterId: str,
storageId: str,
displayType: str,
) -> str:
"""
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
the creating machine
@ -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:
"""
@ -333,16 +409,16 @@ class OVirtProvider(services.ServiceProvider): # pylint: disable=too-many-publi
return self.__getApi().removeTemplate(templateId)
def deployFromTemplate(
self,
name: str,
comments: str,
templateId: str,
clusterId: str,
displayType: str,
usbType: str,
memoryMB: int,
guaranteedMB: int
) -> str:
self,
name: str,
comments: str,
templateId: str,
clusterId: str,
displayType: str,
usbType: str,
memoryMB: 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

View File

@ -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'
@ -123,7 +144,7 @@ class OVirtPublication(Publication):
try:
self._state = self.service().getTemplateState(self._templateId)
if self._state == 'removed':
raise Exception('Template has been removed!')
raise Exception('Template has been removed!')
except Exception as e:
self._state = 'error'
self._reason = str(e)

View File

@ -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)

View File

@ -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)

View File

@ -48,8 +48,9 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class OpenGnsysMaintainer(jobs.Job):
frecuency = 60 * 60 * 4 # Once every 4 hours
frecuency = 60 * 60 * 4 # Once every 4 hours
friendly_name = 'OpenGnsys cache renewal job'
def run(self) -> None:
@ -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')

View File

@ -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:

View File

@ -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([
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)
])
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),
]
)
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],
)

View File

@ -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
"""

View File

@ -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)

View File

@ -32,6 +32,7 @@
import enum
import typing
class VmState(enum.Enum): # pylint: disable=too-few-public-methods
INIT = 0
PENDING = 1
@ -81,7 +82,7 @@ class StorageType(typing.NamedTuple):
id: str
name: str
total: int # In Megabytes
free: int # In Megabytes
free: int # In Megabytes
xml: typing.Optional[str]
@ -95,7 +96,7 @@ class TemplateType(typing.NamedTuple):
class ImageType(typing.NamedTuple):
id: str
name: str
size: int # In Megabytes
size: int # In Megabytes
persistent: bool
running_vms: int
state: ImageState

View File

@ -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>

View File

@ -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]:

View File

@ -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'

View File

@ -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)

View File

@ -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([
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)
])
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),
]
)
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],
)

View File

@ -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},

View File

@ -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',
)

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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()
@ -131,7 +153,7 @@ class LivePublication(Publication):
def destroy(self) -> str:
# We do not do anything else to destroy this instance of publication
if self._state == 'error':
return State.ERROR # Nothing to cancel
return State.ERROR # Nothing to cancel
if self._state == 'creating':
self._destroyAfter = 'y'

View File

@ -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,8 +423,10 @@ 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 = six.next(six.itervalues(net))[0]
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']
def getBaseName(self) -> str:

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -260,7 +260,7 @@ class ProxmoxClient:
def isVMIdAvailable(self, vmId: int) -> bool:
try:
self._get(f'cluster/nextid?vmid={vmId}')
except Exception: # Not available
except Exception: # Not available
return False
return True

View File

@ -2,7 +2,7 @@ import datetime
import re
import typing
networkRe = re.compile(r'([a-zA-Z0-9]+)=([^,]+)') # May have vla id at end
networkRe = re.compile(r'([a-zA-Z0-9]+)=([^,]+)') # May have vla id at end
# Conversor from dictionary to NamedTuple
conversors: typing.MutableMapping[typing.Type, typing.Callable] = {
@ -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

View File

@ -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)})
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 = [
{
'name': 'datastore',
'values': res
}
]
data = [{'name': 'datastore', 'values': res}]
logger.debug('return data: %s', data)
return data

View File

@ -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:

View File

@ -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(',')
@ -122,14 +153,16 @@ class ProxmoxPublication(services.Publication):
if task.isErrored():
self._reason = task.exitstatus
self._state = State.ERROR
else: # Finished
else: # Finished
if self._destroyAfter:
return self.destroy()
self._state = State.FINISHED
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)
@ -139,7 +172,7 @@ class ProxmoxPublication(services.Publication):
# This seems to cause problems on Proxmox
# makeTemplate --> setProtection (that calls "config"). Seems that the HD dissapears...
# Seems a concurrency problem?
return self._state
def finish(self) -> None:
@ -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
@ -166,4 +201,4 @@ class ProxmoxPublication(services.Publication):
return self._reason
def machine(self) -> int:
return int(self._vm)
return int(self._vm)

View File

@ -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)
@ -103,7 +104,7 @@ class ProxmoxLinkedService(Service): # pylint: disable=too-many-public-methods
# : Types of deploys (services in cache and/or assigned to users)
deployedType = ProxmoxDeployment
allowedProtocols = protocols.GENERIC # + (protocols.SPICE,)
allowedProtocols = protocols.GENERIC # + (protocols.SPICE,)
servicesTypeProvided = (serviceTypes.VDI,)
pool = gui.ChoiceField(
@ -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)