mirror of
https://github.com/dkmstr/openuds.git
synced 2025-02-02 09:47:13 +03:00
Many more improvements to openstack and advanced on tests. Hope to finish them by tomorrow
This commit is contained in:
parent
d542e65a9c
commit
12548b44cc
@ -98,21 +98,29 @@ class Publication(Environmentable, Serializable):
|
||||
service: 'services.Service',
|
||||
osmanager: typing.Optional['osmanagers.OSManager'] = None,
|
||||
revision: int = -1,
|
||||
servicepool_name: str = 'Unknown',
|
||||
servicepool_name: typing.Optional[str] = None,
|
||||
uuid: str = '',
|
||||
) -> None:
|
||||
"""
|
||||
Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, values)"
|
||||
Instead of overriding __init__, override initialize method, that will be called
|
||||
just after all internal initialization is completed.
|
||||
We want to use the env, cache and storage methods outside class. If not called, you must implement your own methods
|
||||
cache and storage are "convenient" methods to access _env.cache and _env.storage
|
||||
@param environment: Environment assigned to this publication
|
||||
|
||||
Args:
|
||||
environment (Environment): Environment of the service
|
||||
service (services.Service): Service that owns this publication
|
||||
osmanager (osmanagers.OSManager, optional): OsManager associated with this publication. Defaults to None.
|
||||
revision (int, optional): Revision of the publication. Defaults to -1.
|
||||
servicepool_name (str, optional): Name of the service pool. Defaults to None. (Unknown)
|
||||
uuid (str, optional): UUID of the publication. Defaults to ''.
|
||||
"""
|
||||
Environmentable.__init__(self, environment)
|
||||
Serializable.__init__(self)
|
||||
self._service = service
|
||||
self._osmanager = osmanager
|
||||
self._revision = revision
|
||||
self._servicepool_name = servicepool_name
|
||||
self._servicepool_name = servicepool_name or 'Unknown'
|
||||
self._uuid = uuid
|
||||
|
||||
self.initialize()
|
||||
|
@ -109,7 +109,7 @@ class UserService(Environmentable, Serializable):
|
||||
method error_reason can be called multiple times, including
|
||||
serializations in middle, so remember to include reason of error in serializations
|
||||
"""
|
||||
|
||||
USER: int = 0 # : Constant for User cache level
|
||||
L1_CACHE = 1 # : Constant for Cache of level 1
|
||||
L2_CACHE = 2 # : Constant for Cache of level 2
|
||||
|
||||
|
@ -51,6 +51,10 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# How many times we will check for a machine to be ready/stopped/whatever
|
||||
# 25 = 25 * 5 = 125 seconds (5 is suggested_delay)
|
||||
CHECK_COUNT_BEFORE_FAILURE: typing.Final[int] = 25
|
||||
|
||||
|
||||
class Operation(enum.IntEnum):
|
||||
CREATE = 0
|
||||
@ -61,6 +65,7 @@ class Operation(enum.IntEnum):
|
||||
ERROR = 5
|
||||
FINISH = 6
|
||||
RETRY = 7
|
||||
STOP = 8
|
||||
|
||||
UNKNOWN = 99
|
||||
|
||||
@ -72,7 +77,7 @@ class Operation(enum.IntEnum):
|
||||
return Operation.UNKNOWN
|
||||
|
||||
|
||||
class OpenStackLiveDeployment(
|
||||
class OpenStackLiveUserService(
|
||||
services.UserService, autoserializable.AutoSerializable
|
||||
): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
@ -90,6 +95,7 @@ class OpenStackLiveDeployment(
|
||||
_mac = autoserializable.StringField(default='')
|
||||
_vmid = autoserializable.StringField(default='')
|
||||
_reason = autoserializable.StringField(default='')
|
||||
_check_count = autoserializable.IntegerField(default=CHECK_COUNT_BEFORE_FAILURE)
|
||||
_queue = autoserializable.ListField[Operation]()
|
||||
|
||||
# _name: str = ''
|
||||
@ -200,26 +206,32 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
Deploys an service instance for cache
|
||||
"""
|
||||
self._init_queue_for_deploy(level == self.L2_CACHE)
|
||||
self._init_queue_for_deploy(level)
|
||||
return self._execute_queue()
|
||||
|
||||
def _init_queue_for_deploy(self, forLevel2: bool = False) -> None:
|
||||
if forLevel2 is False:
|
||||
def _init_queue_for_deploy(self, level: int) -> None:
|
||||
if level in (self.USER, self.L1_CACHE):
|
||||
self._queue = [Operation.CREATE, Operation.FINISH]
|
||||
else:
|
||||
self._queue = [Operation.CREATE, Operation.WAIT, Operation.SUSPEND, Operation.FINISH]
|
||||
|
||||
def _check_machine_power_state(self, check_state: openstack_types.PowerState) -> types.states.TaskState:
|
||||
def _check_machine_power_state(self, *check_state: 'openstack_types.PowerState') -> types.states.TaskState:
|
||||
self._check_count -= 1
|
||||
if self._check_count < 0:
|
||||
return self._error('Machine is not {str(check_state)} after {CHECK_COUNT_BEFORE_FAILURE} checks')
|
||||
|
||||
logger.debug(
|
||||
'Checking that state of machine %s (%s) is %s',
|
||||
'Checking that state of machine %s (%s) is %s (remaining checks: %s)',
|
||||
self._vmid,
|
||||
self._name,
|
||||
check_state,
|
||||
self._check_count,
|
||||
)
|
||||
power_state = self.service().get_machine_power_state(self._vmid)
|
||||
|
||||
ret = types.states.TaskState.RUNNING
|
||||
if power_state == check_state:
|
||||
|
||||
if power_state in check_state:
|
||||
ret = types.states.TaskState.FINISHED
|
||||
|
||||
return ret
|
||||
@ -236,6 +248,11 @@ class OpenStackLiveDeployment(
|
||||
|
||||
return self._queue.pop(0)
|
||||
|
||||
def _reset_check_count(self) -> None:
|
||||
# Check the maximum number of checks before failure
|
||||
# So we dont stuck forever on check state to CHECK_COUNT_BEFORE_FAILURE
|
||||
self._check_count = CHECK_COUNT_BEFORE_FAILURE
|
||||
|
||||
def _error(self, reason: typing.Any) -> types.states.TaskState:
|
||||
"""
|
||||
Internal method to set object as error state
|
||||
@ -244,16 +261,26 @@ class OpenStackLiveDeployment(
|
||||
types.states.DeployState.ERROR, so we can do "return self.__error(reason)"
|
||||
"""
|
||||
logger.debug('Setting error state, reason: %s', reason)
|
||||
is_creation = self._get_current_op() == Operation.CREATE
|
||||
self._queue = [Operation.ERROR]
|
||||
self._reason = str(reason)
|
||||
|
||||
self.do_log(log.LogLevel.ERROR, self._reason)
|
||||
|
||||
if self._vmid and self.service().keep_on_error() is False: # Powers off & delete it
|
||||
try:
|
||||
self.service().delete_machine(self._vmid)
|
||||
except Exception:
|
||||
logger.warning('Can\t set machine %s state to stopped', self._vmid)
|
||||
if self._vmid:
|
||||
# Creating machines should be deleted on error
|
||||
if is_creation or self.service().keep_on_error() is False: # Powers off & delete it
|
||||
try:
|
||||
self.service().delete_machine(self._vmid)
|
||||
except Exception:
|
||||
logger.warning('Can\t set machine %s state to stopped', self._vmid)
|
||||
else:
|
||||
self.do_log(
|
||||
log.LogLevel.INFO, 'Keep on error is enabled, machine will not be marked for deletion'
|
||||
)
|
||||
# Simple fix queue to FINISH and return it
|
||||
self._queue = [Operation.FINISH]
|
||||
return types.states.TaskState.FINISHED
|
||||
|
||||
return types.states.TaskState.ERROR
|
||||
|
||||
@ -267,27 +294,18 @@ class OpenStackLiveDeployment(
|
||||
if op == Operation.FINISH:
|
||||
return types.states.TaskState.FINISHED
|
||||
|
||||
fncs: dict[int, collections.abc.Callable[[], str]] = {
|
||||
Operation.CREATE: self._create,
|
||||
Operation.RETRY: self._retry,
|
||||
Operation.START: self._start_machine,
|
||||
Operation.SUSPEND: self._suspend_machine,
|
||||
Operation.WAIT: self._wait,
|
||||
Operation.REMOVE: self._remove,
|
||||
}
|
||||
|
||||
try:
|
||||
if op not in fncs:
|
||||
if op not in _EXECUTE_FNCS:
|
||||
return self._error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
|
||||
fncs[op]()
|
||||
_EXECUTE_FNCS[op](self)
|
||||
|
||||
return types.states.TaskState.RUNNING
|
||||
except Exception as e:
|
||||
return self._error(e)
|
||||
|
||||
# Queue execution methods
|
||||
def _retry(self) -> types.states.TaskState:
|
||||
def _retry(self) -> None:
|
||||
"""
|
||||
Used to retry an operation
|
||||
In fact, this will not be never invoked, unless we push it twice, because
|
||||
@ -295,15 +313,15 @@ class OpenStackLiveDeployment(
|
||||
|
||||
At executeQueue this return value will be ignored, and it will only be used at check_state
|
||||
"""
|
||||
return types.states.TaskState.FINISHED
|
||||
pass
|
||||
|
||||
def _wait(self) -> types.states.TaskState:
|
||||
def _wait(self) -> None:
|
||||
"""
|
||||
Executes opWait, it simply waits something "external" to end
|
||||
"""
|
||||
return types.states.TaskState.RUNNING
|
||||
pass
|
||||
|
||||
def _create(self) -> str:
|
||||
def _create(self) -> None:
|
||||
"""
|
||||
Deploys a machine from template for user/cache
|
||||
"""
|
||||
@ -320,9 +338,11 @@ class OpenStackLiveDeployment(
|
||||
if not self._vmid:
|
||||
raise Exception('Can\'t create machine')
|
||||
|
||||
return types.states.TaskState.RUNNING
|
||||
self._reset_check_count()
|
||||
|
||||
def _remove(self) -> str:
|
||||
return None
|
||||
|
||||
def _remove(self) -> None:
|
||||
"""
|
||||
Removes a machine from system
|
||||
"""
|
||||
@ -333,22 +353,40 @@ class OpenStackLiveDeployment(
|
||||
|
||||
self.service().delete_machine(self._vmid)
|
||||
|
||||
return types.states.TaskState.RUNNING
|
||||
|
||||
def _start_machine(self) -> str:
|
||||
def _start_machine(self) -> None:
|
||||
"""
|
||||
Powers on the machine
|
||||
"""
|
||||
self.service().start_machine(self._vmid)
|
||||
|
||||
return types.states.TaskState.RUNNING
|
||||
self._reset_check_count()
|
||||
|
||||
def _suspend_machine(self) -> str:
|
||||
def _stop_machine(self) -> None:
|
||||
"""
|
||||
Powers off the machine
|
||||
"""
|
||||
self.service().stop_machine(self._vmid)
|
||||
|
||||
self._reset_check_count()
|
||||
|
||||
def _suspend_machine(self) -> None:
|
||||
"""
|
||||
Suspends the machine
|
||||
"""
|
||||
self.service().suspend_machine(self._vmid)
|
||||
|
||||
self._reset_check_count()
|
||||
|
||||
def _check_retry(self) -> types.states.TaskState:
|
||||
"""
|
||||
This method is invoked when a task has been retried.
|
||||
"""
|
||||
return types.states.TaskState.FINISHED
|
||||
|
||||
def _check_wait(self) -> types.states.TaskState:
|
||||
"""
|
||||
This method is invoked when a task is waiting for something.
|
||||
"""
|
||||
return types.states.TaskState.RUNNING
|
||||
|
||||
# Check methods
|
||||
@ -356,8 +394,11 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
Checks the state of a deploy for an user or cache
|
||||
"""
|
||||
# Checks if machine has been created
|
||||
ret = self._check_machine_power_state(openstack_types.PowerState.RUNNING)
|
||||
if ret == types.states.TaskState.FINISHED:
|
||||
# If machine is requested to not be removed never, we may end with
|
||||
# an empty mac and ip, but no problem. Next time we will get it
|
||||
# Get IP & MAC (early stage)
|
||||
addr = self.service().get_server_address(self._vmid)
|
||||
self._mac, self._ip = addr.mac, addr.ip
|
||||
@ -370,6 +411,16 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
return self._check_machine_power_state(openstack_types.PowerState.RUNNING)
|
||||
|
||||
def _check_stop(self) -> types.states.TaskState:
|
||||
"""
|
||||
Checks if machine has stopped
|
||||
"""
|
||||
return self._check_machine_power_state(
|
||||
openstack_types.PowerState.SHUTDOWN,
|
||||
openstack_types.PowerState.CRASHED,
|
||||
openstack_types.PowerState.SUSPENDED,
|
||||
)
|
||||
|
||||
def _check_suspend(self) -> types.states.TaskState:
|
||||
"""
|
||||
Check if the machine has suspended
|
||||
@ -395,20 +446,11 @@ class OpenStackLiveDeployment(
|
||||
if op == Operation.FINISH:
|
||||
return types.states.TaskState.FINISHED
|
||||
|
||||
fncs: dict[int, collections.abc.Callable[[], types.states.TaskState]] = {
|
||||
Operation.CREATE: self._check_create,
|
||||
Operation.RETRY: self._retry,
|
||||
Operation.WAIT: self._wait,
|
||||
Operation.START: self._check_start,
|
||||
Operation.SUSPEND: self._check_suspend,
|
||||
Operation.REMOVE: self._check_removed,
|
||||
}
|
||||
|
||||
try:
|
||||
if op not in fncs:
|
||||
if op not in _CHECK_FNCS:
|
||||
return self._error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
|
||||
state = fncs[op]()
|
||||
state = _CHECK_FNCS[op](self)
|
||||
if state == types.states.TaskState.FINISHED:
|
||||
self._pop_current_op() # Remove runing op
|
||||
return self._execute_queue()
|
||||
@ -426,7 +468,7 @@ class OpenStackLiveDeployment(
|
||||
|
||||
if level == self.L1_CACHE:
|
||||
self._queue = [Operation.START, Operation.FINISH]
|
||||
else:
|
||||
else: # Currently L2 is not supported
|
||||
self._queue = [Operation.START, Operation.SUSPEND, Operation.FINISH]
|
||||
|
||||
return self._execute_queue()
|
||||
@ -446,11 +488,15 @@ class OpenStackLiveDeployment(
|
||||
if op == Operation.ERROR:
|
||||
return self._error('Machine is already in error state!')
|
||||
|
||||
if op == Operation.FINISH or op == Operation.WAIT:
|
||||
self._queue = [Operation.REMOVE, Operation.FINISH]
|
||||
return self._execute_queue()
|
||||
ops = [Operation.STOP, Operation.REMOVE, Operation.FINISH]
|
||||
|
||||
if op == Operation.FINISH or op == Operation.WAIT:
|
||||
self._queue = ops
|
||||
return self._execute_queue() # Run it right now
|
||||
|
||||
# If an operation is pending, maybe checking, so we will wait until it finishes
|
||||
self._queue = [op] + ops
|
||||
|
||||
self._queue = [op, Operation.REMOVE, Operation.FINISH]
|
||||
# Do not execute anything.here, just continue normally
|
||||
return types.states.TaskState.RUNNING
|
||||
|
||||
@ -487,5 +533,28 @@ class OpenStackLiveDeployment(
|
||||
self._ip,
|
||||
self._mac,
|
||||
self._vmid,
|
||||
[OpenStackLiveDeployment._op2str(op) for op in self._queue],
|
||||
[OpenStackLiveUserService._op2str(op) for op in self._queue],
|
||||
)
|
||||
|
||||
|
||||
# Execution methods
|
||||
_EXECUTE_FNCS: dict[int, collections.abc.Callable[[OpenStackLiveUserService], None]] = {
|
||||
Operation.CREATE: OpenStackLiveUserService._create,
|
||||
Operation.RETRY: OpenStackLiveUserService._retry,
|
||||
Operation.START: OpenStackLiveUserService._start_machine,
|
||||
Operation.STOP: OpenStackLiveUserService._stop_machine,
|
||||
Operation.SUSPEND: OpenStackLiveUserService._suspend_machine,
|
||||
Operation.WAIT: OpenStackLiveUserService._wait,
|
||||
Operation.REMOVE: OpenStackLiveUserService._remove,
|
||||
}
|
||||
|
||||
# Check methods
|
||||
_CHECK_FNCS: dict[int, collections.abc.Callable[[OpenStackLiveUserService], types.states.TaskState]] = {
|
||||
Operation.CREATE: OpenStackLiveUserService._check_create,
|
||||
Operation.RETRY: OpenStackLiveUserService._check_retry,
|
||||
Operation.WAIT: OpenStackLiveUserService._check_wait,
|
||||
Operation.START: OpenStackLiveUserService._check_start,
|
||||
Operation.STOP: OpenStackLiveUserService._check_stop,
|
||||
Operation.SUSPEND: OpenStackLiveUserService._check_suspend,
|
||||
Operation.REMOVE: OpenStackLiveUserService._check_removed,
|
||||
}
|
||||
|
@ -558,6 +558,10 @@ class OpenstackClient: # pylint: disable=too-many-public-methods
|
||||
)
|
||||
]
|
||||
|
||||
# Very small timeout, so repeated operations will use same data
|
||||
# Any cache time less than 5 seconds will be fine, beceuse checks on
|
||||
# openstack are done every 5 seconds
|
||||
@decorators.cached(prefix='svr', timeout=3, key_helper=cache_key_helper)
|
||||
def get_server(self, server_id: str) -> openstack_types.ServerInfo:
|
||||
r = self._request_from_endpoint(
|
||||
'get',
|
||||
@ -771,14 +775,15 @@ class OpenstackClient: # pylint: disable=too-many-public-methods
|
||||
expects_json=False,
|
||||
)
|
||||
|
||||
def reset_server(self, server_id: str) -> None:
|
||||
def reset_server(self, server_id: str, hard: bool = True) -> None:
|
||||
# Does not need return value
|
||||
try:
|
||||
type_reboot = 'HARD' if hard else 'SOFT'
|
||||
self._request_from_endpoint(
|
||||
'post',
|
||||
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
||||
path=f'/servers/{server_id}/action',
|
||||
data='{"reboot":{"type":"HARD"}}',
|
||||
data='{"reboot":{"type":"' + type_reboot + '"}}',
|
||||
error_message='Resetting server',
|
||||
expects_json=False,
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ from uds.core.util import fields, validators
|
||||
from uds.core.ui import gui
|
||||
|
||||
from .publication import OpenStackLivePublication
|
||||
from .deployment import OpenStackLiveDeployment
|
||||
from .deployment import OpenStackLiveUserService
|
||||
from .openstack import types as openstack_types, openstack_client
|
||||
from . import helpers
|
||||
|
||||
@ -97,7 +97,7 @@ class OpenStackLiveService(services.Service):
|
||||
# : In our case, we do no need a publication, so this is None
|
||||
publication_type = OpenStackLivePublication
|
||||
# : Types of deploys (services in cache and/or assigned to users)
|
||||
user_service_type = OpenStackLiveDeployment
|
||||
user_service_type = OpenStackLiveUserService
|
||||
|
||||
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
||||
services_type_provided = types.services.ServiceType.VDI
|
||||
@ -175,11 +175,14 @@ class OpenStackLiveService(services.Service):
|
||||
basename = fields.basename_field(order=9, tab=_('Machine'))
|
||||
lenname = fields.lenname_field(order=10, tab=_('Machine'))
|
||||
|
||||
maintain_on_error = fields.maintain_on_error_field(order=104)
|
||||
maintain_on_error = fields.maintain_on_error_field(order=11, tab=_('Machine'))
|
||||
|
||||
prov_uuid = gui.HiddenField()
|
||||
|
||||
_api: typing.Optional['openstack_client.OpenstackClient'] = None
|
||||
|
||||
# Note: currently, Openstack does not provides a way of specifying how to stop the server
|
||||
# At least, i have not found it on the documentation
|
||||
|
||||
def initialize(self, values: types.core.ValuesType) -> None:
|
||||
"""
|
||||
@ -305,6 +308,10 @@ class OpenStackLiveService(services.Service):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
# if already running, do nothing
|
||||
if self.get_machine_power_state(machineId) == openstack_types.PowerState.RUNNING:
|
||||
return
|
||||
|
||||
self.api.start_server(machineId)
|
||||
|
||||
def stop_machine(self, machineId: str) -> None:
|
||||
@ -316,6 +323,10 @@ class OpenStackLiveService(services.Service):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
# If already stopped, do nothing
|
||||
if self.get_machine_power_state(machineId) == openstack_types.PowerState.SHUTDOWN:
|
||||
return
|
||||
|
||||
self.api.stop_server(machineId)
|
||||
|
||||
def reset_machine(self, machineId: str) -> None:
|
||||
@ -338,6 +349,9 @@ class OpenStackLiveService(services.Service):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
# If not running, do nothing
|
||||
if self.get_machine_power_state(machineId) != openstack_types.PowerState.RUNNING:
|
||||
return
|
||||
self.api.suspend_server(machineId)
|
||||
|
||||
def resume_machine(self, machineid: str) -> None:
|
||||
@ -349,6 +363,9 @@ class OpenStackLiveService(services.Service):
|
||||
|
||||
Returns:
|
||||
"""
|
||||
# If not suspended, do nothing
|
||||
if self.get_machine_power_state(machineid) != openstack_types.PowerState.SUSPENDED:
|
||||
return
|
||||
self.api.resume_server(machineid)
|
||||
|
||||
def delete_machine(self, machine_id: str) -> None:
|
||||
|
@ -84,6 +84,12 @@ class StorageTest(UDSTestCase):
|
||||
self.assertEqual(d[UNICODE_CHARS], 'chars')
|
||||
|
||||
self.assertEqual(d['test_key'], UNICODE_CHARS_2)
|
||||
|
||||
# Assert that UNICODE_CHARS is in the dict
|
||||
d['test_key2'] = 0
|
||||
d['test_key2'] += 1
|
||||
|
||||
self.assertEqual(d['test_key2'], 1)
|
||||
|
||||
# The values set inside the "with" are not available "outside"
|
||||
# because the format is not compatible (with the dict, the values are stored as a tuple, with the original key stored
|
||||
|
@ -43,7 +43,7 @@ from uds.core.ui.user_interface import gui
|
||||
|
||||
from ...utils.autospec import autospec, AutoSpecMethodInfo
|
||||
|
||||
from uds.services.OpenStack import provider, provider_legacy, service
|
||||
from uds.services.OpenStack import provider, provider_legacy, service, publication, deployment
|
||||
from uds.services.OpenStack.openstack import openstack_client, types as openstack_types
|
||||
|
||||
AnyOpenStackProvider: typing.TypeAlias = typing.Union[provider.OpenStackProvider, provider_legacy.OpenStackProviderLegacy]
|
||||
@ -395,7 +395,7 @@ def create_provider_legacy(**kwargs: typing.Any) -> provider_legacy.OpenStackPro
|
||||
)
|
||||
|
||||
|
||||
def create_service(
|
||||
def create_live_service(
|
||||
provider: AnyOpenStackProvider, **kwargs: typing.Any
|
||||
) -> service.OpenStackLiveService:
|
||||
"""
|
||||
@ -411,3 +411,31 @@ def create_service(
|
||||
values=values,
|
||||
uuid=uuid_,
|
||||
)
|
||||
|
||||
def create_publication(service: service.OpenStackLiveService) -> publication.OpenStackLivePublication:
|
||||
"""
|
||||
Create a publication
|
||||
"""
|
||||
uuid_ = str(uuid.uuid4())
|
||||
return publication.OpenStackLivePublication(
|
||||
environment=environment.Environment.private_environment(uuid_),
|
||||
service=service,
|
||||
revision=1,
|
||||
servicepool_name='servicepool_name',
|
||||
uuid=uuid_,
|
||||
)
|
||||
|
||||
def create_live_userservice(
|
||||
service: service.OpenStackLiveService,
|
||||
publication: typing.Optional[publication.OpenStackLivePublication] = None,
|
||||
) -> deployment.OpenStackLiveUserService:
|
||||
"""
|
||||
Create a linked user service
|
||||
"""
|
||||
uuid_ = str(uuid.uuid4())
|
||||
return deployment.OpenStackLiveUserService(
|
||||
environment=environment.Environment.private_environment(uuid_),
|
||||
service=service,
|
||||
publication=publication or create_publication(service),
|
||||
uuid=uuid_,
|
||||
)
|
||||
|
@ -58,6 +58,7 @@ EXPECTED_FIELDS: typing.Final[set[str]] = {
|
||||
'_vmid',
|
||||
'_reason',
|
||||
'_queue',
|
||||
'_check_count'
|
||||
}
|
||||
|
||||
TEST_QUEUE: typing.Final[list[deployment.Operation]] = [
|
||||
@ -74,7 +75,7 @@ LAST_VERSION: typing.Final[str] = sorted(SERIALIZED_DEPLOYMENT_DATA.keys(), reve
|
||||
|
||||
|
||||
class OpenStackDeploymentSerializationTest(UDSTransactionTestCase):
|
||||
def check(self, version: str, instance: deployment.OpenStackLiveDeployment) -> None:
|
||||
def check(self, version: str, instance: deployment.OpenStackLiveUserService) -> None:
|
||||
self.assertEqual(instance._name, 'name')
|
||||
self.assertEqual(instance._ip, 'ip')
|
||||
self.assertEqual(instance._mac, 'mac')
|
||||
@ -86,8 +87,8 @@ class OpenStackDeploymentSerializationTest(UDSTransactionTestCase):
|
||||
# queue is kept on "storage", so we need always same environment
|
||||
environment = Environment.testing_environment()
|
||||
|
||||
def _create_instance(unmarshal_data: 'bytes|None' = None) -> deployment.OpenStackLiveDeployment:
|
||||
instance = deployment.OpenStackLiveDeployment(environment=environment, service=None) # type: ignore
|
||||
def _create_instance(unmarshal_data: 'bytes|None' = None) -> deployment.OpenStackLiveUserService:
|
||||
instance = deployment.OpenStackLiveUserService(environment=environment, service=None) # type: ignore
|
||||
if unmarshal_data:
|
||||
instance.unmarshal(unmarshal_data)
|
||||
return instance
|
||||
@ -117,8 +118,8 @@ class OpenStackDeploymentSerializationTest(UDSTransactionTestCase):
|
||||
# Store queue
|
||||
environment.storage.put_pickle('queue', TEST_QUEUE)
|
||||
|
||||
def _create_instance(unmarshal_data: 'bytes|None' = None) -> deployment.OpenStackLiveDeployment:
|
||||
instance = deployment.OpenStackLiveDeployment(environment=environment, service=None) # type: ignore
|
||||
def _create_instance(unmarshal_data: 'bytes|None' = None) -> deployment.OpenStackLiveUserService:
|
||||
instance = deployment.OpenStackLiveUserService(environment=environment, service=None) # type: ignore
|
||||
if unmarshal_data:
|
||||
instance.unmarshal(unmarshal_data)
|
||||
return instance
|
||||
@ -162,6 +163,6 @@ class OpenStackDeploymentSerializationTest(UDSTransactionTestCase):
|
||||
# This test is designed to ensure that all fields are autoserializable
|
||||
# If some field is added or removed, this tests will warn us about it to fix the rest of the related tests
|
||||
with Environment.temporary_environment() as env:
|
||||
instance = deployment.OpenStackLiveDeployment(environment=env, service=None) # type: ignore
|
||||
instance = deployment.OpenStackLiveUserService(environment=env, service=None) # type: ignore
|
||||
|
||||
self.assertSetEqual(set(f[0] for f in instance._autoserializable_fields()), EXPECTED_FIELDS)
|
||||
|
@ -51,7 +51,7 @@ class TestOpenstackService(UDSTransactionTestCase):
|
||||
"""
|
||||
for prov in (fixtures.create_provider_legacy(), fixtures.create_provider()):
|
||||
with fixtures.patch_provider_api(legacy=prov.legacy) as client:
|
||||
service = fixtures.create_service(prov) # Will use provider patched api
|
||||
service = fixtures.create_live_service(prov) # Will use provider patched api
|
||||
self.assertEqual(service.api, client)
|
||||
self.assertEqual(service.sanitized_name('a b c'), 'a_b_c')
|
||||
|
||||
@ -83,24 +83,29 @@ class TestOpenstackService(UDSTransactionTestCase):
|
||||
self.assertIsInstance(data, openstack_types.PowerState)
|
||||
client.get_server.assert_called_once_with(fixtures.SERVERS_LIST[0].id)
|
||||
|
||||
server_id = fixtures.SERVERS_LIST[0].id
|
||||
service.start_machine(server_id)
|
||||
client.start_server.assert_called_once_with(server_id)
|
||||
server = fixtures.SERVERS_LIST[0]
|
||||
service.start_machine(server.id)
|
||||
|
||||
server.power_state = openstack_types.PowerState.SHUTDOWN
|
||||
client.start_server.assert_called_once_with(server.id)
|
||||
|
||||
service.stop_machine(server_id)
|
||||
client.stop_server.assert_called_once_with(server_id)
|
||||
server.power_state = openstack_types.PowerState.RUNNING
|
||||
service.stop_machine(server.id)
|
||||
client.stop_server.assert_called_once_with(server.id)
|
||||
|
||||
service.suspend_machine(server_id)
|
||||
client.suspend_server.assert_called_once_with(server_id)
|
||||
server.power_state = openstack_types.PowerState.RUNNING
|
||||
service.suspend_machine(server.id)
|
||||
client.suspend_server.assert_called_once_with(server.id)
|
||||
|
||||
service.resume_machine(server_id)
|
||||
client.resume_server.assert_called_once_with(server_id)
|
||||
server.power_state = openstack_types.PowerState.SUSPENDED
|
||||
service.resume_machine(server.id)
|
||||
client.resume_server.assert_called_once_with(server.id)
|
||||
|
||||
service.reset_machine(server_id)
|
||||
client.reset_server.assert_called_once_with(server_id)
|
||||
service.reset_machine(server.id)
|
||||
client.reset_server.assert_called_once_with(server.id)
|
||||
|
||||
service.delete_machine(server_id)
|
||||
client.delete_server.assert_called_once_with(server_id)
|
||||
service.delete_machine(server.id)
|
||||
client.delete_server.assert_called_once_with(server.id)
|
||||
|
||||
self.assertTrue(service.is_avaliable())
|
||||
client.is_available.assert_called_once_with()
|
||||
|
174
server/tests/services/openstack/test_userservice.py
Normal file
174
server/tests/services/openstack/test_userservice.py
Normal file
@ -0,0 +1,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2024 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
|
||||
"""
|
||||
from uds import models
|
||||
from uds.core import types
|
||||
|
||||
from uds.services.OpenStack.openstack import types as openstack_types
|
||||
from uds.services.OpenStack.deployment import Operation
|
||||
|
||||
from . import fixtures
|
||||
|
||||
from ...utils.test import UDSTransactionTestCase
|
||||
from ...utils.generators import limited_iterator
|
||||
|
||||
|
||||
# We use transactions on some related methods (storage access, etc...)
|
||||
class TestOpenstackLiveDeployment(UDSTransactionTestCase):
|
||||
def setUp(self) -> None:
|
||||
pass
|
||||
|
||||
# Openstack only have l1 cache. L2 is not considered useful right now
|
||||
def test_userservice_cachel1_and_user(self) -> None:
|
||||
"""
|
||||
Test the user service
|
||||
"""
|
||||
# Deploy for cache and deploy for user are the same, so we will test both at the same time
|
||||
for to_test in ['cache', 'user']:
|
||||
for prov in (fixtures.create_provider_legacy(), fixtures.create_provider()):
|
||||
with fixtures.patch_provider_api(legacy=prov.legacy) as api:
|
||||
service = fixtures.create_live_service(prov)
|
||||
userservice = fixtures.create_live_userservice(service=service)
|
||||
publication = userservice.publication()
|
||||
publication._template_id = 'snap1'
|
||||
|
||||
if to_test == 'cache':
|
||||
state = userservice.deploy_for_cache(level=1)
|
||||
else:
|
||||
state = userservice.deploy_for_user(models.User())
|
||||
|
||||
self.assertEqual(state, types.states.TaskState.RUNNING, f'Error on {to_test} deployment')
|
||||
|
||||
# Create server should have been called
|
||||
api.create_server_from_snapshot.assert_called_with(
|
||||
snapshot_id='snap1',
|
||||
name=userservice._name,
|
||||
availability_zone=service.availability_zone.value,
|
||||
flavor_id=service.flavor.value,
|
||||
network_id=service.network.value,
|
||||
security_groups_ids=service.security_groups.value,
|
||||
)
|
||||
|
||||
vmid = userservice._vmid
|
||||
|
||||
# Set power state of machine to running (userservice._vmid)
|
||||
fixtures.get_id(fixtures.SERVERS_LIST, vmid).power_state = (
|
||||
openstack_types.PowerState.RUNNING
|
||||
)
|
||||
|
||||
# Ensure that in the event of failure, we don't loop forever
|
||||
for _ in limited_iterator(lambda: state == types.states.TaskState.RUNNING, limit=128):
|
||||
state = userservice.check_state()
|
||||
|
||||
self.assertEqual(state, types.states.TaskState.FINISHED, f'Error on {to_test} deployment')
|
||||
|
||||
self.assertEqual(
|
||||
userservice._name[: len(service.get_basename())],
|
||||
service.get_basename(),
|
||||
f'Error on {to_test} deployment',
|
||||
)
|
||||
self.assertEqual(
|
||||
len(userservice._name),
|
||||
len(service.get_basename()) + service.get_lenname(),
|
||||
f'Error on {to_test} deployment',
|
||||
)
|
||||
|
||||
# Get server should have been called at least once
|
||||
api.get_server.assert_called_with(vmid)
|
||||
|
||||
# Mac an ip should have been set
|
||||
self.assertNotEqual(userservice._mac, '', f'Error on {to_test} deployment')
|
||||
self.assertNotEqual(userservice._ip, '', f'Error on {to_test} deployment')
|
||||
|
||||
# And queue must be finished
|
||||
self.assertEqual(userservice._queue, [Operation.FINISH], f'Error on {to_test} deployment')
|
||||
|
||||
def test_userservice_cancel(self) -> None:
|
||||
"""
|
||||
Test the user service
|
||||
"""
|
||||
for prov in (fixtures.create_provider_legacy(), fixtures.create_provider()):
|
||||
with fixtures.patch_provider_api(legacy=prov.legacy) as _api:
|
||||
service = fixtures.create_live_service(prov)
|
||||
userservice = fixtures.create_live_userservice(service=service)
|
||||
publication = userservice.publication()
|
||||
publication._template_id = 'snap1'
|
||||
|
||||
state = userservice.deploy_for_user(models.User())
|
||||
|
||||
self.assertEqual(state, types.states.TaskState.RUNNING)
|
||||
|
||||
server = fixtures.get_id(fixtures.SERVERS_LIST, userservice._vmid)
|
||||
server.power_state = openstack_types.PowerState.RUNNING
|
||||
|
||||
current_op = userservice._get_current_op()
|
||||
|
||||
# Invoke cancel
|
||||
state = userservice.cancel()
|
||||
|
||||
self.assertEqual(state, types.states.TaskState.RUNNING)
|
||||
|
||||
self.assertEqual(
|
||||
userservice._queue,
|
||||
[current_op] + [Operation.STOP, Operation.REMOVE, Operation.FINISH],
|
||||
)
|
||||
|
||||
counter = 0
|
||||
for counter in limited_iterator(lambda: state == types.states.TaskState.RUNNING, limit=128):
|
||||
state = userservice.check_state()
|
||||
if counter > 5:
|
||||
server.power_state = openstack_types.PowerState.SHUTDOWN
|
||||
|
||||
self.assertGreater(counter, 5)
|
||||
self.assertEqual(state, types.states.TaskState.FINISHED)
|
||||
|
||||
def test_userservice_error(self) -> None:
|
||||
"""
|
||||
This test will not have keep on error active, and will create correctly
|
||||
but will error on set_ready, so it will be put on error state
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_userservice_error_keep_create(self) -> None:
|
||||
"""
|
||||
This test will have keep on error active, and will create incorrectly
|
||||
so vm will be deleted and put on error state
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_userservice_error_keep(self) -> None:
|
||||
"""
|
||||
This test will have keep on error active, and will create correctly
|
||||
but error will came later (on set_ready) and will not be put on error state
|
||||
nor deleted
|
||||
"""
|
||||
pass
|
Loading…
x
Reference in New Issue
Block a user