mirror of
https://github.com/dkmstr/openuds.git
synced 2025-02-03 13:47:14 +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)
|
||||
|
||||
def process(classes: collections.abc.Iterable[type[V]]) -> None:
|
||||
def _process(classes: collections.abc.Iterable[type[V]]) -> None:
|
||||
cls: type[V]
|
||||
for cls in classes:
|
||||
clsSubCls = cls.__subclasses__()
|
||||
|
||||
if clsSubCls:
|
||||
process(clsSubCls) # recursive add sub classes
|
||||
_process(clsSubCls) # recursive add sub classes
|
||||
|
||||
if not check_function(cls):
|
||||
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.info('* Start registering %s', module_name)
|
||||
process(type_.__subclasses__())
|
||||
_process(type_.__subclasses__())
|
||||
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
|
||||
'''
|
||||
|
||||
def checker(cls: type[T]) -> bool:
|
||||
def _checker(cls: type[T]) -> bool:
|
||||
# Will receive all classes that are subclasses of type_
|
||||
return not cls.is_base
|
||||
|
||||
@ -195,5 +195,5 @@ def dynamically_load_and_register_modules(
|
||||
factory.insert,
|
||||
type_,
|
||||
module_name,
|
||||
checker=checker,
|
||||
checker=_checker,
|
||||
)
|
||||
|
@ -33,8 +33,6 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
# pyright: reportUnusedImport=false
|
||||
import os.path
|
||||
import typing
|
||||
import sys
|
||||
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from uds.core import managers
|
||||
|
@ -39,7 +39,7 @@ import typing
|
||||
from uds.core import consts, services, types
|
||||
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
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -155,12 +155,14 @@ class OpenStackLiveDeployment(
|
||||
try:
|
||||
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')
|
||||
|
||||
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)
|
||||
elif status in (openstack.STOPPED, openstack.SHUTOFF):
|
||||
elif power_state.is_stopped():
|
||||
self.service().start_machine(self._vmid)
|
||||
|
||||
# Right now, we suppose the machine is ready
|
||||
@ -207,26 +209,22 @@ class OpenStackLiveDeployment(
|
||||
else:
|
||||
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(
|
||||
'Checking that state of machine %s (%s) is %s',
|
||||
self._vmid,
|
||||
self._name,
|
||||
chkState,
|
||||
check_state,
|
||||
)
|
||||
status = self.service().get_machine_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))
|
||||
|
||||
power_state = self.service().get_machine_power_state(self._vmid)
|
||||
|
||||
ret = types.states.TaskState.RUNNING
|
||||
chkStates = [chkState] if not isinstance(chkState, (list, tuple)) else chkState
|
||||
if status in chkStates:
|
||||
if power_state == check_state:
|
||||
ret = types.states.TaskState.FINISHED
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _get_current_op(self) -> Operation:
|
||||
if not self._queue:
|
||||
return Operation.FINISH
|
||||
@ -252,7 +250,7 @@ class OpenStackLiveDeployment(
|
||||
|
||||
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:
|
||||
self.service().remove_machine(self._vmid)
|
||||
except Exception:
|
||||
@ -333,7 +331,7 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
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))
|
||||
|
||||
self.service().remove_machine(self._vmid)
|
||||
@ -361,7 +359,7 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
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:
|
||||
# Get IP & MAC (early stage)
|
||||
self._mac, self._ip = self.service().get_network_info(self._vmid)
|
||||
@ -372,13 +370,13 @@ class OpenStackLiveDeployment(
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
|
@ -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()]
|
||||
networks = [
|
||||
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()
|
||||
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, 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 = [
|
||||
{'name': 'availabilityZone', 'choices': zones},
|
||||
{'name': 'availability_zone', 'choices': zones},
|
||||
{'name': 'network', 'choices': networks},
|
||||
{'name': 'flavor', 'choices': flavors},
|
||||
{'name': 'securityGroups', 'choices': securityGroups},
|
||||
{'name': 'volumeType', 'choices': volumeTypes},
|
||||
{'name': 'security_groups', 'choices': security_groups},
|
||||
]
|
||||
logger.debug('Return data: %s', data)
|
||||
return data
|
||||
|
@ -30,12 +30,10 @@
|
||||
"""
|
||||
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
# pyright: reportUnusedImport=false
|
||||
import re
|
||||
|
||||
from .openstack_client import Client
|
||||
|
||||
# Import submodules
|
||||
from .common import *
|
||||
|
||||
# Logger imported from .common, if you ask
|
||||
logger = logging.getLogger(__name__)
|
||||
def sanitized_name(name: str) -> str:
|
||||
"""
|
||||
machine names with [a-zA-Z0-9_-]
|
||||
"""
|
||||
return re.sub("[^a-zA-Z0-9._-]", "_", 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',
|
||||
] # 'volume' is also valid, but it is deprecated A LONG TYPE AGO
|
||||
COMPUTE_ENDPOINT_TYPES = ['compute', 'compute_legacy']
|
||||
NETWORKS_ENDPOINT_TYPES = ['network']
|
||||
|
||||
|
||||
# Helpers
|
||||
@ -122,7 +123,7 @@ def auth_required(for_project: bool = False) -> collections.abc.Callable[[decora
|
||||
|
||||
def decorator(func: decorators.FT) -> decorators.FT:
|
||||
@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 obj._projectid is None:
|
||||
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
|
||||
|
||||
|
||||
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(
|
||||
[
|
||||
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'
|
||||
PRIVATE = 'private'
|
||||
INTERNAL = 'url'
|
||||
@ -200,7 +201,7 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
self._catalog = None
|
||||
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._userid = None
|
||||
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)
|
||||
def list_volumes(self) -> list[openstack_types.VolumeInfo]:
|
||||
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(
|
||||
self, volume_id: typing.Optional[dict[str, typing.Any]] = None
|
||||
) -> 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
|
||||
]
|
||||
|
||||
# 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)
|
||||
def list_availability_zones(self) -> list[openstack_types.AvailabilityZoneInfo]:
|
||||
return [
|
||||
@ -551,155 +520,97 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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)
|
||||
def list_flavors(self) -> list[typing.Any]:
|
||||
return list(
|
||||
self._get_recurring_from_endpoint(
|
||||
def list_flavors(self) -> list[openstack_types.FlavorInfo]:
|
||||
return [
|
||||
openstack_types.FlavorInfo.from_dict(f)
|
||||
for f in self._get_recurring_from_endpoint(
|
||||
endpoint_types=COMPUTE_ENDPOINT_TYPES,
|
||||
path='/flavors',
|
||||
path='/flavors/detail',
|
||||
error_message='List 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)
|
||||
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(
|
||||
endpoint_types=['network'],
|
||||
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||
path='/v2.0/networks',
|
||||
error_message='List 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:
|
||||
return list(nets)
|
||||
return [openstack_types.NetworkInfo.from_dict(n) for n in nets]
|
||||
else:
|
||||
# Get and cache subnets names
|
||||
subnetNames = {s['id']: s['name'] for s in self.list_subnets()}
|
||||
res: list[typing.Any] = []
|
||||
subnets_dct = {s.id: f'{s.name} ({s.cidr})' for s in self.list_subnets()}
|
||||
res: list[openstack_types.NetworkInfo] = []
|
||||
for net in nets:
|
||||
name = ','.join(subnetNames[i] for i in net['subnets'] if i in subnetNames)
|
||||
net['old_name'] = net['name']
|
||||
name = ','.join(subnets_dct[i] for i in net['subnets'] if i in subnets_dct)
|
||||
if name:
|
||||
net['name'] = name
|
||||
|
||||
res.append(net)
|
||||
res.append(openstack_types.NetworkInfo.from_dict(net))
|
||||
|
||||
return res
|
||||
|
||||
@decorators.cached(prefix='subns', timeout=consts.cache.LONG_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||
def list_subnets(self) -> collections.abc.Iterable[typing.Any]:
|
||||
return list(
|
||||
self._get_recurring_from_endpoint(
|
||||
endpoint_types=['network'],
|
||||
def list_subnets(self) -> collections.abc.Iterable[openstack_types.SubnetInfo]:
|
||||
return [
|
||||
openstack_types.SubnetInfo.from_dict(s)
|
||||
for s in self._get_recurring_from_endpoint(
|
||||
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||
path='/v2.0/subnets',
|
||||
error_message='List 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)
|
||||
def list_ports(
|
||||
self,
|
||||
networkId: typing.Optional[str] = None,
|
||||
ownerId: typing.Optional[str] = None,
|
||||
) -> list[typing.Any]:
|
||||
network_id: typing.Optional[str] = None,
|
||||
owner_id: typing.Optional[str] = None,
|
||||
) -> list[openstack_types.PortInfo]:
|
||||
params: dict[str, typing.Any] = {}
|
||||
if networkId is not None:
|
||||
params['network_id'] = networkId
|
||||
if ownerId is not None:
|
||||
params['device_owner'] = ownerId
|
||||
if network_id is not None:
|
||||
params['network_id'] = network_id
|
||||
if owner_id is not None:
|
||||
params['device_owner'] = owner_id
|
||||
|
||||
return list(
|
||||
self._get_recurring_from_endpoint(
|
||||
endpoint_types=['network'],
|
||||
return [
|
||||
openstack_types.PortInfo.from_dict(p)
|
||||
for p in self._get_recurring_from_endpoint(
|
||||
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||
path='/v2.0/ports',
|
||||
error_message='List ports',
|
||||
key='ports',
|
||||
params=params,
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
# TODO: Remove this
|
||||
# return get_recurring_url_json(
|
||||
# self._get_endpoint_for('network') + '/v2.0/ports',
|
||||
# self._session,
|
||||
# headers=self._get_request_headers(),
|
||||
# key='ports',
|
||||
# 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'],
|
||||
@decorators.cached(prefix='sgps', timeout=consts.cache.EXTREME_CACHE_TIMEOUT, key_helper=cache_key_helper)
|
||||
def list_security_groups(self) -> list[openstack_types.SecurityGroupInfo]:
|
||||
return [
|
||||
openstack_types.SecurityGroupInfo.from_dict(sg)
|
||||
for sg in self._get_recurring_from_endpoint(
|
||||
endpoint_types=NETWORKS_ENDPOINT_TYPES,
|
||||
path='/v2.0/security-groups',
|
||||
error_message='List security groups',
|
||||
key='security_groups',
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
# TODO: Remove this
|
||||
# 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]:
|
||||
def get_server(self, server_id: str) -> openstack_types.VMInfo:
|
||||
r = self._request_from_endpoint(
|
||||
'get',
|
||||
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
||||
path=f'/servers/{server_id}',
|
||||
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]:
|
||||
r = self._request_from_endpoint(
|
||||
@ -709,15 +620,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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']
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
# 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']
|
||||
|
||||
def update_snapshot(
|
||||
@ -764,16 +657,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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']
|
||||
|
||||
def create_volume_snapshot(
|
||||
@ -799,16 +682,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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']
|
||||
|
||||
def create_volume_from_snapshot(
|
||||
@ -832,16 +705,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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']
|
||||
|
||||
def create_server_from_snapshot(
|
||||
@ -889,16 +752,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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']
|
||||
|
||||
@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).',
|
||||
)
|
||||
|
||||
# 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:
|
||||
# This does not returns anything
|
||||
self._request_from_endpoint(
|
||||
@ -936,17 +773,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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:
|
||||
# this does not returns anything
|
||||
self._request_from_endpoint(
|
||||
@ -957,16 +783,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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:
|
||||
# this does not returns anything
|
||||
self._request_from_endpoint(
|
||||
@ -977,16 +793,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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)
|
||||
def suspend_server(self, server_id: str) -> None:
|
||||
# this does not returns anything
|
||||
@ -998,16 +804,6 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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:
|
||||
# This does not returns anything
|
||||
self._request_from_endpoint(
|
||||
@ -1018,26 +814,18 @@ class Client: # pylint: disable=too-many-public-methods
|
||||
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:
|
||||
# Does not need return value
|
||||
self._session.post(
|
||||
self._get_compute_endpoint() + f'/servers/{server_id}/action',
|
||||
data='{"reboot":{"type":"HARD"}}',
|
||||
headers=self._get_request_headers(),
|
||||
timeout=self._timeout,
|
||||
)
|
||||
|
||||
# Ignore response for this...
|
||||
# ensureResponseIsValid(r, 'Reseting server')
|
||||
try:
|
||||
self._request_from_endpoint(
|
||||
'post',
|
||||
endpoints_types=COMPUTE_ENDPOINT_TYPES,
|
||||
path=f'/servers/{server_id}/action',
|
||||
data='{"reboot":{"type":"HARD"}}',
|
||||
error_message='Resetting server',
|
||||
)
|
||||
except Exception:
|
||||
pass # Ignore error for reseting server
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
# First, ensure requested api is supported
|
||||
|
@ -36,33 +36,119 @@ import dataclasses
|
||||
import enum
|
||||
|
||||
|
||||
class State(enum.StrEnum):
|
||||
ACTIVE = 'ACTIVE'
|
||||
BUILDING = 'BUILDING'
|
||||
DELETED = 'DELETED'
|
||||
ERROR = 'ERROR'
|
||||
HARD_REBOOT = 'HARD_REBOOT'
|
||||
MIGRATING = 'MIGRATING'
|
||||
PASSWORD = 'PASSWORD'
|
||||
PAUSED = 'PAUSED'
|
||||
REBOOT = 'REBOOT'
|
||||
REBUILD = 'REBUILD'
|
||||
RESCUED = 'RESCUED'
|
||||
RESIZED = 'RESIZED'
|
||||
REVERT_RESIZE = 'REVERT_RESIZE'
|
||||
SOFT_DELETED = 'SOFT_DELETED'
|
||||
STOPPED = 'STOPPED'
|
||||
SUSPENDED = 'SUSPENDED'
|
||||
UNKNOWN = 'UNKNOWN'
|
||||
VERIFY_RESIZE = 'VERIFY_RESIZE'
|
||||
SHUTOFF = 'SHUTOFF'
|
||||
class Status(enum.StrEnum):
|
||||
ACTIVE = 'ACTIVE' # The server is active.
|
||||
BUILD = 'BUILD' # The server has not finished the original build process.
|
||||
DELETED = 'DELETED' # The server is permanently deleted.
|
||||
ERROR = 'ERROR' # The server is in error.
|
||||
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' # The server is being migrated to a new host.
|
||||
PASSWORD = 'PASSWORD' # The password is being reset on the server.
|
||||
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' # The server is in a soft reboot state. A reboot command was passed to the operating system.
|
||||
)
|
||||
REBUILD = 'REBUILD' # The server is currently being rebuilt from an image.
|
||||
RESCUE = 'RESCUE' # The server is in rescue mode. A rescue image is running with the original server image attached.
|
||||
RESIZE = 'RESIZE' # Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
|
||||
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.
|
||||
SHELVED = 'SHELVED' # The server is in shelved state. Depending on the shelve offload time, the server will be automatically shelved offloaded.
|
||||
SHELVED_OFFLOADED = 'SHELVED_OFFLOADED' # The shelved server is offloaded (removed from the compute host) and it needs unshelved action to be used again.
|
||||
SHUTOFF = 'SHUTOFF' # The server is powered off and the disk image still persists.
|
||||
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
|
||||
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
|
||||
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
|
||||
def from_dict(d: dict[str, typing.Any]) -> 'VMInfo':
|
||||
@ -75,10 +161,22 @@ class VMInfo:
|
||||
break
|
||||
except Exception:
|
||||
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(
|
||||
id=d['id'],
|
||||
name=d['name'],
|
||||
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'],
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class AvailabilityZoneInfo:
|
||||
id: str
|
||||
@ -189,4 +288,124 @@ class AvailabilityZoneInfo:
|
||||
return AvailabilityZoneInfo(
|
||||
id=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.decorators import cached
|
||||
|
||||
from . import openstack
|
||||
from .openstack import openstack_client, sanitized_name
|
||||
from .service import OpenStackLiveService
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
@ -188,7 +188,7 @@ class OpenStackProvider(ServiceProvider):
|
||||
legacy = False
|
||||
|
||||
# Own variables
|
||||
_api: typing.Optional[openstack.Client] = None
|
||||
_api: typing.Optional[openstack_client.OpenstackClient] = None
|
||||
|
||||
def initialize(self, values: 'types.core.ValuesType' = None) -> None:
|
||||
"""
|
||||
@ -201,14 +201,14 @@ class OpenStackProvider(ServiceProvider):
|
||||
|
||||
def api(
|
||||
self, projectid: typing.Optional[str] = None, region: typing.Optional[str] = None
|
||||
) -> openstack.Client:
|
||||
) -> openstack_client.OpenstackClient:
|
||||
projectid = projectid or self.tenant.value or None
|
||||
region = region or self.region.value or None
|
||||
if self._api is None:
|
||||
proxies = None
|
||||
if self.https_proxy.value.strip():
|
||||
proxies = {'https': self.https_proxy.value}
|
||||
self._api = openstack.Client(
|
||||
self._api = openstack_client.OpenstackClient(
|
||||
self.endpoint.value,
|
||||
-1,
|
||||
self.domain.value,
|
||||
@ -224,7 +224,7 @@ class OpenStackProvider(ServiceProvider):
|
||||
return self._api
|
||||
|
||||
def sanitized_name(self, name: str) -> str:
|
||||
return openstack.sanitized_name(name)
|
||||
return sanitized_name(name)
|
||||
|
||||
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.decorators import cached
|
||||
|
||||
from . import openstack
|
||||
from .openstack import openstack_client, sanitized_name
|
||||
from .service import OpenStackLiveService
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
@ -179,7 +179,7 @@ class OpenStackProviderLegacy(ServiceProvider):
|
||||
legacy = True
|
||||
|
||||
# Own variables
|
||||
_api: typing.Optional[openstack.Client] = None
|
||||
_api: typing.Optional[openstack_client.OpenstackClient] = None
|
||||
|
||||
def initialize(self, values: 'types.core.ValuesType') -> None:
|
||||
"""
|
||||
@ -190,11 +190,11 @@ class OpenStackProviderLegacy(ServiceProvider):
|
||||
if values is not None:
|
||||
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
|
||||
if self.https_proxy.value.strip():
|
||||
proxies = {'https': self.https_proxy.value}
|
||||
return openstack.Client(
|
||||
return openstack_client.OpenstackClient(
|
||||
self.host.value,
|
||||
self.port.value,
|
||||
self.domain.value,
|
||||
@ -209,7 +209,7 @@ class OpenStackProviderLegacy(ServiceProvider):
|
||||
)
|
||||
|
||||
def sanitized_name(self, name: str) -> str:
|
||||
return openstack.sanitized_name(name)
|
||||
return sanitized_name(name)
|
||||
|
||||
def test_connection(self) -> types.core.TestResult:
|
||||
"""
|
||||
|
@ -36,11 +36,12 @@ import typing
|
||||
from django.utils.translation import gettext_noop as _
|
||||
|
||||
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 .publication import OpenStackLivePublication
|
||||
from .deployment import OpenStackLiveDeployment
|
||||
from .openstack import types as openstack_types, openstack_client
|
||||
from . import helpers
|
||||
|
||||
|
||||
@ -48,7 +49,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import openstack
|
||||
from .provider import OpenStackProvider
|
||||
from .provider_legacy import OpenStackProviderLegacy
|
||||
|
||||
@ -102,8 +102,6 @@ class OpenStackLiveService(services.Service):
|
||||
allowed_protocols = types.transports.Protocol.generic_vdi(types.transports.Protocol.SPICE)
|
||||
services_type_provided = types.services.ServiceType.VDI
|
||||
|
||||
|
||||
|
||||
# Now the form part
|
||||
region = gui.ChoiceField(
|
||||
label=_('Region'),
|
||||
@ -174,31 +172,16 @@ class OpenStackLiveService(services.Service):
|
||||
old_field_name='securityGroups',
|
||||
)
|
||||
|
||||
baseName = gui.TextField(
|
||||
label=_('Machine Names'),
|
||||
readonly=False,
|
||||
order=9,
|
||||
tooltip=_('Base name for clones from this machine'),
|
||||
required=True,
|
||||
tab=_('Machine'),
|
||||
)
|
||||
basename = fields.basename_field(order=9, tab=_('Machine'))
|
||||
lenname = fields.lenname_field(order=10, tab=_('Machine'))
|
||||
|
||||
lenName = gui.NumericField(
|
||||
length=1,
|
||||
label=_('Name Length'),
|
||||
default=5,
|
||||
order=10,
|
||||
tooltip=_('Size of numeric part for the names of these machines'),
|
||||
required=True,
|
||||
tab=_('Machine'),
|
||||
)
|
||||
maintain_on_error = fields.maintain_on_error_field(order=104)
|
||||
|
||||
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.
|
||||
|
||||
@ -206,7 +189,7 @@ class OpenStackLiveService(services.Service):
|
||||
initialized by __init__ method of base class, before invoking this.
|
||||
"""
|
||||
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.ev.value = self.provider().env.key
|
||||
@ -221,55 +204,45 @@ class OpenStackLiveService(services.Service):
|
||||
api = self.provider().api()
|
||||
|
||||
# Checks if legacy or current openstack provider
|
||||
parent = (
|
||||
typing.cast('OpenStackProvider', self.provider())
|
||||
if not self.provider().legacy
|
||||
else None
|
||||
)
|
||||
parent = typing.cast('OpenStackProvider', self.provider()) if not self.provider().legacy else None
|
||||
|
||||
if parent and parent.region.value:
|
||||
regions = [
|
||||
gui.choice_item(parent.region.value, parent.region.value)
|
||||
]
|
||||
regions = [gui.choice_item(parent.region.value, parent.region.value)]
|
||||
else:
|
||||
regions = [gui.choice_item(r.id, r.name) for r in api.list_regions()]
|
||||
|
||||
self.region.set_choices(regions)
|
||||
|
||||
if parent and parent.tenant.value:
|
||||
tenants = [
|
||||
gui.choice_item(parent.tenant.value, parent.tenant.value)
|
||||
]
|
||||
tenants = [gui.choice_item(parent.tenant.value, parent.tenant.value)]
|
||||
else:
|
||||
tenants = [gui.choice_item(t.id, t.name) for t in api.list_projects()]
|
||||
self.project.set_choices(tenants)
|
||||
|
||||
# So we can instantiate parent to get API
|
||||
logger.debug(self.provider().serialize())
|
||||
|
||||
|
||||
self.parent_uuid.value = self.provider().get_uuid()
|
||||
|
||||
@property
|
||||
def api(self) -> 'openstack.Client':
|
||||
def api(self) -> 'openstack_client.OpenstackClient':
|
||||
if not self._api:
|
||||
self._api = self.provider().api(
|
||||
projectid=self.project.value, region=self.region.value
|
||||
)
|
||||
self._api = self.provider().api(projectid=self.project.value, region=self.region.value)
|
||||
|
||||
return self._api
|
||||
|
||||
def sanitized_name(self, name: str) -> str:
|
||||
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
|
||||
# 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')
|
||||
|
||||
description = description or 'UDS Template snapshot'
|
||||
return self.api.create_volume_snapshot(
|
||||
self.volume.value, template_name, description
|
||||
)
|
||||
return self.api.create_volume_snapshot(self.volume.value, template_name, description)
|
||||
|
||||
def get_template(self, snapshot_id: str) -> dict[str, typing.Any]:
|
||||
"""
|
||||
@ -306,43 +279,20 @@ class OpenStackLiveService(services.Service):
|
||||
"""
|
||||
self.api.delete_snapshot(templateId)
|
||||
|
||||
def get_machine_state(self, machineId: str) -> str:
|
||||
"""
|
||||
Invokes getServer from openstack client
|
||||
|
||||
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'):
|
||||
def get_machine_state(self, machine_id: str) -> openstack_types.Status:
|
||||
vminfo = self.api.get_server(machine_id)
|
||||
if vminfo.status in (openstack_types.Status.ERROR, openstack_types.Status.DELETED):
|
||||
logger.warning(
|
||||
'Got server status %s for %s: %s',
|
||||
server['status'],
|
||||
machineId,
|
||||
server.get('fault'),
|
||||
vminfo.status,
|
||||
machine_id,
|
||||
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:
|
||||
"""
|
||||
@ -416,24 +366,26 @@ class OpenStackLiveService(services.Service):
|
||||
"""
|
||||
Gets the mac address of first nic of the machine
|
||||
"""
|
||||
net = self.api.get_server(machineid)['addresses']
|
||||
vals = next(iter(net.values()))[
|
||||
0
|
||||
] # Returns "any" mac address of any interface. We just need only one interface info
|
||||
# vals = six.next(six.itervalues(net))[0]
|
||||
return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']
|
||||
vminfo = self.api.get_server(machineid)
|
||||
return vminfo.addresses[0].addr, vminfo.addresses[0].mac.upper()
|
||||
|
||||
def get_basename(self) -> str:
|
||||
"""
|
||||
Returns the base name
|
||||
"""
|
||||
return self.baseName.value
|
||||
return self.basename.value
|
||||
|
||||
def get_lenname(self) -> int:
|
||||
"""
|
||||
Returns the length of numbers part
|
||||
"""
|
||||
return int(self.lenName.value)
|
||||
return int(self.lenname.value)
|
||||
|
||||
def is_avaliable(self) -> bool:
|
||||
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:
|
||||
try:
|
||||
errList = {
|
||||
error_list = {
|
||||
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.exHostIsSlave: 'The connected host is an slave, try to connect to {1}',
|
||||
XenFailure.exSRError: 'Error on SR: {2}',
|
||||
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))
|
||||
except Exception:
|
||||
|
Loading…
x
Reference in New Issue
Block a user