mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
Adding fixed pool of machines to ProxMox
This commit is contained in:
parent
06f3487d2c
commit
8ec94e0cdf
@ -137,7 +137,7 @@ class Providers(ModelHandler):
|
||||
try:
|
||||
perm = permissions.effective_permissions(self._user, s)
|
||||
if perm >= uds.core.types.permissions.PermissionType.READ:
|
||||
yield DetailServices.serviceToDict(s, perm, True)
|
||||
yield DetailServices.service_to_dict(s, perm, True)
|
||||
except Exception:
|
||||
logger.exception('Passed service cause type is unknown')
|
||||
|
||||
@ -149,7 +149,7 @@ class Providers(ModelHandler):
|
||||
service = Service.objects.get(uuid=self._args[1])
|
||||
self.ensure_has_access(service.provider, uds.core.types.permissions.PermissionType.READ)
|
||||
perm = self.get_permissions(service.provider)
|
||||
return DetailServices.serviceToDict(service, perm, True)
|
||||
return DetailServices.service_to_dict(service, perm, True)
|
||||
except Exception:
|
||||
# logger.exception('Exception')
|
||||
return {}
|
||||
|
@ -63,10 +63,10 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
Detail handler for Services, whose parent is a Provider
|
||||
"""
|
||||
|
||||
custom_methods = ['servicesPools']
|
||||
custom_methods = ['servicepools']
|
||||
|
||||
@staticmethod
|
||||
def serviceInfo(item: models.Service) -> dict[str, typing.Any]:
|
||||
def service_info(item: models.Service) -> dict[str, typing.Any]:
|
||||
info = item.get_type()
|
||||
|
||||
return {
|
||||
@ -86,7 +86,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def serviceToDict(
|
||||
def service_to_dict(
|
||||
item: models.Service, perm: int, full: bool = False
|
||||
) -> dict[str, typing.Any]:
|
||||
"""
|
||||
@ -113,7 +113,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'permission': perm,
|
||||
}
|
||||
if full:
|
||||
retVal['info'] = Services.serviceInfo(item)
|
||||
retVal['info'] = Services.service_info(item)
|
||||
|
||||
return retVal
|
||||
|
||||
@ -123,9 +123,9 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
perm = permissions.effective_permissions(self._user, parent)
|
||||
try:
|
||||
if item is None:
|
||||
return [Services.serviceToDict(k, perm) for k in parent.services.all()]
|
||||
return [Services.service_to_dict(k, perm) for k in parent.services.all()]
|
||||
k = parent.services.get(uuid=process_uuid(item))
|
||||
val = Services.serviceToDict(k, perm, full=True)
|
||||
val = Services.service_to_dict(k, perm, full=True)
|
||||
return self.fill_instance_fields(k, val)
|
||||
except Exception as e:
|
||||
logger.error('Error getting services for %s: %s', parent, e)
|
||||
@ -334,7 +334,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
except Exception:
|
||||
raise self.invalid_item_response() from None
|
||||
|
||||
def servicesPools(self, parent: 'Model', item: str) -> typing.Any:
|
||||
def servicepools(self, parent: 'Model', item: str) -> typing.Any:
|
||||
parent = ensure.is_instance(parent, models.Provider)
|
||||
service = parent.services.get(uuid=process_uuid(item))
|
||||
logger.debug('Got parameters for servicepools: %s, %s', parent, item)
|
||||
|
@ -253,7 +253,7 @@ class ServicesPools(ModelHandler):
|
||||
val['tags'] = [tag.tag for tag in item.tags.all()]
|
||||
val['restrained'] = restrained
|
||||
val['permission'] = permissions.effective_permissions(self._user, item)
|
||||
val['info'] = Services.serviceInfo(item.service) # type: ignore
|
||||
val['info'] = Services.service_info(item.service) # type: ignore
|
||||
val['pool_group_id'] = poolGroupId
|
||||
val['pool_group_name'] = poolGroupName
|
||||
val['pool_group_thumb'] = poolGroupThumb
|
||||
|
@ -110,7 +110,7 @@ class FieldPatternType(enum.StrEnum):
|
||||
|
||||
|
||||
class CallbackResultItem(typing.TypedDict):
|
||||
# data = [{'name': 'datastore', 'choices': res}]
|
||||
# {'name': 'datastore', 'choices': res}
|
||||
name: str
|
||||
choices: typing.List['ChoiceItem']
|
||||
|
||||
|
@ -131,24 +131,24 @@ def get_server_group_from_field(fld: ui.gui.ChoiceField) -> models.ServerGroup:
|
||||
|
||||
|
||||
# Ticket validity time field (for http related tunnels)
|
||||
def tunnel_ticket_validity_field() -> ui.gui.NumericField:
|
||||
def tunnel_ticket_validity_field(order: int = 90, tab: 'types.ui.Tab|None' = types.ui.Tab.ADVANCED) -> ui.gui.NumericField:
|
||||
return ui.gui.NumericField(
|
||||
length=3,
|
||||
label=_('Ticket Validity'),
|
||||
default=60,
|
||||
order=90,
|
||||
order=order,
|
||||
tooltip=_(
|
||||
'Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.'
|
||||
),
|
||||
required=True,
|
||||
min_value=60,
|
||||
tab=types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name='ticketValidity',
|
||||
)
|
||||
|
||||
|
||||
# Tunnel wait time (for uds client related tunnels)
|
||||
def tunnel_wait_time_field(order: int = 2) -> ui.gui.NumericField:
|
||||
def tunnel_wait_time_field(order: int = 2, tab: 'types.ui.Tab|None' = types.ui.Tab.TUNNEL) -> ui.gui.NumericField:
|
||||
return ui.gui.NumericField(
|
||||
length=3,
|
||||
label=_('Tunnel wait time'),
|
||||
@ -158,7 +158,7 @@ def tunnel_wait_time_field(order: int = 2) -> ui.gui.NumericField:
|
||||
order=order,
|
||||
tooltip=_('Maximum time, in seconds, to wait before disable new connections on client tunnel listener'),
|
||||
required=True,
|
||||
tab=types.ui.Tab.TUNNEL,
|
||||
tab=tab,
|
||||
old_field_name='tunnelWait',
|
||||
)
|
||||
|
||||
@ -192,7 +192,7 @@ def get_certificates_from_field(
|
||||
def timeout_field(
|
||||
default: int = 3,
|
||||
order: int = 90,
|
||||
tab: 'types.ui.Tab|str|None|bool' = None,
|
||||
tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
) -> ui.gui.NumericField:
|
||||
return ui.gui.NumericField(
|
||||
@ -203,7 +203,7 @@ def timeout_field(
|
||||
tooltip=_('Timeout in seconds for network connections'),
|
||||
required=True,
|
||||
min_value=1,
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name=old_field_name,
|
||||
)
|
||||
|
||||
@ -226,26 +226,26 @@ def verify_ssl_field(
|
||||
|
||||
|
||||
# Basename field
|
||||
def basename_field(order: int = 32, tab: 'types.ui.Tab|str|None|bool' = None) -> ui.gui.TextField:
|
||||
def basename_field(order: int = 32, tab: 'types.ui.Tab|str|None' = None) -> ui.gui.TextField:
|
||||
return ui.gui.TextField(
|
||||
label=_('Base Name'),
|
||||
order=order,
|
||||
tooltip=_('Base name for clones from this service'),
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
required=True,
|
||||
old_field_name='baseName',
|
||||
)
|
||||
|
||||
|
||||
# Length of name field
|
||||
def lenname_field(order: int = 33, tab: 'types.ui.Tab|str|None|bool' = None) -> ui.gui.NumericField:
|
||||
def lenname_field(order: int = 33, tab: 'types.ui.Tab|str|None' = None) -> ui.gui.NumericField:
|
||||
return ui.gui.NumericField(
|
||||
length=1,
|
||||
label=_('Name Length'),
|
||||
default=3,
|
||||
order=order,
|
||||
tooltip=_('Size of numeric part for the names derived from this service'),
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
required=True,
|
||||
old_field_name='lenName',
|
||||
)
|
||||
@ -253,7 +253,8 @@ def lenname_field(order: int = 33, tab: 'types.ui.Tab|str|None|bool' = None) ->
|
||||
|
||||
# Max preparing services field
|
||||
def concurrent_creation_limit_field(
|
||||
order: int = 50, tab: 'types.ui.Tab|str|None|bool' = None
|
||||
order: int = 50,
|
||||
tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED,
|
||||
) -> ui.gui.NumericField:
|
||||
# Advanced tab
|
||||
return ui.gui.NumericField(
|
||||
@ -265,13 +266,14 @@ def concurrent_creation_limit_field(
|
||||
order=order,
|
||||
tooltip=_('Maximum number of concurrently creating VMs'),
|
||||
required=True,
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name='maxPreparingServices',
|
||||
)
|
||||
|
||||
|
||||
def concurrent_removal_limit_field(
|
||||
order: int = 51, tab: 'types.ui.Tab|str|None|bool' = None
|
||||
order: int = 51,
|
||||
tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED,
|
||||
) -> ui.gui.NumericField:
|
||||
return ui.gui.NumericField(
|
||||
length=3,
|
||||
@ -282,25 +284,27 @@ def concurrent_removal_limit_field(
|
||||
order=order,
|
||||
tooltip=_('Maximum number of concurrently removing VMs'),
|
||||
required=True,
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name='maxRemovingServices',
|
||||
)
|
||||
|
||||
|
||||
def remove_duplicates_field(order: int = 102, tab: 'types.ui.Tab|str|None|bool' = None) -> ui.gui.CheckBoxField:
|
||||
def remove_duplicates_field(
|
||||
order: int = 102, tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED
|
||||
) -> ui.gui.CheckBoxField:
|
||||
return ui.gui.CheckBoxField(
|
||||
label=_('Remove found duplicates'),
|
||||
default=True,
|
||||
order=order,
|
||||
tooltip=_('If active, found duplicates vApps for this service will be removed'),
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name='removeDuplicates',
|
||||
)
|
||||
|
||||
|
||||
def soft_shutdown_field(
|
||||
order: int = 103,
|
||||
tab: 'types.ui.Tab|str|None|bool' = None,
|
||||
tab: 'types.ui.Tab|str|None' = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
) -> ui.gui.CheckBoxField:
|
||||
return ui.gui.CheckBoxField(
|
||||
@ -308,16 +312,16 @@ def soft_shutdown_field(
|
||||
default=False,
|
||||
order=order,
|
||||
tooltip=_(
|
||||
'If active, UDS will try to shutdown (soft) the machine using Nutanix ACPI. Will delay 30 seconds the power off of hanged machines.'
|
||||
'If active, UDS will try to shutdown (soft) the machine. Will delay 90 seconds the power off of hanged machines.'
|
||||
),
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name=old_field_name,
|
||||
)
|
||||
|
||||
|
||||
def keep_on_access_error_field(
|
||||
order: int = 104,
|
||||
tab: 'types.ui.Tab|str|None|bool' = None,
|
||||
tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
) -> ui.gui.CheckBoxField:
|
||||
return ui.gui.CheckBoxField(
|
||||
@ -325,7 +329,7 @@ def keep_on_access_error_field(
|
||||
value=False,
|
||||
order=order,
|
||||
tooltip=_('If active, access errors found on machine will not be considered errors.'),
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name=old_field_name,
|
||||
)
|
||||
|
||||
@ -333,7 +337,7 @@ def keep_on_access_error_field(
|
||||
def macs_range_field(
|
||||
default: str,
|
||||
order: int = 91,
|
||||
tab: 'types.ui.Tab|str|None|bool' = None,
|
||||
tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED,
|
||||
readonly: bool = False,
|
||||
) -> ui.gui.TextField:
|
||||
return ui.gui.TextField(
|
||||
@ -346,12 +350,12 @@ def macs_range_field(
|
||||
default=default
|
||||
),
|
||||
required=True,
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
old_field_name='macsRange',
|
||||
)
|
||||
|
||||
|
||||
def mfa_attr_field(order: int = 20, tab: 'types.ui.Tab|str|None|bool' = None) -> ui.gui.TextField:
|
||||
def mfa_attr_field(order: int = 20, tab: 'types.ui.Tab|str|None' = types.ui.Tab.MFA) -> ui.gui.TextField:
|
||||
return ui.gui.TextField(
|
||||
length=2048,
|
||||
lines=2,
|
||||
@ -359,15 +363,17 @@ def mfa_attr_field(order: int = 20, tab: 'types.ui.Tab|str|None|bool' = None) ->
|
||||
order=order,
|
||||
tooltip=_('Attribute from where to extract the MFA code'),
|
||||
required=False,
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.MFA,
|
||||
tab=tab,
|
||||
old_field_name='mfaAttr',
|
||||
)
|
||||
|
||||
|
||||
def on_logout_field(order: int = 10, tab: 'types.ui.Tab|str|None|bool' = False) -> ui.gui.ChoiceField:
|
||||
def on_logout_field(
|
||||
order: int = 10, tab: 'types.ui.Tab|str|None' = types.ui.Tab.ADVANCED
|
||||
) -> ui.gui.ChoiceField:
|
||||
return ui.gui.ChoiceField(
|
||||
label=_('Logout Action'),
|
||||
order=10,
|
||||
order=order,
|
||||
readonly=True,
|
||||
tooltip=_('What to do when user logs out from service'),
|
||||
choices=[
|
||||
@ -375,15 +381,18 @@ def on_logout_field(order: int = 10, tab: 'types.ui.Tab|str|None|bool' = False)
|
||||
ui.gui.choice_item('remove', _('Remove service')),
|
||||
ui.gui.choice_item('keep-always', _('Keep service assigned even on new publication')),
|
||||
],
|
||||
tab=None if tab is False else None if tab is None else types.ui.Tab.ADVANCED,
|
||||
tab=tab,
|
||||
default='keep',
|
||||
)
|
||||
|
||||
|
||||
def onlogout_field_is_persistent(fld: ui.gui.ChoiceField) -> bool:
|
||||
return fld.value == 'keep-always'
|
||||
|
||||
|
||||
def onlogout_field_is_removable(fld: ui.gui.ChoiceField) -> bool:
|
||||
return fld.value == 'remove'
|
||||
|
||||
|
||||
def onlogout_field_is_keep(fld: ui.gui.ChoiceField) -> bool:
|
||||
return fld.value == 'keep'
|
||||
return fld.value == 'keep'
|
||||
|
@ -75,9 +75,9 @@ class Client:
|
||||
_password: str
|
||||
_timeout: int
|
||||
_cache: 'Cache'
|
||||
_needsUsbFix = True
|
||||
_needs_usb_fix = True
|
||||
|
||||
def __getKey(self, prefix: str = '') -> str:
|
||||
def _generate_key(self, prefix: str = '') -> str:
|
||||
"""
|
||||
Creates a key for the cache, using the prefix indicated as part of it
|
||||
|
||||
@ -88,7 +88,7 @@ class Client:
|
||||
prefix, self._host, self._username, self._password, self._timeout
|
||||
)
|
||||
|
||||
def __getApi(self) -> ovirt.Connection:
|
||||
def _api(self) -> ovirt.Connection:
|
||||
"""
|
||||
Gets the api connection.
|
||||
|
||||
@ -97,7 +97,7 @@ class Client:
|
||||
|
||||
Must be accesed "locked", so we can safely alter cached_api and cached_api_key
|
||||
"""
|
||||
aKey = self.__getKey('o-host')
|
||||
the_key = self._generate_key('o-host')
|
||||
# if cached_api_key == aKey:
|
||||
# return cached_api
|
||||
|
||||
@ -108,7 +108,7 @@ class Client:
|
||||
# Nothing happens, may it was already disconnected
|
||||
pass
|
||||
try:
|
||||
Client.cached_api_key = aKey
|
||||
Client.cached_api_key = the_key
|
||||
Client.cached_api = ovirt.Connection(
|
||||
url='https://' + self._host + '/ovirt-engine/api',
|
||||
username=self._username,
|
||||
@ -137,25 +137,25 @@ class Client:
|
||||
self._password = password
|
||||
self._timeout = int(timeout)
|
||||
self._cache = cache
|
||||
self._needsUsbFix = True
|
||||
self._needs_usb_fix = True
|
||||
|
||||
def test(self) -> bool:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
return self.__getApi().test()
|
||||
return self._api().test()
|
||||
except Exception as e:
|
||||
logger.error('Testing Server failed for oVirt: %s', e)
|
||||
return False
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def isFullyFunctionalVersion(self) -> tuple[bool, str]:
|
||||
def is_fully_functional_version(self) -> tuple[bool, str]:
|
||||
"""
|
||||
'4.0 version is always functional (right now...)
|
||||
"""
|
||||
return True, 'Test successfully passed'
|
||||
|
||||
def getVms(
|
||||
def list_machines(
|
||||
self, force: bool = False
|
||||
) -> list[collections.abc.MutableMapping[str, typing.Any]]:
|
||||
"""
|
||||
@ -172,7 +172,7 @@ class Client:
|
||||
'cluster_id'
|
||||
|
||||
"""
|
||||
vmsKey = self.__getKey('o-vms')
|
||||
vmsKey = self._generate_key('o-vms')
|
||||
val: typing.Optional[typing.Any] = self._cache.get(vmsKey)
|
||||
|
||||
if val is not None and force is False:
|
||||
@ -181,7 +181,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vms: collections.abc.Iterable[typing.Any] = api.system_service().vms_service().list() # type: ignore
|
||||
|
||||
@ -210,7 +210,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getClusters(
|
||||
def list_clusters(
|
||||
self, force: bool = False
|
||||
) -> list[collections.abc.MutableMapping[str, typing.Any]]:
|
||||
"""
|
||||
@ -228,7 +228,7 @@ class Client:
|
||||
'datacenter_id'
|
||||
|
||||
"""
|
||||
clsKey = self.__getKey('o-clusters')
|
||||
clsKey = self._generate_key('o-clusters')
|
||||
val: typing.Optional[typing.Any] = self._cache.get(clsKey)
|
||||
|
||||
if val and not force:
|
||||
@ -237,7 +237,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
clusters: list[typing.Any] = api.system_service().clusters_service().list() # type: ignore
|
||||
|
||||
@ -254,7 +254,7 @@ class Client:
|
||||
}
|
||||
|
||||
# Updates cache info for every single cluster
|
||||
clKey = self.__getKey('o-cluster' + cluster.id)
|
||||
clKey = self._generate_key('o-cluster' + cluster.id)
|
||||
self._cache.put(clKey, val)
|
||||
|
||||
if dc is not None:
|
||||
@ -267,7 +267,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getClusterInfo(
|
||||
def get_cluster_info(
|
||||
self, clusterId: str, force: bool = False
|
||||
) -> collections.abc.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
@ -285,7 +285,7 @@ class Client:
|
||||
'id'
|
||||
'datacenter_id'
|
||||
"""
|
||||
clKey = self.__getKey('o-cluster' + clusterId)
|
||||
clKey = self._generate_key('o-cluster' + clusterId)
|
||||
val = self._cache.get(clKey)
|
||||
|
||||
if val and not force:
|
||||
@ -294,7 +294,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
c: typing.Any = api.system_service().clusters_service().service(clusterId).get() # type: ignore
|
||||
|
||||
@ -309,7 +309,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getDatacenterInfo(
|
||||
def get_datacenter_info(
|
||||
self, datacenterId: str, force: bool = False
|
||||
) -> collections.abc.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
@ -336,7 +336,7 @@ class Client:
|
||||
'active' -> True or False
|
||||
|
||||
"""
|
||||
dcKey = self.__getKey('o-dc' + datacenterId)
|
||||
dcKey = self._generate_key('o-dc' + datacenterId)
|
||||
val = self._cache.get(dcKey)
|
||||
|
||||
if val is not None and force is False:
|
||||
@ -345,7 +345,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
datacenter_service = (
|
||||
api.system_service().data_centers_service().service(datacenterId)
|
||||
@ -383,7 +383,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getStorageInfo(
|
||||
def get_storage_info(
|
||||
self, storageId: str, force: bool = False
|
||||
) -> collections.abc.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
@ -405,7 +405,7 @@ class Client:
|
||||
# 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)
|
||||
|
||||
"""
|
||||
sdKey = self.__getKey('o-sd' + storageId)
|
||||
sdKey = self._generate_key('o-sd' + storageId)
|
||||
val = self._cache.get(sdKey)
|
||||
|
||||
if val and not force:
|
||||
@ -414,7 +414,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
dd: typing.Any = api.system_service().storage_domains_service().service(storageId).get() # type: ignore
|
||||
|
||||
@ -431,7 +431,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def makeTemplate(
|
||||
def create_template(
|
||||
self,
|
||||
name: str,
|
||||
comments: str,
|
||||
@ -467,7 +467,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
# cluster = ov.clusters_service().service('00000002-0002-0002-0002-0000000002e4') # .get()
|
||||
# vm = ov.vms_service().service('e7ff4e00-b175-4e80-9c1f-e50a5e76d347') # .get()
|
||||
@ -506,7 +506,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getTemplateState(self, templateId: str) -> str:
|
||||
def get_template_state(self, templateId: str) -> str:
|
||||
"""
|
||||
Returns current template state.
|
||||
This method do not uses cache at all (it always tries to get template state from oVirt server)
|
||||
@ -521,7 +521,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
try:
|
||||
template: typing.Any = (
|
||||
@ -538,16 +538,16 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def deployFromTemplate(
|
||||
def deploy_from_template(
|
||||
self,
|
||||
name: str,
|
||||
comments: str,
|
||||
templateId: str,
|
||||
clusterId: str,
|
||||
displayType: str,
|
||||
usbType: str,
|
||||
memoryMB: int,
|
||||
guaranteedMB: int,
|
||||
template_id: str,
|
||||
cluster_id: str,
|
||||
display_type: str,
|
||||
usb_type: str,
|
||||
memory_mb: int,
|
||||
guaranteed_mb: int,
|
||||
) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
@ -567,24 +567,24 @@ class Client:
|
||||
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,
|
||||
template_id,
|
||||
cluster_id,
|
||||
display_type,
|
||||
usb_type,
|
||||
memory_mb,
|
||||
guaranteed_mb,
|
||||
)
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
logger.debug('Deploying machine %s', name)
|
||||
|
||||
cluster = ovirt.types.Cluster(id=clusterId)
|
||||
template = ovirt.types.Template(id=templateId)
|
||||
cluster = ovirt.types.Cluster(id=cluster_id)
|
||||
template = ovirt.types.Template(id=template_id)
|
||||
|
||||
if self._needsUsbFix is False and usbType in (
|
||||
if self._needs_usb_fix is False and usb_type 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)
|
||||
@ -592,7 +592,7 @@ class Client:
|
||||
usb = ovirt.types.Usb(enabled=False)
|
||||
|
||||
memoryPolicy = ovirt.types.MemoryPolicy(
|
||||
guaranteed=guaranteedMB * 1024 * 1024
|
||||
guaranteed=guaranteed_mb * 1024 * 1024
|
||||
)
|
||||
par = ovirt.types.Vm(
|
||||
name=name,
|
||||
@ -600,7 +600,7 @@ class Client:
|
||||
template=template,
|
||||
description=comments,
|
||||
type=ovirt.types.VmType.DESKTOP,
|
||||
memory=memoryMB * 1024 * 1024,
|
||||
memory=memory_mb * 1024 * 1024,
|
||||
memory_policy=memoryPolicy,
|
||||
usb=usb,
|
||||
) # display=display,
|
||||
@ -610,7 +610,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
def remove_template(self, templateId: str) -> None:
|
||||
"""
|
||||
Removes a template from ovirt server
|
||||
|
||||
@ -619,14 +619,14 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
api.system_service().templates_service().service(templateId).remove() # type: ignore
|
||||
# This returns nothing, if it fails it raises an exception
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getMachineState(self, machineId: str) -> str:
|
||||
def get_machine_state(self, machineId: str) -> str:
|
||||
"""
|
||||
Returns current state of a machine (running, suspended, ...).
|
||||
This method do not uses cache at all (it always tries to get machine state from oVirt server)
|
||||
@ -645,7 +645,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
try:
|
||||
vm = api.system_service().vms_service().service(machineId).get() # type: ignore
|
||||
@ -660,7 +660,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def startMachine(self, machineId: str) -> None:
|
||||
def start_machine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to oVirt.
|
||||
|
||||
@ -674,7 +674,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
@ -688,7 +688,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def stopMachine(self, machineId: str) -> None:
|
||||
def stop_machine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to oVirt
|
||||
|
||||
@ -700,7 +700,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
@ -714,7 +714,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def suspendMachine(self, machineId: str) -> None:
|
||||
def suspend_machine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to oVirt
|
||||
|
||||
@ -726,7 +726,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
@ -740,7 +740,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def removeMachine(self, machineId: str) -> None:
|
||||
def remove_machine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to delete a machine. No check is done, it is simply requested to oVirt
|
||||
|
||||
@ -752,7 +752,7 @@ class Client:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
@ -766,14 +766,14 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def updateMachineMac(self, machineId: str, macAddres: str) -> None:
|
||||
def update_machine_mac(self, machineId: str, macAddres: str) -> None:
|
||||
"""
|
||||
Changes the mac address of first nic of the machine to the one specified
|
||||
"""
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
@ -794,13 +794,13 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def fixUsb(self, machineId: str) -> None:
|
||||
def fix_usb(self, machineId: str) -> None:
|
||||
# Fix for usb support
|
||||
if self._needsUsbFix:
|
||||
if self._needs_usb_fix:
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
usb = ovirt.types.Usb(enabled=True, type=ovirt.types.UsbType.NATIVE)
|
||||
vms: typing.Any = api.system_service().vms_service().service(machineId)
|
||||
vmu = ovirt.types.Vm(usb=usb)
|
||||
@ -808,7 +808,7 @@ class Client:
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def getConsoleConnection(
|
||||
def get_console_connection(
|
||||
self, machineId: str
|
||||
) -> typing.Optional[collections.abc.MutableMapping[str, typing.Any]]:
|
||||
"""
|
||||
@ -816,7 +816,7 @@ class Client:
|
||||
"""
|
||||
try:
|
||||
lock.acquire(True)
|
||||
api = self.__getApi()
|
||||
api = self._api()
|
||||
|
||||
vmService: typing.Any = (
|
||||
api.system_service().vms_service().service(machineId)
|
||||
|
@ -31,7 +31,7 @@ def get_resources(parameters: typing.Any) -> types.ui.CallbackResultType:
|
||||
provider.deserialize(parameters['ov'])
|
||||
|
||||
# Obtains datacenter from cluster
|
||||
ci = provider.getClusterInfo(parameters['cluster'])
|
||||
ci = provider.get_cluster_info(parameters['cluster'])
|
||||
|
||||
res: list[types.ui.ChoiceItem] = []
|
||||
# Get storages for that datacenter
|
||||
|
@ -89,7 +89,6 @@ class OVirtDeferredRemoval(jobs.Job):
|
||||
|
||||
logger.debug('Looking for deferred vm removals')
|
||||
|
||||
provider: Provider
|
||||
# Look for Providers of type Ovirt
|
||||
for provider in Provider.objects.filter(
|
||||
maintenance_mode=False, data_type=OVirtProvider.type_type
|
||||
|
@ -37,7 +37,7 @@ from django.utils.translation import gettext_noop as _
|
||||
|
||||
from uds.core import services, types, consts
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators
|
||||
from uds.core.util import validators, fields
|
||||
from uds.core.util.cache import Cache
|
||||
from uds.core.util.decorators import cached
|
||||
|
||||
@ -95,7 +95,7 @@ class OVirtProvider(
|
||||
# but used for sample purposes
|
||||
# If we don't indicate an order, the output order of fields will be
|
||||
# "random"
|
||||
ovirtVersion = gui.ChoiceField(
|
||||
ovirt_version = gui.ChoiceField(
|
||||
order=1,
|
||||
label=_('oVirt Version'),
|
||||
tooltip=_('oVirt Server Version'),
|
||||
@ -106,6 +106,7 @@ class OVirtProvider(
|
||||
gui.choice_item('4', '4.x'),
|
||||
],
|
||||
default='4', # Default value is the ID of the choicefield
|
||||
old_field_name='ovirtVersion',
|
||||
)
|
||||
|
||||
host = gui.TextField(
|
||||
@ -131,30 +132,9 @@ class OVirtProvider(
|
||||
required=True,
|
||||
)
|
||||
|
||||
concurrent_creation_limit = gui.NumericField(
|
||||
length=3,
|
||||
label=_('Creation concurrency'),
|
||||
default=10,
|
||||
min_value=1,
|
||||
max_value=65536,
|
||||
order=50,
|
||||
tooltip=_('Maximum number of concurrently creating VMs'),
|
||||
required=True,
|
||||
tab=types.ui.Tab.ADVANCED,
|
||||
old_field_name='maxPreparingServices',
|
||||
)
|
||||
concurrent_removal_limit = gui.NumericField(
|
||||
length=3,
|
||||
label=_('Removal concurrency'),
|
||||
default=5,
|
||||
min_value=1,
|
||||
max_value=65536,
|
||||
order=51,
|
||||
tooltip=_('Maximum number of concurrently removing VMs'),
|
||||
required=True,
|
||||
tab=types.ui.Tab.ADVANCED,
|
||||
old_field_name='maxRemovingServices',
|
||||
)
|
||||
concurrent_creation_limit = fields.concurrent_creation_limit_field()
|
||||
concurrent_removal_limit = fields.concurrent_removal_limit_field()
|
||||
timeout = fields.timeout_field()
|
||||
|
||||
timeout = gui.NumericField(
|
||||
length=3,
|
||||
@ -227,7 +207,7 @@ class OVirtProvider(
|
||||
"""
|
||||
Checks that this version of ovirt if "fully functional" and does not needs "patchs'
|
||||
"""
|
||||
return list(self.__getApi().isFullyFunctionalVersion())
|
||||
return list(self.__getApi().is_fully_functional_version())
|
||||
|
||||
def getMachines(
|
||||
self, force: bool = False
|
||||
@ -247,7 +227,7 @@ class OVirtProvider(
|
||||
'cluster_id'
|
||||
"""
|
||||
|
||||
return self.__getApi().getVms(force)
|
||||
return self.__getApi().list_machines(force)
|
||||
|
||||
def getClusters(
|
||||
self, force: bool = False
|
||||
@ -267,9 +247,9 @@ class OVirtProvider(
|
||||
'datacenter_id'
|
||||
"""
|
||||
|
||||
return self.__getApi().getClusters(force)
|
||||
return self.__getApi().list_clusters(force)
|
||||
|
||||
def getClusterInfo(
|
||||
def get_cluster_info(
|
||||
self, clusterId: str, force: bool = False
|
||||
) -> collections.abc.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
@ -287,7 +267,7 @@ class OVirtProvider(
|
||||
'id'
|
||||
'datacenter_id'
|
||||
"""
|
||||
return self.__getApi().getClusterInfo(clusterId, force)
|
||||
return self.__getApi().get_cluster_info(clusterId, force)
|
||||
|
||||
def getDatacenterInfo(
|
||||
self, datacenterId: str, force: bool = False
|
||||
@ -317,7 +297,7 @@ class OVirtProvider(
|
||||
'active' -> True or False
|
||||
|
||||
"""
|
||||
return self.__getApi().getDatacenterInfo(datacenterId, force)
|
||||
return self.__getApi().get_datacenter_info(datacenterId, force)
|
||||
|
||||
def getStorageInfo(
|
||||
self, storageId: str, force: bool = False
|
||||
@ -341,7 +321,7 @@ class OVirtProvider(
|
||||
# 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)
|
||||
|
||||
"""
|
||||
return self.__getApi().getStorageInfo(storageId, force)
|
||||
return self.__getApi().get_storage_info(storageId, force)
|
||||
|
||||
def makeTemplate(
|
||||
self,
|
||||
@ -366,7 +346,7 @@ class OVirtProvider(
|
||||
Returns
|
||||
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
||||
"""
|
||||
return self.__getApi().makeTemplate(
|
||||
return self.__getApi().create_template(
|
||||
name, comments, machineId, clusterId, storageId, displayType
|
||||
)
|
||||
|
||||
@ -381,7 +361,7 @@ class OVirtProvider(
|
||||
|
||||
(don't know if ovirt returns something more right now, will test what happens when template can't be published)
|
||||
"""
|
||||
return self.__getApi().getTemplateState(templateId)
|
||||
return self.__getApi().get_template_state(templateId)
|
||||
|
||||
def getMachineState(self, machineId: str) -> str:
|
||||
"""
|
||||
@ -399,7 +379,7 @@ class OVirtProvider(
|
||||
suspended, image_illegal, image_locked or powering_down
|
||||
Also can return'unknown' if Machine is not known
|
||||
"""
|
||||
return self.__getApi().getMachineState(machineId)
|
||||
return self.__getApi().get_machine_state(machineId)
|
||||
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
"""
|
||||
@ -407,7 +387,7 @@ class OVirtProvider(
|
||||
|
||||
Returns nothing, and raises an Exception if it fails
|
||||
"""
|
||||
return self.__getApi().removeTemplate(templateId)
|
||||
return self.__getApi().remove_template(templateId)
|
||||
|
||||
def deployFromTemplate(
|
||||
self,
|
||||
@ -435,7 +415,7 @@ class OVirtProvider(
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
"""
|
||||
return self.__getApi().deployFromTemplate(
|
||||
return self.__getApi().deploy_from_template(
|
||||
name,
|
||||
comments,
|
||||
templateId,
|
||||
@ -457,7 +437,7 @@ class OVirtProvider(
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.__getApi().startMachine(machineId)
|
||||
self.__getApi().start_machine(machineId)
|
||||
|
||||
def stopMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
@ -468,7 +448,7 @@ class OVirtProvider(
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.__getApi().stopMachine(machineId)
|
||||
self.__getApi().stop_machine(machineId)
|
||||
|
||||
def suspendMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
@ -479,7 +459,7 @@ class OVirtProvider(
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.__getApi().suspendMachine(machineId)
|
||||
self.__getApi().suspend_machine(machineId)
|
||||
|
||||
def removeMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
@ -490,16 +470,16 @@ class OVirtProvider(
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.__getApi().removeMachine(machineId)
|
||||
self.__getApi().remove_machine(machineId)
|
||||
|
||||
def updateMachineMac(self, machineId: str, macAddres: str) -> None:
|
||||
"""
|
||||
Changes the mac address of first nic of the machine to the one specified
|
||||
"""
|
||||
self.__getApi().updateMachineMac(machineId, macAddres)
|
||||
self.__getApi().update_machine_mac(machineId, macAddres)
|
||||
|
||||
def fixUsb(self, machineId: str) -> None:
|
||||
self.__getApi().fixUsb(machineId)
|
||||
self.__getApi().fix_usb(machineId)
|
||||
|
||||
def getMacRange(self) -> str:
|
||||
return self.macsRange.value
|
||||
@ -507,7 +487,7 @@ class OVirtProvider(
|
||||
def getConsoleConnection(
|
||||
self, machineId: str
|
||||
) -> typing.Optional[collections.abc.MutableMapping[str, typing.Any]]:
|
||||
return self.__getApi().getConsoleConnection(machineId)
|
||||
return self.__getApi().get_console_connection(machineId)
|
||||
|
||||
@cached('reachable', consts.cache.SHORT_CACHE_TIMEOUT)
|
||||
def is_available(self) -> bool:
|
||||
|
@ -32,7 +32,6 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import collections.abc
|
||||
import logging
|
||||
from re import T
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -294,7 +294,9 @@ class ProxmoxClient:
|
||||
key_fnc=caching_key_helper,
|
||||
)
|
||||
def list_node_gpu_devices(self, node: str, **kwargs) -> list[str]:
|
||||
return [device['id'] for device in self._get(f'nodes/{node}/hardware/pci')['data'] if device.get('mdev')]
|
||||
return [
|
||||
device['id'] for device in self._get(f'nodes/{node}/hardware/pci')['data'] if device.get('mdev')
|
||||
]
|
||||
|
||||
@ensure_connected
|
||||
def list_node_vgpus(self, node: str, **kwargs) -> list[typing.Any]:
|
||||
@ -387,9 +389,11 @@ class ProxmoxClient:
|
||||
# Check if mustHaveVGPUS is compatible with the node
|
||||
if must_have_vgpus is not None and must_have_vgpus != bool(self.list_node_gpu_devices(use_node)):
|
||||
raise ProxmoxNoGPUError(f'Node "{use_node}" does not have VGPUS and they are required')
|
||||
|
||||
|
||||
if self.node_has_vgpus_available(use_node, vmInfo.vgpu_type):
|
||||
raise ProxmoxNoGPUError(f'Node "{use_node}" does not have free VGPUS of type {vmInfo.vgpu_type} (requred by VM {vmInfo.name})')
|
||||
raise ProxmoxNoGPUError(
|
||||
f'Node "{use_node}" does not have free VGPUS of type {vmInfo.vgpu_type} (requred by VM {vmInfo.name})'
|
||||
)
|
||||
|
||||
# From normal vm, disable "linked cloning"
|
||||
if as_linked_clone and not vmInfo.template:
|
||||
@ -424,7 +428,7 @@ class ProxmoxClient:
|
||||
|
||||
@ensure_connected
|
||||
@cached('hagrps', CACHE_DURATION, key_fnc=caching_key_helper)
|
||||
def list_ha_groups(self) -> list[str]:
|
||||
def list_ha_groups(self, **kwargs) -> list[str]:
|
||||
return [g['group'] for g in self._get('cluster/ha/groups')['data']]
|
||||
|
||||
@ensure_connected
|
||||
@ -475,7 +479,9 @@ class ProxmoxClient:
|
||||
kwargs='node',
|
||||
key_fnc=caching_key_helper,
|
||||
)
|
||||
def list_machines(self, node: typing.Union[None, str, collections.abc.Iterable[str]] = None) -> list[types.VMInfo]:
|
||||
def list_machines(
|
||||
self, node: typing.Union[None, str, collections.abc.Iterable[str]] = None, **kwargs
|
||||
) -> list[types.VMInfo]:
|
||||
nodeList: collections.abc.Iterable[str]
|
||||
if node is None:
|
||||
nodeList = [n.name for n in self.get_cluster_info().nodes if n.online]
|
||||
@ -672,8 +678,20 @@ class ProxmoxClient:
|
||||
|
||||
@ensure_connected
|
||||
@cached('pools', CACHE_DURATION // 6, key_fnc=caching_key_helper)
|
||||
def list_pools(self) -> list[types.PoolInfo]:
|
||||
return [types.PoolInfo.from_dict(nodeStat) for nodeStat in self._get('pools')['data']]
|
||||
def list_pools(self, **kwargs) -> list[types.PoolInfo]:
|
||||
return [types.PoolInfo.from_dict(poolInfo) for poolInfo in self._get('pools')['data']]
|
||||
|
||||
@ensure_connected
|
||||
@cached('pool', CACHE_DURATION, args=[1, 2], kwargs=['pool_id', 'retrieve_vm_names'], key_fnc=caching_key_helper)
|
||||
def get_pool_info(self, pool_id: str, retrieve_vm_names: bool = False, **kwargs) -> types.PoolInfo:
|
||||
pool_info = types.PoolInfo.from_dict(self._get(f'pools/{pool_id}')['data'])
|
||||
if retrieve_vm_names:
|
||||
for i in range(len(pool_info.members)):
|
||||
try:
|
||||
pool_info.members[i] = pool_info.members[i]._replace(vmname=self.get_machine_info(pool_info.members[i].vmid).name or '')
|
||||
except Exception:
|
||||
pool_info.members[i] = pool_info.members[i]._replace(vmname=f'VM-{pool_info.members[i].vmid}')
|
||||
return pool_info
|
||||
|
||||
@ensure_connected
|
||||
def get_console_connection(
|
||||
|
@ -1,14 +1,13 @@
|
||||
import datetime
|
||||
import re
|
||||
import typing
|
||||
import dataclasses
|
||||
import collections.abc
|
||||
|
||||
NETWORK_RE: typing.Final[typing.Pattern] = re.compile(r'([a-zA-Z0-9]+)=([^,]+)') # May have vla id at end
|
||||
|
||||
# Conversor from dictionary to NamedTuple
|
||||
CONVERSORS: typing.Final[collections.abc.MutableMapping[typing.Type, collections.abc.Callable]] = {
|
||||
str: lambda x: str(x),
|
||||
str: lambda x: str(x or ''),
|
||||
bool: lambda x: bool(x),
|
||||
int: lambda x: int(x or '0'),
|
||||
float: lambda x: float(x or '0'),
|
||||
@ -17,7 +16,7 @@ CONVERSORS: typing.Final[collections.abc.MutableMapping[typing.Type, collections
|
||||
|
||||
|
||||
def _from_dict(
|
||||
type: type[typing.Any],
|
||||
type: type[typing.NamedTuple],
|
||||
dictionary: collections.abc.MutableMapping[str, typing.Any],
|
||||
extra: typing.Optional[collections.abc.Mapping[str, typing.Any]] = None,
|
||||
) -> typing.Any:
|
||||
@ -32,8 +31,8 @@ def _from_dict(
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class Cluster:
|
||||
# Need to be "NamedTuple"s because we use _fields attribute
|
||||
class Cluster(typing.NamedTuple):
|
||||
name: str
|
||||
version: str
|
||||
id: str
|
||||
@ -45,8 +44,7 @@ class Cluster:
|
||||
return _from_dict(Cluster, dictionary)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class Node:
|
||||
class Node(typing.NamedTuple):
|
||||
name: str
|
||||
online: bool
|
||||
local: bool
|
||||
@ -60,8 +58,7 @@ class Node:
|
||||
return _from_dict(Node, dictionary)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class NodeStats:
|
||||
class NodeStats(typing.NamedTuple):
|
||||
name: str
|
||||
status: str
|
||||
uptime: int
|
||||
@ -96,8 +93,7 @@ class NodeStats:
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class ClusterStatus:
|
||||
class ClusterStatus(typing.NamedTuple):
|
||||
cluster: typing.Optional[Cluster]
|
||||
nodes: list[Node]
|
||||
|
||||
@ -115,8 +111,7 @@ class ClusterStatus:
|
||||
return ClusterStatus(cluster=cluster, nodes=nodes)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class UPID:
|
||||
class UPID(typing.NamedTuple):
|
||||
node: str
|
||||
pid: int
|
||||
pstart: int
|
||||
@ -142,8 +137,7 @@ class UPID:
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class TaskStatus:
|
||||
class TaskStatus(typing.NamedTuple):
|
||||
node: str
|
||||
pid: int
|
||||
pstart: int
|
||||
@ -172,8 +166,7 @@ class TaskStatus:
|
||||
return self.is_finished() and not self.is_completed()
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class NetworkConfiguration:
|
||||
class NetworkConfiguration(typing.NamedTuple):
|
||||
net: str
|
||||
type: str
|
||||
mac: str
|
||||
@ -188,8 +181,7 @@ class NetworkConfiguration:
|
||||
return NetworkConfiguration(net=net, type=type, mac=mac)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class VMInfo:
|
||||
class VMInfo(typing.NamedTuple):
|
||||
status: str
|
||||
vmid: int
|
||||
node: str
|
||||
@ -231,8 +223,7 @@ class VMInfo:
|
||||
return data
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class VMConfiguration:
|
||||
class VMConfiguration(typing.NamedTuple):
|
||||
name: str
|
||||
vga: str
|
||||
sockets: int
|
||||
@ -254,15 +245,13 @@ class VMConfiguration:
|
||||
return _from_dict(VMConfiguration, src)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class VmCreationResult:
|
||||
class VmCreationResult(typing.NamedTuple):
|
||||
node: str
|
||||
vmid: int
|
||||
upid: UPID
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class StorageInfo:
|
||||
class StorageInfo(typing.NamedTuple):
|
||||
node: str
|
||||
storage: str
|
||||
content: tuple[str, ...]
|
||||
@ -280,11 +269,32 @@ class StorageInfo:
|
||||
return _from_dict(StorageInfo, dictionary)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class PoolInfo:
|
||||
class PoolMemberInfo(typing.NamedTuple):
|
||||
id: str
|
||||
node: str
|
||||
storage: str
|
||||
type: str
|
||||
vmid: int
|
||||
vmname: str
|
||||
|
||||
@staticmethod
|
||||
def from_dict(dictionary: collections.abc.MutableMapping[str, typing.Any]) -> 'PoolMemberInfo':
|
||||
return _from_dict(PoolMemberInfo, dictionary)
|
||||
|
||||
|
||||
class PoolInfo(typing.NamedTuple):
|
||||
poolid: str
|
||||
comments: str
|
||||
members: list[PoolMemberInfo]
|
||||
|
||||
@staticmethod
|
||||
def from_dict(dictionary: collections.abc.MutableMapping[str, typing.Any]) -> 'PoolInfo':
|
||||
return _from_dict(PoolInfo, dictionary)
|
||||
if 'members' in dictionary:
|
||||
members: list[PoolMemberInfo] = [PoolMemberInfo.from_dict(i) for i in dictionary['members']]
|
||||
else:
|
||||
members = []
|
||||
|
||||
dictionary['comments'] = dictionary.get('comments', '')
|
||||
|
||||
dictionary['members'] = members
|
||||
return _from_dict(PoolInfo, dictionary=dictionary)
|
||||
|
0
server/src/uds/services/Proxmox/deployment_fixed.py
Normal file
0
server/src/uds/services/Proxmox/deployment_fixed.py
Normal file
@ -27,17 +27,18 @@
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import logging
|
||||
import typing
|
||||
import collections.abc
|
||||
from uds.core import types
|
||||
|
||||
from uds.core.environment import Environment
|
||||
import logging
|
||||
from multiprocessing import pool
|
||||
import typing
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from uds.core import types
|
||||
from uds.core.environment import Environment
|
||||
from uds.core.ui.user_interface import gui
|
||||
|
||||
from uds import models
|
||||
from uds.services.OpenNebula.on import vm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -46,9 +47,9 @@ def get_storage(parameters: typing.Any) -> types.ui.CallbackResultType:
|
||||
from .provider import ProxmoxProvider # pylint: disable=import-outside-toplevel
|
||||
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider: ProxmoxProvider = ProxmoxProvider(env)
|
||||
provider.deserialize(parameters['ov'])
|
||||
provider = typing.cast(
|
||||
ProxmoxProvider, models.Provider.objects.get(uuid=parameters['prov_uuid']).get_instance()
|
||||
)
|
||||
|
||||
# Obtains datacenter from cluster
|
||||
try:
|
||||
@ -58,26 +59,41 @@ def get_storage(parameters: typing.Any) -> types.ui.CallbackResultType:
|
||||
|
||||
res = []
|
||||
# Get storages for that datacenter
|
||||
for storage in sorted(
|
||||
provider.listStorages(vm_info.node), key=lambda x: int(not x.shared)
|
||||
):
|
||||
for storage in sorted(provider.list_storages(vm_info.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(vm_info.node)
|
||||
)
|
||||
extra = _(' shared') if storage.shared else _(' (bound to {})').format(vm_info.node)
|
||||
res.append(
|
||||
gui.choice_item(
|
||||
storage.storage,
|
||||
f'{storage.storage} ({space:4.2f} GB/{free:4.2f} GB){extra}'
|
||||
)
|
||||
gui.choice_item(storage.storage, f'{storage.storage} ({space:4.2f} GB/{free:4.2f} GB){extra}')
|
||||
)
|
||||
|
||||
data: types.ui.CallbackResultType = [{'name': 'datastore', 'choices': res}]
|
||||
|
||||
logger.debug('return data: %s', data)
|
||||
return data
|
||||
|
||||
|
||||
def get_machines(parameters: typing.Any) -> types.ui.CallbackResultType:
|
||||
from .provider import ProxmoxProvider # pylint: disable=import-outside-toplevel
|
||||
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
provider = typing.cast(
|
||||
ProxmoxProvider, models.Provider.objects.get(uuid=parameters['prov_uuid']).get_instance()
|
||||
)
|
||||
|
||||
# Obtains datacenter from cluster
|
||||
try:
|
||||
pool_info = provider.get_pool_info(parameters['pool'], retrieve_vm_names=True)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
'name': 'machines',
|
||||
'choices': [gui.choice_item(member.vmid, member.vmname) for member in pool_info.members],
|
||||
}
|
||||
]
|
||||
|
@ -42,6 +42,7 @@ from uds.core.util.unique_mac_generator import UniqueMacGenerator
|
||||
|
||||
from . import client
|
||||
from .service import ProxmoxLinkedService
|
||||
from .service_fixed import ProxmoxFixedService
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -53,13 +54,14 @@ logger = logging.getLogger(__name__)
|
||||
MAX_VM_ID: typing.Final[int] = 999999999
|
||||
|
||||
|
||||
class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-public-methods
|
||||
offers = [ProxmoxLinkedService]
|
||||
class ProxmoxProvider(services.ServiceProvider):
|
||||
type_name = _('Proxmox Platform Provider')
|
||||
type_type = 'ProxmoxPlatform'
|
||||
type_description = _('Proxmox platform service provider')
|
||||
icon_file = 'provider.png'
|
||||
|
||||
offers = [ProxmoxLinkedService, ProxmoxFixedService]
|
||||
|
||||
host = gui.TextField(
|
||||
length=64,
|
||||
label=_('Host'),
|
||||
@ -113,15 +115,15 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
|
||||
macs_range = fields.macs_range_field(default='52:54:00:00:00:00-52:54:00:FF:FF:FF')
|
||||
|
||||
# Own variables
|
||||
_api: typing.Optional[client.ProxmoxClient] = None
|
||||
_cached_api: typing.Optional[client.ProxmoxClient] = None
|
||||
_vmid_generator: UniqueIDGenerator
|
||||
|
||||
def _getApi(self) -> client.ProxmoxClient:
|
||||
def _api(self) -> client.ProxmoxClient:
|
||||
"""
|
||||
Returns the connection API object
|
||||
"""
|
||||
if self._api is None:
|
||||
self._api = client.ProxmoxClient(
|
||||
if self._cached_api is None:
|
||||
self._cached_api = client.ProxmoxClient(
|
||||
self.host.value,
|
||||
self.port.as_int(),
|
||||
self.username.value,
|
||||
@ -131,7 +133,7 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
|
||||
self.cache,
|
||||
)
|
||||
|
||||
return self._api
|
||||
return self._cached_api
|
||||
|
||||
# There is more fields type, but not here the best place to cover it
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
@ -140,7 +142,7 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
|
||||
"""
|
||||
|
||||
# Just reset _api connection variable
|
||||
self._api = None
|
||||
self._cached_api = None
|
||||
|
||||
if values is not None:
|
||||
self.timeout.value = validators.validate_timeout(self.timeout.value)
|
||||
@ -158,97 +160,100 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
|
||||
True if all went fine, false if id didn't
|
||||
"""
|
||||
|
||||
return self._getApi().test()
|
||||
return self._api().test()
|
||||
|
||||
def listMachines(self) -> list[client.types.VMInfo]:
|
||||
return self._getApi().list_machines()
|
||||
def list_machines(self) -> list[client.types.VMInfo]:
|
||||
return self._api().list_machines()
|
||||
|
||||
def get_machine_info(self, vmId: int, poolId: typing.Optional[str] = None) -> client.types.VMInfo:
|
||||
return self._getApi().get_machines_pool_info(vmId, poolId, force=True)
|
||||
return self._api().get_machines_pool_info(vmId, poolId, force=True)
|
||||
|
||||
def get_machine_configuration(self, vmId: int) -> client.types.VMConfiguration:
|
||||
return self._getApi().get_machine_configuration(vmId, force=True)
|
||||
return self._api().get_machine_configuration(vmId, force=True)
|
||||
|
||||
def getStorageInfo(self, storageId: str, node: str) -> client.types.StorageInfo:
|
||||
return self._getApi().get_storage(storageId, node)
|
||||
def get_storage_info(self, storageId: str, node: str) -> client.types.StorageInfo:
|
||||
return self._api().get_storage(storageId, node)
|
||||
|
||||
def listStorages(self, node: typing.Optional[str]) -> list[client.types.StorageInfo]:
|
||||
return self._getApi().list_storages(node=node, content='images')
|
||||
def list_storages(self, node: typing.Optional[str]) -> list[client.types.StorageInfo]:
|
||||
return self._api().list_storages(node=node, content='images')
|
||||
|
||||
def listPools(self) -> list[client.types.PoolInfo]:
|
||||
return self._getApi().list_pools()
|
||||
def list_pools(self) -> list[client.types.PoolInfo]:
|
||||
return self._api().list_pools()
|
||||
|
||||
def get_pool_info(self, pool_id: str, retrieve_vm_names: bool = False) -> client.types.PoolInfo:
|
||||
return self._api().get_pool_info(pool_id, retrieve_vm_names=retrieve_vm_names)
|
||||
|
||||
def make_template(self, vmId: int) -> None:
|
||||
return self._getApi().convertToTemplate(vmId)
|
||||
def create_template(self, vmId: int) -> None:
|
||||
return self._api().convertToTemplate(vmId)
|
||||
|
||||
def cloneMachine(
|
||||
def clone_machine(
|
||||
self,
|
||||
vmId: int,
|
||||
vmid: int,
|
||||
name: str,
|
||||
description: typing.Optional[str],
|
||||
linkedClone: bool,
|
||||
toNode: typing.Optional[str] = None,
|
||||
toStorage: typing.Optional[str] = None,
|
||||
toPool: typing.Optional[str] = None,
|
||||
mustHaveVGPUS: typing.Optional[bool] = None,
|
||||
as_linked_clone: bool,
|
||||
target_node: typing.Optional[str] = None,
|
||||
target_storage: typing.Optional[str] = None,
|
||||
target_pool: typing.Optional[str] = None,
|
||||
must_have_vgpus: typing.Optional[bool] = None,
|
||||
) -> client.types.VmCreationResult:
|
||||
return self._getApi().clone_machine(
|
||||
vmId,
|
||||
return self._api().clone_machine(
|
||||
vmid,
|
||||
self.get_new_vmid(),
|
||||
name,
|
||||
description,
|
||||
linkedClone,
|
||||
toNode,
|
||||
toStorage,
|
||||
toPool,
|
||||
mustHaveVGPUS,
|
||||
as_linked_clone,
|
||||
target_node,
|
||||
target_storage,
|
||||
target_pool,
|
||||
must_have_vgpus,
|
||||
)
|
||||
|
||||
def start_machine(self, vmId: int) -> client.types.UPID:
|
||||
return self._getApi().start_machine(vmId)
|
||||
return self._api().start_machine(vmId)
|
||||
|
||||
def stop_machine(self, vmid: int) -> client.types.UPID:
|
||||
return self._getApi().stop_machine(vmid)
|
||||
return self._api().stop_machine(vmid)
|
||||
|
||||
def reset_machine(self, vmid: int) -> client.types.UPID:
|
||||
return self._getApi().reset_machine(vmid)
|
||||
return self._api().reset_machine(vmid)
|
||||
|
||||
def suspend_machine(self, vmId: int) -> client.types.UPID:
|
||||
return self._getApi().suspend_machine(vmId)
|
||||
return self._api().suspend_machine(vmId)
|
||||
|
||||
def shutdown_machine(self, vmId: int) -> client.types.UPID:
|
||||
return self._getApi().shutdown_machine(vmId)
|
||||
return self._api().shutdown_machine(vmId)
|
||||
|
||||
def remove_machine(self, vmid: int) -> client.types.UPID:
|
||||
return self._getApi().remove_machine(vmid)
|
||||
return self._api().remove_machine(vmid)
|
||||
|
||||
def get_task_info(self, node: str, upid: str) -> client.types.TaskStatus:
|
||||
return self._getApi().get_task(node, upid)
|
||||
return self._api().get_task(node, upid)
|
||||
|
||||
def enable_ha(self, vmId: int, started: bool = False, group: typing.Optional[str] = None) -> None:
|
||||
self._getApi().enable_machine_ha(vmId, started, group)
|
||||
self._api().enable_machine_ha(vmId, started, group)
|
||||
|
||||
def set_machine_mac(self, vmId: int, macAddress: str) -> None:
|
||||
self._getApi().set_machine_ha(vmId, macAddress)
|
||||
self._api().set_machine_ha(vmId, macAddress)
|
||||
|
||||
def disable_ha(self, vmid: int) -> None:
|
||||
self._getApi().disable_machine_ha(vmid)
|
||||
self._api().disable_machine_ha(vmid)
|
||||
|
||||
def set_protection(self, vmId: int, node: typing.Optional[str] = None, protection: bool = False) -> None:
|
||||
self._getApi().set_protection(vmId, node, protection)
|
||||
self._api().set_protection(vmId, node, protection)
|
||||
|
||||
def list_ha_groups(self) -> list[str]:
|
||||
return self._getApi().list_ha_groups()
|
||||
return self._api().list_ha_groups()
|
||||
|
||||
def get_console_connection(
|
||||
self, machineId: str
|
||||
) -> typing.Optional[collections.abc.MutableMapping[str, typing.Any]]:
|
||||
return self._getApi().get_console_connection(machineId)
|
||||
return self._api().get_console_connection(machineId)
|
||||
|
||||
def get_new_vmid(self) -> int:
|
||||
while True: # look for an unused VmId
|
||||
vmid = self._vmid_generator.get(self.start_vmid.as_int(), MAX_VM_ID)
|
||||
if self._getApi().is_vmid_available(vmid):
|
||||
if self._api().is_vmid_available(vmid):
|
||||
return vmid
|
||||
# All assigned VMId will be left as unusable on UDS until released by time (3 years)
|
||||
# This is not a problem at all, in the rare case that a machine id is released from uds db
|
||||
@ -256,7 +261,7 @@ class ProxmoxProvider(services.ServiceProvider): # pylint: disable=too-many-pub
|
||||
|
||||
@cached('reachable', consts.cache.SHORT_CACHE_TIMEOUT)
|
||||
def is_available(self) -> bool:
|
||||
return self._getApi().test()
|
||||
return self._api().test()
|
||||
|
||||
def get_macs_range(self) -> str:
|
||||
return self.macs_range.value
|
||||
|
@ -36,7 +36,7 @@ import collections.abc
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from uds.core import services, types, consts
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators, log
|
||||
from uds.core.util import validators, log, fields
|
||||
from uds.core.util.cache import Cache
|
||||
from uds.core.util.decorators import cached
|
||||
|
||||
@ -66,7 +66,7 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
# : Type used internally to identify this provider
|
||||
type_type = 'ProxmoxLinkedService'
|
||||
# : Description shown at administration interface for this provider
|
||||
type_description = _('Proxmox Services based on templates and COW (experimental)')
|
||||
type_description = _('Proxmox Services based on templates and COW')
|
||||
# : Icon file used as icon for this provider. This string will be translated
|
||||
# : BEFORE sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using gettext_noop)
|
||||
@ -105,8 +105,6 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
||||
services_type_provided = types.services.ServiceType.VDI
|
||||
|
||||
|
||||
|
||||
pool = gui.ChoiceField(
|
||||
label=_("Pool"),
|
||||
order=1,
|
||||
@ -123,23 +121,16 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
guestShutdown = gui.CheckBoxField(
|
||||
label=_('Try SOFT Shutdown first'),
|
||||
default=False,
|
||||
order=103,
|
||||
tooltip=_(
|
||||
'If active, UDS will try to shutdown (soft) the machine using VMWare Guest Tools. Will delay 30 seconds the power off of hanged machines.'
|
||||
),
|
||||
old_field_name='guestShutdown',
|
||||
)
|
||||
|
||||
|
||||
soft_shutdown_field = fields.soft_shutdown_field()
|
||||
|
||||
machine = gui.ChoiceField(
|
||||
label=_("Base Machine"),
|
||||
order=110,
|
||||
fills={
|
||||
'callback_name': 'pmFillResourcesFromMachine',
|
||||
'function': helpers.get_storage,
|
||||
'parameters': ['machine', 'ov', 'ev'],
|
||||
'parameters': ['machine', 'prov_uuid'],
|
||||
},
|
||||
tooltip=_('Service base machine'),
|
||||
tab=_('Machine'),
|
||||
@ -168,37 +159,16 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
tab=_('Machine'),
|
||||
required=True,
|
||||
)
|
||||
|
||||
basename = fields.basename_field(order=115)
|
||||
lenname = fields.lenname_field(order=116)
|
||||
|
||||
baseName = gui.TextField(
|
||||
label=_('Machine Names'),
|
||||
readonly=False,
|
||||
order=115,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
tab=_('Machine'),
|
||||
required=True,
|
||||
old_field_name='baseName',
|
||||
)
|
||||
|
||||
lenName = gui.NumericField(
|
||||
length=1,
|
||||
label=_('Name Length'),
|
||||
default=5,
|
||||
order=116,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
tab=_('Machine'),
|
||||
required=True,
|
||||
old_field_name='lenName',
|
||||
)
|
||||
|
||||
ov = gui.HiddenField(value=None)
|
||||
ev = gui.HiddenField(
|
||||
value=None
|
||||
) # We need to keep the env so we can instantiate the Provider
|
||||
|
||||
prov_uuid = gui.HiddenField(value=None)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
if values:
|
||||
self.baseName.value = validators.validate_basename(
|
||||
self.baseName.value, length=self.lenName.as_int()
|
||||
self.basename.value = validators.validate_basename(
|
||||
self.basename.value, length=self.lenname.as_int()
|
||||
)
|
||||
# if int(self.memory.value) < 128:
|
||||
# raise exceptions.ValidationException(_('The minimum allowed memory is 128 Mb'))
|
||||
@ -207,23 +177,20 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
# Here we have to use "default values", cause values aren't used at form initialization
|
||||
# This is that value is always '', so if we want to change something, we have to do it
|
||||
# at defValue
|
||||
self.ov.value = self.parent().serialize()
|
||||
self.ev.value = self.parent().env.key
|
||||
self.prov_uuid.value = self.parent().db_obj().uuid
|
||||
|
||||
# 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.set_choices(
|
||||
[
|
||||
gui.choice_item(
|
||||
str(m.vmid), f'{m.node}\\{m.name or m.vmid} ({m.vmid})'
|
||||
)
|
||||
for m in self.parent().listMachines()
|
||||
gui.choice_item(str(m.vmid), f'{m.node}\\{m.name or m.vmid} ({m.vmid})')
|
||||
for m in self.parent().list_machines()
|
||||
if m.name and m.name[:3] != 'UDS'
|
||||
]
|
||||
)
|
||||
self.pool.set_choices(
|
||||
[gui.choice_item('', _('None'))]
|
||||
+ [gui.choice_item(p.poolid, p.poolid) for p in self.parent().listPools()]
|
||||
+ [gui.choice_item(p.poolid, p.poolid) for p in self.parent().list_pools()]
|
||||
)
|
||||
self.ha.set_choices(
|
||||
[gui.choice_item('', _('Enabled')), gui.choice_item('__', _('Disabled'))]
|
||||
@ -240,31 +207,29 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
return re.sub("[^a-zA-Z0-9_-]", "-", name)
|
||||
|
||||
def make_template(self, vmId: int) -> None:
|
||||
self.parent().make_template(vmId)
|
||||
self.parent().create_template(vmId)
|
||||
|
||||
def clone_machine(
|
||||
self, name: str, description: str, vmId: int = -1
|
||||
) -> 'client.types.VmCreationResult':
|
||||
def clone_machine(self, name: str, description: str, vmId: int = -1) -> 'client.types.VmCreationResult':
|
||||
name = self.sanitized_name(name)
|
||||
pool = self.pool.value or None
|
||||
if vmId == -1: # vmId == -1 if cloning for template
|
||||
return self.parent().cloneMachine(
|
||||
return self.parent().clone_machine(
|
||||
self.machine.value,
|
||||
name,
|
||||
description,
|
||||
linkedClone=False,
|
||||
toStorage=self.datastore.value,
|
||||
toPool=pool,
|
||||
as_linked_clone=False,
|
||||
target_storage=self.datastore.value,
|
||||
target_pool=pool,
|
||||
)
|
||||
|
||||
return self.parent().cloneMachine(
|
||||
return self.parent().clone_machine(
|
||||
vmId,
|
||||
name,
|
||||
description,
|
||||
linkedClone=True,
|
||||
toStorage=self.datastore.value,
|
||||
toPool=pool,
|
||||
mustHaveVGPUS={'1': True, '2': False}.get(self.gpu.value, None),
|
||||
as_linked_clone=True,
|
||||
target_storage=self.datastore.value,
|
||||
target_pool=pool,
|
||||
must_have_vgpus={'1': True, '2': False}.get(self.gpu.value, None),
|
||||
)
|
||||
|
||||
def get_machine_info(self, vmId: int) -> 'client.types.VMInfo':
|
||||
@ -299,7 +264,7 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
except Exception as e:
|
||||
logger.warning('Exception disabling HA for vm %s: %s', vmId, e)
|
||||
self.do_log(level=log.LogLevel.WARNING, message=f'Exception disabling HA for vm {vmId}: {e}')
|
||||
|
||||
|
||||
# And remove it
|
||||
return self.parent().remove_machine(vmId)
|
||||
|
||||
@ -313,19 +278,17 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
return
|
||||
self.parent().disable_ha(vmId)
|
||||
|
||||
def set_protection(
|
||||
self, vmId: int, node: typing.Optional[str] = None, protection: bool = False
|
||||
) -> None:
|
||||
def set_protection(self, vmId: int, node: typing.Optional[str] = None, protection: bool = False) -> None:
|
||||
self.parent().set_protection(vmId, node, protection)
|
||||
|
||||
def set_machine_mac(self, vmId: int, mac: str) -> None:
|
||||
self.parent().set_machine_mac(vmId, mac)
|
||||
|
||||
def get_basename(self) -> str:
|
||||
return self.baseName.value
|
||||
return self.basename.value
|
||||
|
||||
def get_lenname(self) -> int:
|
||||
return int(self.lenName.value)
|
||||
return int(self.lenname.value)
|
||||
|
||||
def get_macs_range(self) -> str:
|
||||
"""
|
||||
@ -337,7 +300,7 @@ class ProxmoxLinkedService(services.Service): # pylint: disable=too-many-public
|
||||
return self.ha.value != '__'
|
||||
|
||||
def try_graceful_shutdown(self) -> bool:
|
||||
return self.guestShutdown.as_bool()
|
||||
return self.soft_shutdown_field.as_bool()
|
||||
|
||||
def get_console_connection(
|
||||
self, machineId: str
|
||||
|
292
server/src/uds/services/Proxmox/service_fixed.py
Normal file
292
server/src/uds/services/Proxmox/service_fixed.py
Normal file
@ -0,0 +1,292 @@
|
||||
#
|
||||
# Copyright (c) 2012-2022 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# 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.
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# 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"
|
||||
# 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
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
import typing
|
||||
import collections.abc
|
||||
|
||||
from django.utils.translation import gettext_noop as _, gettext
|
||||
from uds.core import services, types, consts, exceptions
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators, log
|
||||
from uds.core.util.cache import Cache
|
||||
from uds.core.util.decorators import cached
|
||||
from uds.core.workers import initialize
|
||||
|
||||
from . import helpers
|
||||
from .deployment import ProxmoxDeployment
|
||||
from .publication import ProxmoxPublication
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.module import Module
|
||||
from uds import models
|
||||
|
||||
from . import client
|
||||
from .provider import ProxmoxProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProxmoxFixedService(services.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 gettext_noop)
|
||||
type_name = _('Proxmox Fixed Machines')
|
||||
# : Type used internally to identify this provider
|
||||
type_type = 'ProxmoxFixedService'
|
||||
# : Description shown at administration interface for this provider
|
||||
type_description = _('Proxmox Services based on fixed machines')
|
||||
# : Icon file used as icon for this provider. This string will be translated
|
||||
# : BEFORE sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using gettext_noop)
|
||||
icon_file = 'service.png'
|
||||
|
||||
# Functional related data
|
||||
|
||||
# : If we need to generate "cache" for this service, so users can access the
|
||||
# : provided services faster. Is uses_cache is True, you will need also
|
||||
# : set publication_type, do take care about that!
|
||||
uses_cache = True
|
||||
# : Tooltip shown to user when this item is pointed at admin interface, none
|
||||
# : because we don't use it
|
||||
cache_tooltip = _('Number of desired machines to keep running waiting for a user')
|
||||
# : If we need to generate a "Level 2" cache for this service (i.e., L1
|
||||
# : could be running machines and L2 suspended machines)
|
||||
uses_cache_l2 = True
|
||||
# : Tooltip shown to user when this item is pointed at admin interface, None
|
||||
# : also because we don't use it
|
||||
cache_tooltip_l2 = _('Number of desired VMs to keep stopped waiting for use')
|
||||
|
||||
# : If the service needs a s.o. manager (managers are related to agents
|
||||
# : provided by services itselfs, i.e. virtual machines with actors)
|
||||
needs_manager = True
|
||||
# : If true, the system can't do an automatic assignation of a deployed user
|
||||
# : service from this service
|
||||
must_assign_manually = False
|
||||
can_reset = True
|
||||
|
||||
# : Types of publications (preparated data for deploys)
|
||||
# : In our case, we do no need a publication, so this is None
|
||||
publication_type = ProxmoxPublication
|
||||
# : Types of deploys (services in cache and/or assigned to users)
|
||||
user_service_type = ProxmoxDeployment
|
||||
|
||||
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
||||
services_type_provided = types.services.ServiceType.VDI
|
||||
|
||||
# Gui
|
||||
token = gui.TextField(
|
||||
order=1,
|
||||
label=_('Service Token'),
|
||||
length=16,
|
||||
tooltip=_(
|
||||
'Service token that will be used by actors to communicate with service. Leave empty for persistent assignation.'
|
||||
),
|
||||
default='',
|
||||
required=False,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
pool = gui.ChoiceField(
|
||||
label=_("Resource Pool"),
|
||||
readonly=False,
|
||||
order=20,
|
||||
fills={
|
||||
'callback_name': 'pmFillMachinesFromResource',
|
||||
'function': helpers.get_machines,
|
||||
'parameters': ['prov_uuid', 'pool'],
|
||||
},
|
||||
tooltip=_('Resource Pool containing base machines'),
|
||||
required=True,
|
||||
tab=_('Machines'),
|
||||
old_field_name='resourcePool',
|
||||
)
|
||||
# Keep name as "machine" so we can use VCHelpers.getMachines
|
||||
machines = gui.MultiChoiceField(
|
||||
label=_("Machines"),
|
||||
order=21,
|
||||
tooltip=_('Machines for this service'),
|
||||
required=True,
|
||||
tab=_('Machines'),
|
||||
rows=10,
|
||||
)
|
||||
|
||||
use_snapshots = gui.CheckBoxField(
|
||||
label=_('Use snapshots'),
|
||||
default=False,
|
||||
order=22,
|
||||
tooltip=_('If active, UDS will try to create an snapshot on VM use and recover if on exit.'),
|
||||
tab=_('Machines'),
|
||||
old_field_name='useSnapshots',
|
||||
)
|
||||
|
||||
prov_uuid = gui.HiddenField(value=None)
|
||||
|
||||
def _get_assigned_machines(self) -> typing.Set[int]:
|
||||
vals = self.storage.get_unpickle('vms')
|
||||
logger.debug('Got storage VMS: %s', vals)
|
||||
return vals or set()
|
||||
|
||||
def _save_assigned_machines(self, vals: typing.Set[int]) -> None:
|
||||
logger.debug('Saving storage VMS: %s', vals)
|
||||
self.storage.put_pickle('vms', vals)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
"""
|
||||
Loads the assigned machines from storage
|
||||
"""
|
||||
if values:
|
||||
if not self.machines.value:
|
||||
raise exceptions.ui.ValidationError(gettext('We need at least a machine'))
|
||||
|
||||
self.storage.put_pickle('maxDeployed', len(self.machines.as_list()))
|
||||
|
||||
# Remove machines not in values from "assigned" set
|
||||
self._save_assigned_machines(self._get_assigned_machines() & set(self.machines.as_list()))
|
||||
self.token.value = self.token.value.strip()
|
||||
self.userservices_limit = self.storage.get_unpickle('maxDeployed')
|
||||
|
||||
def init_gui(self) -> None:
|
||||
# Here we have to use "default values", cause values aren't used at form initialization
|
||||
# This is that value is always '', so if we want to change something, we have to do it
|
||||
# at defValue
|
||||
self.prov_uuid.value = self.parent().get_uuid()
|
||||
|
||||
self.pool.set_choices(
|
||||
[gui.choice_item('', _('None'))]
|
||||
+ [gui.choice_item(p.poolid, p.poolid) for p in self.parent().list_pools()]
|
||||
)
|
||||
|
||||
def parent(self) -> 'ProxmoxProvider':
|
||||
return typing.cast('ProxmoxProvider', super().parent())
|
||||
|
||||
def sanitized_name(self, name: str) -> str:
|
||||
"""
|
||||
Proxmox only allows machine names with [a-zA-Z0-9_-]
|
||||
"""
|
||||
return re.sub("[^a-zA-Z0-9_-]", "-", name)
|
||||
|
||||
def get_machine_info(self, vmId: int) -> 'client.types.VMInfo':
|
||||
return self.parent().get_machine_info(vmId, self.pool.value.strip())
|
||||
|
||||
def get_nic_mac(self, vmid: int) -> str:
|
||||
config = self.parent().get_machine_configuration(vmid)
|
||||
return config.networks[0].mac.lower()
|
||||
|
||||
def get_task_info(self, node: str, upid: str) -> 'client.types.TaskStatus':
|
||||
return self.parent().get_task_info(node, upid)
|
||||
|
||||
def start_machine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().start_machine(vmId)
|
||||
|
||||
def stop_machine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().stop_machine(vmId)
|
||||
|
||||
def reset_machine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().reset_machine(vmId)
|
||||
|
||||
def suspend_machine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().suspend_machine(vmId)
|
||||
|
||||
def shutdown_machine(self, vmId: int) -> 'client.types.UPID':
|
||||
return self.parent().shutdown_machine(vmId)
|
||||
|
||||
def get_machine_from_pool(self) -> int:
|
||||
found_vmid: typing.Optional[int] = None
|
||||
try:
|
||||
assignedVmsSet = self._get_assigned_machines()
|
||||
for k in self.machines.as_list():
|
||||
checking_vmid = int(k)
|
||||
if found_vmid not in assignedVmsSet: # Not assigned
|
||||
# Check that the machine exists...
|
||||
try:
|
||||
vm_info = self.parent().get_machine_info(checking_vmid, self.pool.value.strip())
|
||||
found_vmid = checking_vmid
|
||||
break
|
||||
except Exception: # Notifies on log, but skipt it
|
||||
self.parent().do_log(
|
||||
log.LogLevel.WARNING, 'Machine {} not accesible'.format(found_vmid)
|
||||
)
|
||||
logger.warning(
|
||||
'The service has machines that cannot be checked on vmware (connection error or machine has been deleted): %s',
|
||||
found_vmid,
|
||||
)
|
||||
|
||||
if found_vmid:
|
||||
assignedVmsSet.add(found_vmid)
|
||||
self._save_assigned_machines(assignedVmsSet)
|
||||
except Exception: #
|
||||
raise Exception('No machine available')
|
||||
|
||||
if not found_vmid:
|
||||
raise Exception('All machines from list already assigned.')
|
||||
|
||||
return found_vmid
|
||||
|
||||
def release_machine_from_pool(self, vmid: int) -> None:
|
||||
try:
|
||||
self._save_assigned_machines(self._get_assigned_machines() - {vmid}) # Sets operation
|
||||
except Exception as e:
|
||||
logger.warn('Cound not save assigned machines on vmware fixed pool: %s', e)
|
||||
|
||||
def enumerate_assignables(self) -> list[tuple[str, str]]:
|
||||
# Obtain machines names and ids for asignables
|
||||
vms: dict[int, str] = {}
|
||||
|
||||
for member in self.parent().get_pool_info(self.pool.value.strip()).members:
|
||||
vms[member.vmid] = member.vmname
|
||||
|
||||
assignedVmsSet = self._get_assigned_machines()
|
||||
k: str
|
||||
return [
|
||||
(k, vms.get(int(k), 'Unknown!')) for k in self.machines.as_list() if int(k) not in assignedVmsSet
|
||||
]
|
||||
|
||||
def assign_from_assignables(
|
||||
self, assignable_id: str, user: 'models.User', user_deployment: 'services.UserService'
|
||||
) -> str:
|
||||
userservice_instance: ProxmoxDeployment = typing.cast(ProxmoxDeployment, user_deployment)
|
||||
assignedVmsSet = self._get_assigned_machines()
|
||||
if assignable_id not in assignedVmsSet:
|
||||
assignedVmsSet.add(int(assignable_id))
|
||||
self._save_assigned_machines(assignedVmsSet)
|
||||
return userservice_instance.assign(assignable_id)
|
||||
|
||||
return userservice_instance.error('VM not available!')
|
||||
|
||||
@cached('reachable', consts.cache.SHORT_CACHE_TIMEOUT)
|
||||
def is_avaliable(self) -> bool:
|
||||
return self.parent().is_available()
|
File diff suppressed because one or more lines are too long
@ -102,6 +102,6 @@
|
||||
</svg>
|
||||
</div>
|
||||
</uds-root>
|
||||
<script src="/uds/res/admin/runtime.js?stamp=1706231732" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1706231732" type="module"></script><script src="/uds/res/admin/main.js?stamp=1706231732" type="module"></script></body>
|
||||
<script src="/uds/res/admin/runtime.js?stamp=1706650006" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1706650006" type="module"></script><script src="/uds/res/admin/main.js?stamp=1706650006" type="module"></script></body>
|
||||
|
||||
</html>
|
||||
|
@ -135,8 +135,9 @@ class HTML5SSHTransport(transports.Transport):
|
||||
)
|
||||
ssh_host_key = gui.TextField(
|
||||
label=_('SSH Host Key'),
|
||||
length=512,
|
||||
order=34,
|
||||
tooltip=_('Host key of the SSH server. If not provided, no verification of host identity is done.'),
|
||||
tooltip=_('Host key of the SSH server. If not provided, no verification of host identity is done. (as the line in known_hosts file)'),
|
||||
tab=types.ui.Tab.PARAMETERS,
|
||||
old_field_name='sshHostKey'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user