mirror of
https://github.com/dkmstr/openuds.git
synced 2025-02-09 09:57:36 +03:00
Enhacing OpenStack provider and services
This commit is contained in:
parent
9d00766200
commit
947b35bcf5
@ -147,13 +147,13 @@ def dynamically_load_and_register_packages(
|
|||||||
|
|
||||||
check_function: collections.abc.Callable[[type[V]], bool] = checker or (lambda x: True)
|
check_function: collections.abc.Callable[[type[V]], bool] = checker or (lambda x: True)
|
||||||
|
|
||||||
def process(classes: collections.abc.Iterable[type[V]]) -> None:
|
def _process(classes: collections.abc.Iterable[type[V]]) -> None:
|
||||||
cls: type[V]
|
cls: type[V]
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
clsSubCls = cls.__subclasses__()
|
clsSubCls = cls.__subclasses__()
|
||||||
|
|
||||||
if clsSubCls:
|
if clsSubCls:
|
||||||
process(clsSubCls) # recursive add sub classes
|
_process(clsSubCls) # recursive add sub classes
|
||||||
|
|
||||||
if not check_function(cls):
|
if not check_function(cls):
|
||||||
logger.debug('Node is a not accepted, skipping: %s.%s', cls.__module__, cls.__name__)
|
logger.debug('Node is a not accepted, skipping: %s.%s', cls.__module__, cls.__name__)
|
||||||
@ -168,7 +168,7 @@ def dynamically_load_and_register_packages(
|
|||||||
logger.error(' - Error registering %s.%s: %s', cls.__module__, cls.__name__, e)
|
logger.error(' - Error registering %s.%s: %s', cls.__module__, cls.__name__, e)
|
||||||
|
|
||||||
logger.info('* Start registering %s', module_name)
|
logger.info('* Start registering %s', module_name)
|
||||||
process(type_.__subclasses__())
|
_process(type_.__subclasses__())
|
||||||
logger.info('* Done Registering %s', module_name)
|
logger.info('* Done Registering %s', module_name)
|
||||||
|
|
||||||
|
|
||||||
@ -187,7 +187,7 @@ def dynamically_load_and_register_modules(
|
|||||||
module_name (str): Name of the package to load
|
module_name (str): Name of the package to load
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def checker(cls: type[T]) -> bool:
|
def _checker(cls: type[T]) -> bool:
|
||||||
# Will receive all classes that are subclasses of type_
|
# Will receive all classes that are subclasses of type_
|
||||||
return not cls.is_base
|
return not cls.is_base
|
||||||
|
|
||||||
@ -195,5 +195,5 @@ def dynamically_load_and_register_modules(
|
|||||||
factory.insert,
|
factory.insert,
|
||||||
type_,
|
type_,
|
||||||
module_name,
|
module_name,
|
||||||
checker=checker,
|
checker=_checker,
|
||||||
)
|
)
|
||||||
|
@ -33,8 +33,6 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
|
|||||||
"""
|
"""
|
||||||
# pyright: reportUnusedImport=false
|
# pyright: reportUnusedImport=false
|
||||||
import os.path
|
import os.path
|
||||||
import typing
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_noop as _
|
from django.utils.translation import gettext_noop as _
|
||||||
from uds.core import managers
|
from uds.core import managers
|
||||||
|
@ -39,7 +39,7 @@ import typing
|
|||||||
from uds.core import consts, services, types
|
from uds.core import consts, services, types
|
||||||
from uds.core.util import autoserializable, log
|
from uds.core.util import autoserializable, log
|
||||||
|
|
||||||
from . import openstack
|
from .openstack import types as openstack_types
|
||||||
|
|
||||||
# Not imported at runtime, just for type checking
|
# Not imported at runtime, just for type checking
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
@ -155,12 +155,14 @@ class OpenStackLiveDeployment(
|
|||||||
try:
|
try:
|
||||||
status = self.service().get_machine_state(self._vmid)
|
status = self.service().get_machine_state(self._vmid)
|
||||||
|
|
||||||
if openstack.status_is_lost(status):
|
if status.is_lost():
|
||||||
return self._error('Machine is not available anymore')
|
return self._error('Machine is not available anymore')
|
||||||
|
|
||||||
if status == openstack.PAUSED:
|
power_state = self.service().get_machine_power_state(self._vmid)
|
||||||
|
|
||||||
|
if power_state.is_paused():
|
||||||
self.service().resume_machine(self._vmid)
|
self.service().resume_machine(self._vmid)
|
||||||
elif status in (openstack.STOPPED, openstack.SHUTOFF):
|
elif power_state.is_stopped():
|
||||||
self.service().start_machine(self._vmid)
|
self.service().start_machine(self._vmid)
|
||||||
|
|
||||||
# Right now, we suppose the machine is ready
|
# Right now, we suppose the machine is ready
|
||||||
@ -207,26 +209,22 @@ class OpenStackLiveDeployment(
|
|||||||
else:
|
else:
|
||||||
self._queue = [Operation.CREATE, Operation.WAIT, Operation.SUSPEND, Operation.FINISH]
|
self._queue = [Operation.CREATE, Operation.WAIT, Operation.SUSPEND, Operation.FINISH]
|
||||||
|
|
||||||
def _check_machine_state(self, chkState: str) -> types.states.TaskState:
|
def _check_machine_power_state(self, check_state: openstack_types.PowerState) -> types.states.TaskState:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Checking that state of machine %s (%s) is %s',
|
'Checking that state of machine %s (%s) is %s',
|
||||||
self._vmid,
|
self._vmid,
|
||||||
self._name,
|
self._name,
|
||||||
chkState,
|
check_state,
|
||||||
)
|
)
|
||||||
status = self.service().get_machine_state(self._vmid)
|
power_state = self.service().get_machine_power_state(self._vmid)
|
||||||
|
|
||||||
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
|
||||||
if openstack.status_is_lost(status):
|
|
||||||
return self._error('Machine not available. ({})'.format(status))
|
|
||||||
|
|
||||||
ret = types.states.TaskState.RUNNING
|
ret = types.states.TaskState.RUNNING
|
||||||
chkStates = [chkState] if not isinstance(chkState, (list, tuple)) else chkState
|
if power_state == check_state:
|
||||||
if status in chkStates:
|
|
||||||
ret = types.states.TaskState.FINISHED
|
ret = types.states.TaskState.FINISHED
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def _get_current_op(self) -> Operation:
|
def _get_current_op(self) -> Operation:
|
||||||
if not self._queue:
|
if not self._queue:
|
||||||
return Operation.FINISH
|
return Operation.FINISH
|
||||||
@ -252,7 +250,7 @@ class OpenStackLiveDeployment(
|
|||||||
|
|
||||||
self.do_log(log.LogLevel.ERROR, self._reason)
|
self.do_log(log.LogLevel.ERROR, self._reason)
|
||||||
|
|
||||||
if self._vmid: # Powers off & delete it
|
if self._vmid and self.service().keep_on_error() is False: # Powers off & delete it
|
||||||
try:
|
try:
|
||||||
self.service().remove_machine(self._vmid)
|
self.service().remove_machine(self._vmid)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -333,7 +331,7 @@ class OpenStackLiveDeployment(
|
|||||||
"""
|
"""
|
||||||
status = self.service().get_machine_state(self._vmid)
|
status = self.service().get_machine_state(self._vmid)
|
||||||
|
|
||||||
if openstack.status_is_lost(status):
|
if status.is_lost():
|
||||||
raise Exception('Machine not found. (Status {})'.format(status))
|
raise Exception('Machine not found. (Status {})'.format(status))
|
||||||
|
|
||||||
self.service().remove_machine(self._vmid)
|
self.service().remove_machine(self._vmid)
|
||||||
@ -361,7 +359,7 @@ class OpenStackLiveDeployment(
|
|||||||
"""
|
"""
|
||||||
Checks the state of a deploy for an user or cache
|
Checks the state of a deploy for an user or cache
|
||||||
"""
|
"""
|
||||||
ret = self._check_machine_state(openstack.ACTIVE)
|
ret = self._check_machine_power_state(openstack_types.PowerState.RUNNING)
|
||||||
if ret == types.states.TaskState.FINISHED:
|
if ret == types.states.TaskState.FINISHED:
|
||||||
# Get IP & MAC (early stage)
|
# Get IP & MAC (early stage)
|
||||||
self._mac, self._ip = self.service().get_network_info(self._vmid)
|
self._mac, self._ip = self.service().get_network_info(self._vmid)
|
||||||
@ -372,13 +370,13 @@ class OpenStackLiveDeployment(
|
|||||||
"""
|
"""
|
||||||
Checks if machine has started
|
Checks if machine has started
|
||||||
"""
|
"""
|
||||||
return self._check_machine_state(openstack.ACTIVE)
|
return self._check_machine_power_state(openstack_types.PowerState.RUNNING)
|
||||||
|
|
||||||
def _check_suspend(self) -> types.states.TaskState:
|
def _check_suspend(self) -> types.states.TaskState:
|
||||||
"""
|
"""
|
||||||
Check if the machine has suspended
|
Check if the machine has suspended
|
||||||
"""
|
"""
|
||||||
return self._check_machine_state(openstack.SUSPENDED)
|
return self._check_machine_power_state(openstack_types.PowerState.SUSPENDED)
|
||||||
|
|
||||||
def _check_removed(self) -> types.states.TaskState:
|
def _check_removed(self) -> types.states.TaskState:
|
||||||
"""
|
"""
|
||||||
|
@ -68,20 +68,16 @@ def get_resources(parameters: dict[str, str]) -> types.ui.CallbackResultType:
|
|||||||
|
|
||||||
zones = [gui.choice_item(z.id, z.name) for z in api.list_availability_zones()]
|
zones = [gui.choice_item(z.id, z.name) for z in api.list_availability_zones()]
|
||||||
networks = [
|
networks = [
|
||||||
gui.choice_item(z['id'], z['name']) for z in api.list_networks(name_from_subnets=name_from_subnets)
|
gui.choice_item(z.id, z.name) for z in api.list_networks(name_from_subnets=name_from_subnets)
|
||||||
]
|
|
||||||
flavors = [gui.choice_item(z['id'], z['name']) for z in api.list_flavors()]
|
|
||||||
securityGroups = [gui.choice_item(z['id'], z['name']) for z in api.list_security_groups()]
|
|
||||||
volumeTypes = [gui.choice_item('-', _('None'))] + [
|
|
||||||
gui.choice_item(t.id, t.name) for t in api.list_volume_types()
|
|
||||||
]
|
]
|
||||||
|
flavors = [gui.choice_item(z.id, f'{z.name} ({z.vcpus} vCPUs, {z.ram} MiB)') for z in api.list_flavors() if not z.disabled]
|
||||||
|
security_groups = [gui.choice_item(z.id, z.name) for z in api.list_security_groups()]
|
||||||
|
|
||||||
data: types.ui.CallbackResultType = [
|
data: types.ui.CallbackResultType = [
|
||||||
{'name': 'availabilityZone', 'choices': zones},
|
{'name': 'availability_zone', 'choices': zones},
|
||||||
{'name': 'network', 'choices': networks},
|
{'name': 'network', 'choices': networks},
|
||||||
{'name': 'flavor', 'choices': flavors},
|
{'name': 'flavor', 'choices': flavors},
|
||||||
{'name': 'securityGroups', 'choices': securityGroups},
|
{'name': 'security_groups', 'choices': security_groups},
|
||||||
{'name': 'volumeType', 'choices': volumeTypes},
|
|
||||||
]
|
]
|
||||||
logger.debug('Return data: %s', data)
|
logger.debug('Return data: %s', data)
|
||||||
return data
|
return data
|
||||||
|
@ -30,12 +30,10 @@
|
|||||||
"""
|
"""
|
||||||
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
"""
|
"""
|
||||||
# pyright: reportUnusedImport=false
|
import re
|
||||||
|
|
||||||
from .openstack_client import Client
|
def sanitized_name(name: str) -> str:
|
||||||
|
"""
|
||||||
# Import submodules
|
machine names with [a-zA-Z0-9_-]
|
||||||
from .common import *
|
"""
|
||||||
|
return re.sub("[^a-zA-Z0-9._-]", "_", name)
|
||||||
# Logger imported from .common, if you ask
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
|
||||||
# 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 re
|
|
||||||
|
|
||||||
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',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Helpers to check statuses
|
|
||||||
def status_is_lost(status: str) -> bool:
|
|
||||||
return status in [DELETED, ERROR, UNKNOWN, SOFT_DELETED]
|
|
||||||
|
|
||||||
|
|
||||||
def sanitized_name(name: str) -> str:
|
|
||||||
"""
|
|
||||||
machine names with [a-zA-Z0-9_-]
|
|
||||||
"""
|
|
||||||
return re.sub("[^a-zA-Z0-9._-]", "_", name)
|
|
@ -62,6 +62,7 @@ VOLUMES_ENDPOINT_TYPES = [
|
|||||||
'volumev2',
|
'volumev2',
|
||||||
] # 'volume' is also valid, but it is deprecated A LONG TYPE AGO
|
] # 'volume' is also valid, but it is deprecated A LONG TYPE AGO
|
||||||
COMPUTE_ENDPOINT_TYPES = ['compute', 'compute_legacy']
|
COMPUTE_ENDPOINT_TYPES = ['compute', 'compute_legacy']
|
||||||
|
NETWORKS_ENDPOINT_TYPES = ['network']
|
||||||
|
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
@ -122,7 +123,7 @@ def auth_required(for_project: bool = False) -> collections.abc.Callable[[decora
|
|||||||
|
|
||||||
def decorator(func: decorators.FT) -> decorators.FT:
|
def decorator(func: decorators.FT) -> decorators.FT:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(obj: 'Client', *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
def wrapper(obj: 'OpenstackClient', *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
||||||
if for_project is True:
|
if for_project is True:
|
||||||
if obj._projectid is None:
|
if obj._projectid is None:
|
||||||
raise Exception('Need a project for method {}'.format(func))
|
raise Exception('Need a project for method {}'.format(func))
|
||||||
@ -134,7 +135,7 @@ def auth_required(for_project: bool = False) -> collections.abc.Callable[[decora
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def cache_key_helper(obj: 'Client', *args: typing.Any, **kwargs: typing.Any) -> str:
|
def cache_key_helper(obj: 'OpenstackClient', *args: typing.Any, **kwargs: typing.Any) -> str:
|
||||||
return '_'.join(
|
return '_'.join(
|
||||||
[
|
[
|
||||||
obj._authurl,
|
obj._authurl,
|
||||||
@ -150,7 +151,7 @@ def cache_key_helper(obj: 'Client', *args: typing.Any, **kwargs: typing.Any) ->
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Client: # pylint: disable=too-many-public-methods
|
class OpenstackClient: # pylint: disable=too-many-public-methods
|
||||||
PUBLIC = 'public'
|
PUBLIC = 'public'
|
||||||
PRIVATE = 'private'
|
PRIVATE = 'private'
|
||||||
INTERNAL = 'url'
|
INTERNAL = 'url'
|
||||||
@ -200,7 +201,7 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
self._catalog = None
|
self._catalog = None
|
||||||
self._is_legacy = is_legacy
|
self._is_legacy = is_legacy
|
||||||
|
|
||||||
self._access = Client.PUBLIC if access is None else access
|
self._access = OpenstackClient.PUBLIC if access is None else access
|
||||||
self._domain, self._username, self._password = domain, username, password
|
self._domain, self._username, self._password = domain, username, password
|
||||||
self._userid = None
|
self._userid = None
|
||||||
self._projectid = projectid
|
self._projectid = projectid
|
||||||
@ -480,16 +481,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# return get_recurring_url_json(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/types',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='volume_types',
|
|
||||||
# error_message='List Volume Types',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@decorators.cached(prefix='vols', timeout=consts.cache.SHORT_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='vols', timeout=consts.cache.SHORT_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_volumes(self) -> list[openstack_types.VolumeInfo]:
|
def list_volumes(self) -> list[openstack_types.VolumeInfo]:
|
||||||
return [
|
return [
|
||||||
@ -502,16 +493,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# return get_recurring_url_json(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/volumes/detail',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='volumes',
|
|
||||||
# error_message='List Volumes',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
def list_volume_snapshots(
|
def list_volume_snapshots(
|
||||||
self, volume_id: typing.Optional[dict[str, typing.Any]] = None
|
self, volume_id: typing.Optional[dict[str, typing.Any]] = None
|
||||||
) -> list[openstack_types.VolumeSnapshotInfo]:
|
) -> list[openstack_types.VolumeSnapshotInfo]:
|
||||||
@ -526,18 +507,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
if volume_id is None or snapshot['volume_id'] == volume_id
|
if volume_id is None or snapshot['volume_id'] == volume_id
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# for s in get_recurring_url_json(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/snapshots',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='snapshots',
|
|
||||||
# error_message='List snapshots',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# ):
|
|
||||||
# if volume_id is None or s['volume_id'] == volume_id:
|
|
||||||
# yield s
|
|
||||||
|
|
||||||
@decorators.cached(prefix='azs', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='azs', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_availability_zones(self) -> list[openstack_types.AvailabilityZoneInfo]:
|
def list_availability_zones(self) -> list[openstack_types.AvailabilityZoneInfo]:
|
||||||
return [
|
return [
|
||||||
@ -551,155 +520,97 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
if availability_zone['zoneState']['available'] is True
|
if availability_zone['zoneState']['available'] is True
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# for az in get_recurring_url_json(
|
|
||||||
# self._get_compute_endpoint() + '/os-availability-zone',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='availabilityZoneInfo',
|
|
||||||
# error_message='List Availability Zones',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# ):
|
|
||||||
# if az['zoneState']['available'] is True:
|
|
||||||
# yield az['zoneName']
|
|
||||||
|
|
||||||
@decorators.cached(prefix='flvs', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='flvs', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_flavors(self) -> list[typing.Any]:
|
def list_flavors(self) -> list[openstack_types.FlavorInfo]:
|
||||||
return list(
|
return [
|
||||||
self._get_recurring_from_endpoint(
|
openstack_types.FlavorInfo.from_dict(f)
|
||||||
|
for f in self._get_recurring_from_endpoint(
|
||||||
endpoint_types=COMPUTE_ENDPOINT_TYPES,
|
endpoint_types=COMPUTE_ENDPOINT_TYPES,
|
||||||
path='/flavors',
|
path='/flavors/detail',
|
||||||
error_message='List Flavors',
|
error_message='List Flavors',
|
||||||
key='flavors',
|
key='flavors',
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# return get_recurring_url_json(
|
|
||||||
# self._get_compute_endpoint() + '/flavors',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='flavors',
|
|
||||||
# error_message='List Flavors',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@decorators.cached(prefix='nets', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='nets', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_networks(self, name_from_subnets: bool = False) -> list[typing.Any]:
|
def list_networks(self, name_from_subnets: bool = False) -> list[openstack_types.NetworkInfo]:
|
||||||
nets = self._get_recurring_from_endpoint(
|
nets = self._get_recurring_from_endpoint(
|
||||||
endpoint_types=['network'],
|
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||||
path='/v2.0/networks',
|
path='/v2.0/networks',
|
||||||
error_message='List Networks',
|
error_message='List Networks',
|
||||||
key='networks',
|
key='networks',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# nets = get_recurring_url_json(
|
|
||||||
# self._get_endpoint_for('network') + '/v2.0/networks',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='networks',
|
|
||||||
# error_message='List Networks',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
if not name_from_subnets:
|
if not name_from_subnets:
|
||||||
return list(nets)
|
return [openstack_types.NetworkInfo.from_dict(n) for n in nets]
|
||||||
else:
|
else:
|
||||||
# Get and cache subnets names
|
# Get and cache subnets names
|
||||||
subnetNames = {s['id']: s['name'] for s in self.list_subnets()}
|
subnets_dct = {s.id: f'{s.name} ({s.cidr})' for s in self.list_subnets()}
|
||||||
res: list[typing.Any] = []
|
res: list[openstack_types.NetworkInfo] = []
|
||||||
for net in nets:
|
for net in nets:
|
||||||
name = ','.join(subnetNames[i] for i in net['subnets'] if i in subnetNames)
|
name = ','.join(subnets_dct[i] for i in net['subnets'] if i in subnets_dct)
|
||||||
net['old_name'] = net['name']
|
|
||||||
if name:
|
if name:
|
||||||
net['name'] = name
|
net['name'] = name
|
||||||
|
|
||||||
res.append(net)
|
res.append(openstack_types.NetworkInfo.from_dict(net))
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@decorators.cached(prefix='subns', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='subns', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_subnets(self) -> collections.abc.Iterable[typing.Any]:
|
def list_subnets(self) -> collections.abc.Iterable[openstack_types.SubnetInfo]:
|
||||||
return list(
|
return [
|
||||||
self._get_recurring_from_endpoint(
|
openstack_types.SubnetInfo.from_dict(s)
|
||||||
endpoint_types=['network'],
|
for s in self._get_recurring_from_endpoint(
|
||||||
|
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||||
path='/v2.0/subnets',
|
path='/v2.0/subnets',
|
||||||
error_message='List Subnets',
|
error_message='List Subnets',
|
||||||
key='subnets',
|
key='subnets',
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# return get_recurring_url_json(
|
|
||||||
# self._get_endpoint_for('network') + '/v2.0/subnets',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='subnets',
|
|
||||||
# error_message='List Subnets',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@decorators.cached(prefix='sgps', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
@decorators.cached(prefix='sgps', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
def list_ports(
|
def list_ports(
|
||||||
self,
|
self,
|
||||||
networkId: typing.Optional[str] = None,
|
network_id: typing.Optional[str] = None,
|
||||||
ownerId: typing.Optional[str] = None,
|
owner_id: typing.Optional[str] = None,
|
||||||
) -> list[typing.Any]:
|
) -> list[openstack_types.PortInfo]:
|
||||||
params: dict[str, typing.Any] = {}
|
params: dict[str, typing.Any] = {}
|
||||||
if networkId is not None:
|
if network_id is not None:
|
||||||
params['network_id'] = networkId
|
params['network_id'] = network_id
|
||||||
if ownerId is not None:
|
if owner_id is not None:
|
||||||
params['device_owner'] = ownerId
|
params['device_owner'] = owner_id
|
||||||
|
|
||||||
return list(
|
return [
|
||||||
self._get_recurring_from_endpoint(
|
openstack_types.PortInfo.from_dict(p)
|
||||||
endpoint_types=['network'],
|
for p in self._get_recurring_from_endpoint(
|
||||||
|
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||||
path='/v2.0/ports',
|
path='/v2.0/ports',
|
||||||
error_message='List ports',
|
error_message='List ports',
|
||||||
key='ports',
|
key='ports',
|
||||||
params=params,
|
params=params,
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
@decorators.cached(prefix='sgps', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||||
# return get_recurring_url_json(
|
def list_security_groups(self) -> list[openstack_types.SecurityGroupInfo]:
|
||||||
# self._get_endpoint_for('network') + '/v2.0/ports',
|
return [
|
||||||
# self._session,
|
openstack_types.SecurityGroupInfo.from_dict(sg)
|
||||||
# headers=self._get_request_headers(),
|
for sg in self._get_recurring_from_endpoint(
|
||||||
# key='ports',
|
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||||
# params=params,
|
|
||||||
# error_message='List ports',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@decorators.cached(prefix='sgps', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
|
||||||
def list_security_groups(self) -> list[typing.Any]:
|
|
||||||
return list(
|
|
||||||
self._get_recurring_from_endpoint(
|
|
||||||
endpoint_types=['network'],
|
|
||||||
path='/v2.0/security-groups',
|
path='/v2.0/security-groups',
|
||||||
error_message='List security groups',
|
error_message='List security groups',
|
||||||
key='security_groups',
|
key='security_groups',
|
||||||
)
|
)
|
||||||
)
|
]
|
||||||
|
|
||||||
# TODO: Remove this
|
def get_server(self, server_id: str) -> openstack_types.VMInfo:
|
||||||
# return get_recurring_url_json(
|
|
||||||
# self._get_compute_endpoint() + '/os-security-groups',
|
|
||||||
# self._session,
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# key='security_groups',
|
|
||||||
# error_message='List security groups',
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
def get_server(self, server_id: str) -> dict[str, typing.Any]:
|
|
||||||
r = self._request_from_endpoint(
|
r = self._request_from_endpoint(
|
||||||
'get',
|
'get',
|
||||||
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
||||||
path=f'/servers/{server_id}',
|
path=f'/servers/{server_id}',
|
||||||
error_message='Get Server information',
|
error_message='Get Server information',
|
||||||
)
|
)
|
||||||
return r.json()['server']
|
return openstack_types.VMInfo.from_dict(r.json()['server'])
|
||||||
|
|
||||||
def get_volume(self, volumeId: str) -> dict[str, typing.Any]:
|
def get_volume(self, volumeId: str) -> dict[str, typing.Any]:
|
||||||
r = self._request_from_endpoint(
|
r = self._request_from_endpoint(
|
||||||
@ -709,15 +620,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Get Volume information',
|
error_message='Get Volume information',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.get(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/volumes/{volume_id}'.format(volume_id=volumeId),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Get Volume information')
|
|
||||||
|
|
||||||
return r.json()['volume']
|
return r.json()['volume']
|
||||||
|
|
||||||
def get_snapshot(self, snapshot_id: str) -> dict[str, typing.Any]:
|
def get_snapshot(self, snapshot_id: str) -> dict[str, typing.Any]:
|
||||||
@ -732,15 +634,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Get Snaphost information',
|
error_message='Get Snaphost information',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.get(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/snapshots/{snapshot_id}'.format(snapshot_id=snapshotId),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Get Snaphost information')
|
|
||||||
|
|
||||||
return r.json()['snapshot']
|
return r.json()['snapshot']
|
||||||
|
|
||||||
def update_snapshot(
|
def update_snapshot(
|
||||||
@ -764,16 +657,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Update Snaphost information',
|
error_message='Update Snaphost information',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.put(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/snapshots/{snapshot_id}'.format(snapshot_id=snapshot_id),
|
|
||||||
# data=json.dumps(data),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Update Snaphost information')
|
|
||||||
|
|
||||||
return r.json()['snapshot']
|
return r.json()['snapshot']
|
||||||
|
|
||||||
def create_volume_snapshot(
|
def create_volume_snapshot(
|
||||||
@ -799,16 +682,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Get Volume information',
|
error_message='Get Volume information',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/snapshots',
|
|
||||||
# data=json.dumps(data),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Cannot create snapshot. Ensure volume is in state "available"')
|
|
||||||
|
|
||||||
return r.json()['snapshot']
|
return r.json()['snapshot']
|
||||||
|
|
||||||
def create_volume_from_snapshot(
|
def create_volume_from_snapshot(
|
||||||
@ -832,16 +705,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Create Volume from Snapshot',
|
error_message='Create Volume from Snapshot',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/volumes',
|
|
||||||
# data=json.dumps(data),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Cannot create volume from snapshot.')
|
|
||||||
|
|
||||||
return r.json()['volume']
|
return r.json()['volume']
|
||||||
|
|
||||||
def create_server_from_snapshot(
|
def create_server_from_snapshot(
|
||||||
@ -889,16 +752,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Create instance from snapshot',
|
error_message='Create instance from snapshot',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_compute_endpoint() + '/servers',
|
|
||||||
# data=json.dumps(data),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Cannot create instance from snapshot.')
|
|
||||||
|
|
||||||
return r.json()['server']
|
return r.json()['server']
|
||||||
|
|
||||||
@auth_required(for_project=True)
|
@auth_required(for_project=True)
|
||||||
@ -911,22 +764,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Cannot delete server (probably server does not exists).',
|
error_message='Cannot delete server (probably server does not exists).',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._getEndpointFor('compute', , 'compute_legacy') + '/servers/{server_id}/action'.format(server_id=serverId),
|
|
||||||
# data='{"forceDelete": null}',
|
|
||||||
# headers=self._requestHeaders(),
|
|
||||||
# timeout=self._timeout
|
|
||||||
# )
|
|
||||||
|
|
||||||
# r = self._session.delete(
|
|
||||||
# self._get_compute_endpoint() + f'/servers/{server_id}',
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Cannot delete server (probably server does not exists).')
|
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot_id: str) -> None:
|
def delete_snapshot(self, snapshot_id: str) -> None:
|
||||||
# This does not returns anything
|
# This does not returns anything
|
||||||
self._request_from_endpoint(
|
self._request_from_endpoint(
|
||||||
@ -936,17 +773,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Cannot remove snapshot.',
|
error_message='Cannot remove snapshot.',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.delete(
|
|
||||||
# self._get_endpoint_for('volumev3', 'volumev2') + '/snapshots/{snapshot_id}'.format(snapshot_id=snapshotId),
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Cannot remove snapshot.')
|
|
||||||
|
|
||||||
# Does not returns a message body
|
|
||||||
|
|
||||||
def start_server(self, server_id: str) -> None:
|
def start_server(self, server_id: str) -> None:
|
||||||
# this does not returns anything
|
# this does not returns anything
|
||||||
self._request_from_endpoint(
|
self._request_from_endpoint(
|
||||||
@ -957,16 +783,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Starting server',
|
error_message='Starting server',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
|
||||||
# data='{"os-start": null}',
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Starting server')
|
|
||||||
|
|
||||||
def stop_server(self, server_id: str) -> None:
|
def stop_server(self, server_id: str) -> None:
|
||||||
# this does not returns anything
|
# this does not returns anything
|
||||||
self._request_from_endpoint(
|
self._request_from_endpoint(
|
||||||
@ -977,16 +793,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Stoping server',
|
error_message='Stoping server',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
|
||||||
# data='{"os-stop": null}',
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Stoping server')
|
|
||||||
|
|
||||||
@auth_required(for_project=True)
|
@auth_required(for_project=True)
|
||||||
def suspend_server(self, server_id: str) -> None:
|
def suspend_server(self, server_id: str) -> None:
|
||||||
# this does not returns anything
|
# this does not returns anything
|
||||||
@ -998,16 +804,6 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Suspending server',
|
error_message='Suspending server',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
|
||||||
# data='{"suspend": null}',
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Suspending server')
|
|
||||||
|
|
||||||
def resume_server(self, server_id: str) -> None:
|
def resume_server(self, server_id: str) -> None:
|
||||||
# This does not returns anything
|
# This does not returns anything
|
||||||
self._request_from_endpoint(
|
self._request_from_endpoint(
|
||||||
@ -1018,26 +814,18 @@ class Client: # pylint: disable=too-many-public-methods
|
|||||||
error_message='Resuming server',
|
error_message='Resuming server',
|
||||||
)
|
)
|
||||||
|
|
||||||
# r = self._session.post(
|
|
||||||
# self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
|
||||||
# data='{"resume": null}',
|
|
||||||
# headers=self._get_request_headers(),
|
|
||||||
# timeout=self._timeout,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# ensure_valid_response(r, 'Resuming server')
|
|
||||||
|
|
||||||
def reset_server(self, server_id: str) -> None:
|
def reset_server(self, server_id: str) -> None:
|
||||||
# Does not need return value
|
# Does not need return value
|
||||||
self._session.post(
|
try:
|
||||||
self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
self._request_from_endpoint(
|
||||||
data='{"reboot":{"type":"HARD"}}',
|
'post',
|
||||||
headers=self._get_request_headers(),
|
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
||||||
timeout=self._timeout,
|
path=f'/servers/{server_id}/action',
|
||||||
)
|
data='{"reboot":{"type":"HARD"}}',
|
||||||
|
error_message='Resetting server',
|
||||||
# Ignore response for this...
|
)
|
||||||
# ensureResponseIsValid(r, 'Reseting server')
|
except Exception:
|
||||||
|
pass # Ignore error for reseting server
|
||||||
|
|
||||||
def test_connection(self) -> bool:
|
def test_connection(self) -> bool:
|
||||||
# First, ensure requested api is supported
|
# First, ensure requested api is supported
|
||||||
|
@ -36,33 +36,119 @@ import dataclasses
|
|||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
|
||||||
class State(enum.StrEnum):
|
class Status(enum.StrEnum):
|
||||||
ACTIVE = 'ACTIVE'
|
ACTIVE = 'ACTIVE' # The server is active.
|
||||||
BUILDING = 'BUILDING'
|
BUILD = 'BUILD' # The server has not finished the original build process.
|
||||||
DELETED = 'DELETED'
|
DELETED = 'DELETED' # The server is permanently deleted.
|
||||||
ERROR = 'ERROR'
|
ERROR = 'ERROR' # The server is in error.
|
||||||
HARD_REBOOT = 'HARD_REBOOT'
|
HARD_REBOOT = 'HARD_REBOOT' # The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
|
||||||
MIGRATING = 'MIGRATING'
|
MIGRATING = 'MIGRATING' # The server is being migrated to a new host.
|
||||||
PASSWORD = 'PASSWORD'
|
PASSWORD = 'PASSWORD' # The password is being reset on the server.
|
||||||
PAUSED = 'PAUSED'
|
PAUSED = 'PAUSED' # In a paused state, the state of the server is stored in RAM. A paused server continues to run in frozen state.
|
||||||
REBOOT = 'REBOOT'
|
REBOOT = (
|
||||||
REBUILD = 'REBUILD'
|
'REBOOT' # The server is in a soft reboot state. A reboot command was passed to the operating system.
|
||||||
RESCUED = 'RESCUED'
|
)
|
||||||
RESIZED = 'RESIZED'
|
REBUILD = 'REBUILD' # The server is currently being rebuilt from an image.
|
||||||
REVERT_RESIZE = 'REVERT_RESIZE'
|
RESCUE = 'RESCUE' # The server is in rescue mode. A rescue image is running with the original server image attached.
|
||||||
SOFT_DELETED = 'SOFT_DELETED'
|
RESIZE = 'RESIZE' # Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
|
||||||
STOPPED = 'STOPPED'
|
REVERT_RESIZE = 'REVERT_RESIZE' # The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
|
||||||
SUSPENDED = 'SUSPENDED'
|
SHELVED = 'SHELVED' # The server is in shelved state. Depending on the shelve offload time, the server will be automatically shelved offloaded.
|
||||||
UNKNOWN = 'UNKNOWN'
|
SHELVED_OFFLOADED = 'SHELVED_OFFLOADED' # The shelved server is offloaded (removed from the compute host) and it needs unshelved action to be used again.
|
||||||
VERIFY_RESIZE = 'VERIFY_RESIZE'
|
SHUTOFF = 'SHUTOFF' # The server is powered off and the disk image still persists.
|
||||||
SHUTOFF = 'SHUTOFF'
|
SOFT_DELETED = (
|
||||||
|
'SOFT_DELETED' # The server is marked as deleted but the disk images are still available to restore.
|
||||||
|
)
|
||||||
|
SUSPENDED = 'SUSPENDED' # The server is suspended, either by request or necessity. When you suspend a server, its state is stored on disk, all memory is written to disk, and the server is stopped. Suspending a server is similar to placing a device in hibernation and its occupied resource will not be freed but rather kept for when the server is resumed. If a server is infrequently used and the occupied resource needs to be freed to create other servers, it should be shelved.
|
||||||
|
UNKNOWN = 'UNKNOWN' # The state of the server is unknown. Contact your cloud provider.
|
||||||
|
VERIFY_RESIZE = 'VERIFY_RESIZE' # System is awaiting confirmation that the server is operational after a move or resize.
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_str(s: str) -> 'Status':
|
||||||
|
try:
|
||||||
|
return Status(s)
|
||||||
|
except ValueError:
|
||||||
|
return Status.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
# Helpers to check statuses
|
||||||
|
def is_lost(self) -> bool:
|
||||||
|
return self in [Status.DELETED, Status.ERROR, Status.UNKNOWN, Status.SOFT_DELETED]
|
||||||
|
|
||||||
|
def is_paused(self) -> bool:
|
||||||
|
return self in [Status.PAUSED, Status.SUSPENDED]
|
||||||
|
|
||||||
|
def is_running(self) -> bool:
|
||||||
|
return self in [Status.ACTIVE, Status.RESCUE, Status.RESIZE, Status.VERIFY_RESIZE]
|
||||||
|
|
||||||
|
def is_stopped(self) -> bool:
|
||||||
|
return self in [Status.SHUTOFF, Status.SHELVED, Status.SHELVED_OFFLOADED, Status.SOFT_DELETED]
|
||||||
|
|
||||||
|
|
||||||
|
class PowerState(enum.IntEnum):
|
||||||
|
NOSTATE = 0
|
||||||
|
RUNNING = 1
|
||||||
|
PAUSED = 3
|
||||||
|
SHUTDOWN = 4
|
||||||
|
CRASHED = 6
|
||||||
|
SUSPENDED = 7
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_int(i: int) -> 'PowerState':
|
||||||
|
try:
|
||||||
|
return PowerState(i)
|
||||||
|
except ValueError:
|
||||||
|
return PowerState.NOSTATE
|
||||||
|
|
||||||
|
def is_paused(self) -> bool:
|
||||||
|
return self == PowerState.PAUSED
|
||||||
|
|
||||||
|
def is_running(self) -> bool:
|
||||||
|
return self == PowerState.RUNNING
|
||||||
|
|
||||||
|
def is_stopped(self) -> bool:
|
||||||
|
return self in [PowerState.SHUTDOWN, PowerState.CRASHED, PowerState.SUSPENDED]
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class VMInfo:
|
class VMInfo:
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class AddresInfo:
|
||||||
|
version: int
|
||||||
|
addr: str
|
||||||
|
mac: str
|
||||||
|
type: str
|
||||||
|
network_name: str = ''
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'VMInfo.AddresInfo':
|
||||||
|
return VMInfo.AddresInfo(
|
||||||
|
version=d.get('version') or 4,
|
||||||
|
addr=d.get('addr') or '',
|
||||||
|
mac=d.get('OS-EXT-IPS-MAC:mac_addr') or '',
|
||||||
|
type=d.get('OS-EXT-IPS:type') or '',
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_addresses(adresses: dict[str, list[dict[str, typing.Any]]]) -> list['VMInfo.AddresInfo']:
|
||||||
|
def _build() -> typing.Generator['VMInfo.AddresInfo', None, None]:
|
||||||
|
for net_name, inner_addresses in adresses.items():
|
||||||
|
for address in inner_addresses:
|
||||||
|
address_info = VMInfo.AddresInfo.from_dict(address)
|
||||||
|
address_info.network_name = net_name
|
||||||
|
yield address_info
|
||||||
|
|
||||||
|
return list(_build())
|
||||||
|
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
href: str = ''
|
href: str
|
||||||
|
flavor: str
|
||||||
|
status: Status
|
||||||
|
power_state: PowerState
|
||||||
|
addresses: list[AddresInfo] # network_name: AddresInfo
|
||||||
|
access_addr_ipv4: str
|
||||||
|
access_addr_ipv6: str
|
||||||
|
fault: typing.Optional[str]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(d: dict[str, typing.Any]) -> 'VMInfo':
|
def from_dict(d: dict[str, typing.Any]) -> 'VMInfo':
|
||||||
@ -75,10 +161,22 @@ class VMInfo:
|
|||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Just ignore any error here
|
pass # Just ignore any error here
|
||||||
|
# Try to get flavor, only on >= 2.47
|
||||||
|
try:
|
||||||
|
flavor = d.get('flavor', {}).get('id', '')
|
||||||
|
except Exception:
|
||||||
|
flavor = ''
|
||||||
return VMInfo(
|
return VMInfo(
|
||||||
id=d['id'],
|
id=d['id'],
|
||||||
name=d['name'],
|
name=d['name'],
|
||||||
href=href,
|
href=href,
|
||||||
|
flavor=flavor,
|
||||||
|
status=Status.from_str(d.get('status', Status.UNKNOWN.value)),
|
||||||
|
power_state=PowerState.from_int(d.get('OS-EXT-STS:power_state', PowerState.NOSTATE)),
|
||||||
|
addresses=VMInfo.AddresInfo.from_addresses(d.get('addresses', {})),
|
||||||
|
access_addr_ipv4=d.get('accessIPv4', ''),
|
||||||
|
access_addr_ipv6=d.get('accessIPv6', ''),
|
||||||
|
fault=d.get('fault', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -179,6 +277,7 @@ class VolumeTypeInfo:
|
|||||||
name=d['name'],
|
name=d['name'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class AvailabilityZoneInfo:
|
class AvailabilityZoneInfo:
|
||||||
id: str
|
id: str
|
||||||
@ -189,4 +288,124 @@ class AvailabilityZoneInfo:
|
|||||||
return AvailabilityZoneInfo(
|
return AvailabilityZoneInfo(
|
||||||
id=d['zoneName'],
|
id=d['zoneName'],
|
||||||
name=d['zoneName'],
|
name=d['zoneName'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class FlavorInfo:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
vcpus: int
|
||||||
|
ram: int
|
||||||
|
disk: int
|
||||||
|
swap: int
|
||||||
|
is_public: bool
|
||||||
|
disabled: bool
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'FlavorInfo':
|
||||||
|
return FlavorInfo(
|
||||||
|
id=d['id'],
|
||||||
|
name=d['name'],
|
||||||
|
vcpus=d['vcpus'],
|
||||||
|
ram=d['ram'],
|
||||||
|
disk=d['disk'],
|
||||||
|
swap=d['swap'],
|
||||||
|
is_public=d.get('os-flavor-access:is_public', True),
|
||||||
|
disabled=d.get('OS-FLV-DISABLED:disabled', False),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class NetworkInfo:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
status: str
|
||||||
|
shared: bool
|
||||||
|
subnets: list[str]
|
||||||
|
availability_zones: list[str]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'NetworkInfo':
|
||||||
|
return NetworkInfo(
|
||||||
|
id=d['id'],
|
||||||
|
name=d['name'],
|
||||||
|
status=d['status'],
|
||||||
|
shared=d['shared'],
|
||||||
|
subnets=d['subnets'],
|
||||||
|
availability_zones=d.get('availability_zones', []),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class SubnetInfo:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
cidr: str
|
||||||
|
enable_dhcp: bool
|
||||||
|
gateway_ip: str
|
||||||
|
ip_version: int
|
||||||
|
network_id: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'SubnetInfo':
|
||||||
|
return SubnetInfo(
|
||||||
|
id=d['id'],
|
||||||
|
name=d['name'],
|
||||||
|
cidr=d['cidr'],
|
||||||
|
enable_dhcp=d['enable_dhcp'],
|
||||||
|
gateway_ip=d['gateway_ip'],
|
||||||
|
ip_version=d['ip_version'],
|
||||||
|
network_id=d['network_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class PortInfo:
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class FixedIp:
|
||||||
|
ip_address: str
|
||||||
|
subnet_id: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'PortInfo.FixedIp':
|
||||||
|
return PortInfo.FixedIp(
|
||||||
|
ip_address=d['ip_address'],
|
||||||
|
subnet_id=d['subnet_id'],
|
||||||
|
)
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
status: str
|
||||||
|
device_id: str
|
||||||
|
device_owner: str
|
||||||
|
mac_address: str
|
||||||
|
fixed_ips: list['FixedIp']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'PortInfo':
|
||||||
|
return PortInfo(
|
||||||
|
id=d['id'],
|
||||||
|
name=d['name'],
|
||||||
|
status=d['status'],
|
||||||
|
device_id=d['device_id'],
|
||||||
|
device_owner=d['device_owner'],
|
||||||
|
mac_address=d['mac_address'],
|
||||||
|
fixed_ips=[PortInfo.FixedIp.from_dict(ip) for ip in d['fixed_ips']],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class SecurityGroupInfo:
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(d: dict[str, typing.Any]) -> 'SecurityGroupInfo':
|
||||||
|
return SecurityGroupInfo(
|
||||||
|
id=d['id'],
|
||||||
|
name=d['name'],
|
||||||
|
description=d['description'],
|
||||||
|
)
|
||||||
|
@ -41,7 +41,7 @@ from uds.core.ui import gui
|
|||||||
from uds.core.util import validators, fields
|
from uds.core.util import validators, fields
|
||||||
from uds.core.util.decorators import cached
|
from uds.core.util.decorators import cached
|
||||||
|
|
||||||
from . import openstack
|
from .openstack import openstack_client, sanitized_name
|
||||||
from .service import OpenStackLiveService
|
from .service import OpenStackLiveService
|
||||||
|
|
||||||
# Not imported at runtime, just for type checking
|
# Not imported at runtime, just for type checking
|
||||||
@ -188,7 +188,7 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
legacy = False
|
legacy = False
|
||||||
|
|
||||||
# Own variables
|
# Own variables
|
||||||
_api: typing.Optional[openstack.Client] = None
|
_api: typing.Optional[openstack_client.OpenstackClient] = None
|
||||||
|
|
||||||
def initialize(self, values: 'types.core.ValuesType' = None) -> None:
|
def initialize(self, values: 'types.core.ValuesType' = None) -> None:
|
||||||
"""
|
"""
|
||||||
@ -201,14 +201,14 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
|
|
||||||
def api(
|
def api(
|
||||||
self, projectid: typing.Optional[str] = None, region: typing.Optional[str] = None
|
self, projectid: typing.Optional[str] = None, region: typing.Optional[str] = None
|
||||||
) -> openstack.Client:
|
) -> openstack_client.OpenstackClient:
|
||||||
projectid = projectid or self.tenant.value or None
|
projectid = projectid or self.tenant.value or None
|
||||||
region = region or self.region.value or None
|
region = region or self.region.value or None
|
||||||
if self._api is None:
|
if self._api is None:
|
||||||
proxies = None
|
proxies = None
|
||||||
if self.https_proxy.value.strip():
|
if self.https_proxy.value.strip():
|
||||||
proxies = {'https': self.https_proxy.value}
|
proxies = {'https': self.https_proxy.value}
|
||||||
self._api = openstack.Client(
|
self._api = openstack_client.OpenstackClient(
|
||||||
self.endpoint.value,
|
self.endpoint.value,
|
||||||
-1,
|
-1,
|
||||||
self.domain.value,
|
self.domain.value,
|
||||||
@ -224,7 +224,7 @@ class OpenStackProvider(ServiceProvider):
|
|||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
def sanitized_name(self, name: str) -> str:
|
def sanitized_name(self, name: str) -> str:
|
||||||
return openstack.sanitized_name(name)
|
return sanitized_name(name)
|
||||||
|
|
||||||
def test_connection(self) -> types.core.TestResult:
|
def test_connection(self) -> types.core.TestResult:
|
||||||
"""
|
"""
|
||||||
|
@ -43,7 +43,7 @@ from uds.core.ui import gui
|
|||||||
from uds.core.util import validators, fields
|
from uds.core.util import validators, fields
|
||||||
from uds.core.util.decorators import cached
|
from uds.core.util.decorators import cached
|
||||||
|
|
||||||
from . import openstack
|
from .openstack import openstack_client, sanitized_name
|
||||||
from .service import OpenStackLiveService
|
from .service import OpenStackLiveService
|
||||||
|
|
||||||
# Not imported at runtime, just for type checking
|
# Not imported at runtime, just for type checking
|
||||||
@ -179,7 +179,7 @@ class OpenStackProviderLegacy(ServiceProvider):
|
|||||||
legacy = True
|
legacy = True
|
||||||
|
|
||||||
# Own variables
|
# Own variables
|
||||||
_api: typing.Optional[openstack.Client] = None
|
_api: typing.Optional[openstack_client.OpenstackClient] = None
|
||||||
|
|
||||||
def initialize(self, values: 'types.core.ValuesType') -> None:
|
def initialize(self, values: 'types.core.ValuesType') -> None:
|
||||||
"""
|
"""
|
||||||
@ -190,11 +190,11 @@ class OpenStackProviderLegacy(ServiceProvider):
|
|||||||
if values is not None:
|
if values is not None:
|
||||||
self.timeout.value = validators.validate_timeout(self.timeout.value)
|
self.timeout.value = validators.validate_timeout(self.timeout.value)
|
||||||
|
|
||||||
def api(self, projectid: typing.Optional[str]=None, region: typing.Optional[str]=None) -> openstack.Client:
|
def api(self, projectid: typing.Optional[str]=None, region: typing.Optional[str]=None) -> openstack_client.OpenstackClient:
|
||||||
proxies: typing.Optional[dict[str, str]] = None
|
proxies: typing.Optional[dict[str, str]] = None
|
||||||
if self.https_proxy.value.strip():
|
if self.https_proxy.value.strip():
|
||||||
proxies = {'https': self.https_proxy.value}
|
proxies = {'https': self.https_proxy.value}
|
||||||
return openstack.Client(
|
return openstack_client.OpenstackClient(
|
||||||
self.host.value,
|
self.host.value,
|
||||||
self.port.value,
|
self.port.value,
|
||||||
self.domain.value,
|
self.domain.value,
|
||||||
@ -209,7 +209,7 @@ class OpenStackProviderLegacy(ServiceProvider):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def sanitized_name(self, name: str) -> str:
|
def sanitized_name(self, name: str) -> str:
|
||||||
return openstack.sanitized_name(name)
|
return sanitized_name(name)
|
||||||
|
|
||||||
def test_connection(self) -> types.core.TestResult:
|
def test_connection(self) -> types.core.TestResult:
|
||||||
"""
|
"""
|
||||||
|
@ -36,11 +36,12 @@ import typing
|
|||||||
from django.utils.translation import gettext_noop as _
|
from django.utils.translation import gettext_noop as _
|
||||||
|
|
||||||
from uds.core import services, types
|
from uds.core import services, types
|
||||||
from uds.core.util import validators
|
from uds.core.util import fields, validators
|
||||||
from uds.core.ui import gui
|
from uds.core.ui import gui
|
||||||
|
|
||||||
from .publication import OpenStackLivePublication
|
from .publication import OpenStackLivePublication
|
||||||
from .deployment import OpenStackLiveDeployment
|
from .deployment import OpenStackLiveDeployment
|
||||||
|
from .openstack import types as openstack_types, openstack_client
|
||||||
from . import helpers
|
from . import helpers
|
||||||
|
|
||||||
|
|
||||||
@ -48,7 +49,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# Not imported at runtime, just for type checking
|
# Not imported at runtime, just for type checking
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from . import openstack
|
|
||||||
from .provider import OpenStackProvider
|
from .provider import OpenStackProvider
|
||||||
from .provider_legacy import OpenStackProviderLegacy
|
from .provider_legacy import OpenStackProviderLegacy
|
||||||
|
|
||||||
@ -102,8 +102,6 @@ class OpenStackLiveService(services.Service):
|
|||||||
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
||||||
services_type_provided = types.services.ServiceType.VDI
|
services_type_provided = types.services.ServiceType.VDI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Now the form part
|
# Now the form part
|
||||||
region = gui.ChoiceField(
|
region = gui.ChoiceField(
|
||||||
label=_('Region'),
|
label=_('Region'),
|
||||||
@ -174,31 +172,16 @@ class OpenStackLiveService(services.Service):
|
|||||||
old_field_name='securityGroups',
|
old_field_name='securityGroups',
|
||||||
)
|
)
|
||||||
|
|
||||||
baseName = gui.TextField(
|
basename = fields.basename_field(order=9, tab=_('Machine'))
|
||||||
label=_('Machine Names'),
|
lenname = fields.lenname_field(order=10, tab=_('Machine'))
|
||||||
readonly=False,
|
|
||||||
order=9,
|
|
||||||
tooltip=_('Base name for clones from this machine'),
|
|
||||||
required=True,
|
|
||||||
tab=_('Machine'),
|
|
||||||
)
|
|
||||||
|
|
||||||
lenName = gui.NumericField(
|
maintain_on_error = fields.maintain_on_error_field(order=104)
|
||||||
length=1,
|
|
||||||
label=_('Name Length'),
|
|
||||||
default=5,
|
|
||||||
order=10,
|
|
||||||
tooltip=_('Size of numeric part for the names of these machines'),
|
|
||||||
required=True,
|
|
||||||
tab=_('Machine'),
|
|
||||||
)
|
|
||||||
|
|
||||||
parent_uuid = gui.HiddenField(
|
parent_uuid = gui.HiddenField()
|
||||||
)
|
|
||||||
|
|
||||||
_api: typing.Optional['openstack.Client'] = None
|
_api: typing.Optional['openstack_client.OpenstackClient'] = None
|
||||||
|
|
||||||
def initialize(self, values: types.core.ValuesType) -> None:
|
def initialize(self, values: types.core.ValuesType) -> None:
|
||||||
"""
|
"""
|
||||||
We check here form values to see if they are valid.
|
We check here form values to see if they are valid.
|
||||||
|
|
||||||
@ -206,7 +189,7 @@ class OpenStackLiveService(services.Service):
|
|||||||
initialized by __init__ method of base class, before invoking this.
|
initialized by __init__ method of base class, before invoking this.
|
||||||
"""
|
"""
|
||||||
if values:
|
if values:
|
||||||
validators.validate_basename(self.baseName.value, self.lenName.as_int())
|
validators.validate_basename(self.basename.value, self.lenname.as_int())
|
||||||
|
|
||||||
# self.ov.value = self.provider().serialize()
|
# self.ov.value = self.provider().serialize()
|
||||||
# self.ev.value = self.provider().env.key
|
# self.ev.value = self.provider().env.key
|
||||||
@ -221,55 +204,45 @@ class OpenStackLiveService(services.Service):
|
|||||||
api = self.provider().api()
|
api = self.provider().api()
|
||||||
|
|
||||||
# Checks if legacy or current openstack provider
|
# Checks if legacy or current openstack provider
|
||||||
parent = (
|
parent = typing.cast('OpenStackProvider', self.provider()) if not self.provider().legacy else None
|
||||||
typing.cast('OpenStackProvider', self.provider())
|
|
||||||
if not self.provider().legacy
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
if parent and parent.region.value:
|
if parent and parent.region.value:
|
||||||
regions = [
|
regions = [gui.choice_item(parent.region.value, parent.region.value)]
|
||||||
gui.choice_item(parent.region.value, parent.region.value)
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
regions = [gui.choice_item(r.id, r.name) for r in api.list_regions()]
|
regions = [gui.choice_item(r.id, r.name) for r in api.list_regions()]
|
||||||
|
|
||||||
self.region.set_choices(regions)
|
self.region.set_choices(regions)
|
||||||
|
|
||||||
if parent and parent.tenant.value:
|
if parent and parent.tenant.value:
|
||||||
tenants = [
|
tenants = [gui.choice_item(parent.tenant.value, parent.tenant.value)]
|
||||||
gui.choice_item(parent.tenant.value, parent.tenant.value)
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
tenants = [gui.choice_item(t.id, t.name) for t in api.list_projects()]
|
tenants = [gui.choice_item(t.id, t.name) for t in api.list_projects()]
|
||||||
self.project.set_choices(tenants)
|
self.project.set_choices(tenants)
|
||||||
|
|
||||||
# So we can instantiate parent to get API
|
# So we can instantiate parent to get API
|
||||||
logger.debug(self.provider().serialize())
|
logger.debug(self.provider().serialize())
|
||||||
|
|
||||||
self.parent_uuid.value = self.provider().get_uuid()
|
self.parent_uuid.value = self.provider().get_uuid()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self) -> 'openstack.Client':
|
def api(self) -> 'openstack_client.OpenstackClient':
|
||||||
if not self._api:
|
if not self._api:
|
||||||
self._api = self.provider().api(
|
self._api = self.provider().api(projectid=self.project.value, region=self.region.value)
|
||||||
projectid=self.project.value, region=self.region.value
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
def sanitized_name(self, name: str) -> str:
|
def sanitized_name(self, name: str) -> str:
|
||||||
return self.provider().sanitized_name(name)
|
return self.provider().sanitized_name(name)
|
||||||
|
|
||||||
def make_template(self, template_name: str, description: typing.Optional[str] = None) -> dict[str, typing.Any]:
|
def make_template(
|
||||||
|
self, template_name: str, description: typing.Optional[str] = None
|
||||||
|
) -> dict[str, typing.Any]:
|
||||||
# First, ensures that volume has not any running instances
|
# First, ensures that volume has not any running instances
|
||||||
# if self.api.getVolume(self.volume.value)['status'] != 'available':
|
# if self.api.getVolume(self.volume.value)['status'] != 'available':
|
||||||
# raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')
|
# raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')
|
||||||
|
|
||||||
description = description or 'UDS Template snapshot'
|
description = description or 'UDS Template snapshot'
|
||||||
return self.api.create_volume_snapshot(
|
return self.api.create_volume_snapshot(self.volume.value, template_name, description)
|
||||||
self.volume.value, template_name, description
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_template(self, snapshot_id: str) -> dict[str, typing.Any]:
|
def get_template(self, snapshot_id: str) -> dict[str, typing.Any]:
|
||||||
"""
|
"""
|
||||||
@ -306,43 +279,20 @@ class OpenStackLiveService(services.Service):
|
|||||||
"""
|
"""
|
||||||
self.api.delete_snapshot(templateId)
|
self.api.delete_snapshot(templateId)
|
||||||
|
|
||||||
def get_machine_state(self, machineId: str) -> str:
|
def get_machine_state(self, machine_id: str) -> openstack_types.Status:
|
||||||
"""
|
vminfo = self.api.get_server(machine_id)
|
||||||
Invokes getServer from openstack client
|
if vminfo.status in (openstack_types.Status.ERROR, openstack_types.Status.DELETED):
|
||||||
|
|
||||||
Args:
|
|
||||||
machineId: If of the machine to get state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
one of this values:
|
|
||||||
ACTIVE. The server is active.
|
|
||||||
BUILDING. The server has not finished the original build process.
|
|
||||||
DELETED. The server is permanently deleted.
|
|
||||||
ERROR. The server is in error.
|
|
||||||
HARD_REBOOT. The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
|
|
||||||
MIGRATING. The server is being migrated to a new host.
|
|
||||||
PASSWORD. The password is being reset on the server.
|
|
||||||
PAUSED. In a paused state, the state of the server is stored in RAM. A paused server continues to run in frozen state.
|
|
||||||
REBOOT. The server is in a soft reboot state. A reboot command was passed to the operating system.
|
|
||||||
REBUILD. The server is currently being rebuilt from an image.
|
|
||||||
RESCUED. The server is in rescue mode. A rescue image is running with the original server image attached.
|
|
||||||
RESIZED. Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
|
|
||||||
REVERT_RESIZE. The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
|
|
||||||
SOFT_DELETED. The server is marked as deleted but the disk images are still available to restore.
|
|
||||||
STOPPED. The server is powered off and the disk image still persists.
|
|
||||||
SUSPENDED. The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances.
|
|
||||||
VERIFY_RESIZE. System is awaiting confirmation that the server is operational after a move or resize.
|
|
||||||
SHUTOFF. The server was powered down by the user, either through the OpenStack Compute API or from within the server. For example, the user issued a shutdown -h command from within the server. If the OpenStack Compute manager detects that the VM was powered down, it transitions the server to the SHUTOFF status.
|
|
||||||
"""
|
|
||||||
server = self.api.get_server(machineId)
|
|
||||||
if server['status'] in ('ERROR', 'DELETED'):
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Got server status %s for %s: %s',
|
'Got server status %s for %s: %s',
|
||||||
server['status'],
|
vminfo.status,
|
||||||
machineId,
|
machine_id,
|
||||||
server.get('fault'),
|
vminfo.fault,
|
||||||
)
|
)
|
||||||
return server['status']
|
return vminfo.status
|
||||||
|
|
||||||
|
def get_machine_power_state(self, machine_id: str) -> openstack_types.PowerState:
|
||||||
|
vminfo = self.api.get_server(machine_id)
|
||||||
|
return vminfo.power_state
|
||||||
|
|
||||||
def start_machine(self, machineId: str) -> None:
|
def start_machine(self, machineId: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -416,24 +366,26 @@ class OpenStackLiveService(services.Service):
|
|||||||
"""
|
"""
|
||||||
Gets the mac address of first nic of the machine
|
Gets the mac address of first nic of the machine
|
||||||
"""
|
"""
|
||||||
net = self.api.get_server(machineid)['addresses']
|
vminfo = self.api.get_server(machineid)
|
||||||
vals = next(iter(net.values()))[
|
return vminfo.addresses[0].addr, vminfo.addresses[0].mac.upper()
|
||||||
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 get_basename(self) -> str:
|
def get_basename(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the base name
|
Returns the base name
|
||||||
"""
|
"""
|
||||||
return self.baseName.value
|
return self.basename.value
|
||||||
|
|
||||||
def get_lenname(self) -> int:
|
def get_lenname(self) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the length of numbers part
|
Returns the length of numbers part
|
||||||
"""
|
"""
|
||||||
return int(self.lenName.value)
|
return int(self.lenname.value)
|
||||||
|
|
||||||
def is_avaliable(self) -> bool:
|
def is_avaliable(self) -> bool:
|
||||||
return self.provider().is_available()
|
return self.provider().is_available()
|
||||||
|
|
||||||
|
def can_clean_errored_userservices(self) -> bool:
|
||||||
|
return not self.maintain_on_error.value
|
||||||
|
|
||||||
|
def keep_on_error(self) -> bool:
|
||||||
|
return not self.maintain_on_error.as_bool()
|
||||||
|
@ -76,14 +76,14 @@ class XenFailure(XenAPI.Failure, XenFault):
|
|||||||
|
|
||||||
def as_human_readable(self) -> str:
|
def as_human_readable(self) -> str:
|
||||||
try:
|
try:
|
||||||
errList = {
|
error_list = {
|
||||||
XenFailure.exBadVmPowerState: 'Machine state is invalid for requested operation (needs {2} and state is {3})',
|
XenFailure.exBadVmPowerState: 'Machine state is invalid for requested operation (needs {2} and state is {3})',
|
||||||
XenFailure.exVmMissingPVDrivers: 'Machine needs Xen Server Tools to allow requested operation',
|
XenFailure.exVmMissingPVDrivers: 'Machine needs Xen Server Tools to allow requested operation',
|
||||||
XenFailure.exHostIsSlave: 'The connected host is an slave, try to connect to {1}',
|
XenFailure.exHostIsSlave: 'The connected host is an slave, try to connect to {1}',
|
||||||
XenFailure.exSRError: 'Error on SR: {2}',
|
XenFailure.exSRError: 'Error on SR: {2}',
|
||||||
XenFailure.exHandleInvalid: 'Invalid reference to {1}',
|
XenFailure.exHandleInvalid: 'Invalid reference to {1}',
|
||||||
}
|
}
|
||||||
err = errList.get(typing.cast(typing.Any, self.details[0]), 'Error {0}')
|
err = error_list.get(typing.cast(typing.Any, self.details[0]), 'Error {0}')
|
||||||
|
|
||||||
return err.format(*typing.cast(list[typing.Any], self.details))
|
return err.format(*typing.cast(list[typing.Any], self.details))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user