forked from shaba/openuds
fully ported openstack provider to python 3.x
This commit is contained in:
parent
75f244be2e
commit
bada5ed2af
@ -79,7 +79,7 @@ class Publication(Environmentable, Serializable):
|
||||
# : but full clone takes a lot, so we suggest that checks are done more steady.
|
||||
# : This attribute is always accessed using an instance object, so you can
|
||||
# : change suggestedTime in your implementation.
|
||||
suggestedTime = 10
|
||||
suggestedTime: int = 10
|
||||
|
||||
_osmanager: typing.Optional['osmanagers.OSManager']
|
||||
_service: 'services.Service'
|
||||
|
@ -235,7 +235,7 @@ class Service(Module):
|
||||
We will access the returned array in "name" basis. This means that the service will be assigned by "name", so be care that every single service
|
||||
returned are not repeated... :-)
|
||||
"""
|
||||
raise NotImplementedError('The class {0} has been marked as manually asignable but no requestServicesForAssignetion provided!!!'.format(self.__class__.__name__))
|
||||
raise Exception('The class {0} has been marked as manually asignable but no requestServicesForAssignetion provided!!!'.format(self.__class__.__name__))
|
||||
|
||||
def macGenerator(self) -> typing.Optional['UniqueMacGenerator']:
|
||||
"""
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,16 +30,21 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import pickle
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from uds.core.services import UserDeployment
|
||||
from uds.core.util.state import State
|
||||
from uds.core.util import log
|
||||
|
||||
from . import openStack
|
||||
|
||||
import pickle
|
||||
import logging
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .LiveService import LiveService
|
||||
from .LivePublication import LivePublication
|
||||
|
||||
__updated__ = '2019-02-07'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -58,12 +63,12 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
The logic for managing ovirt deployments (user machines in this case) is here.
|
||||
"""
|
||||
_name = ''
|
||||
_ip = ''
|
||||
_mac = ''
|
||||
_vmid = ''
|
||||
_reason = ''
|
||||
_queue = None
|
||||
_name: str = ''
|
||||
_ip: str = ''
|
||||
_mac: str = ''
|
||||
_vmid: str = ''
|
||||
_reason: str = ''
|
||||
_queue: typing.List[int] = []
|
||||
|
||||
# : Recheck every this seconds by default (for task methods)
|
||||
suggestedTime = 20
|
||||
@ -76,8 +81,16 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
self._reason = ''
|
||||
self._queue = []
|
||||
|
||||
# For typing check only...
|
||||
def service(self) -> 'LiveService':
|
||||
return typing.cast('LiveService', super().service())
|
||||
|
||||
# For typing check only...
|
||||
def publication(self) -> 'LivePublication':
|
||||
return typing.cast('LivePublication', super().publication())
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self):
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
Does nothing right here, we will use envoronment storage in this sample
|
||||
"""
|
||||
@ -91,7 +104,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
pickle.dumps(self._queue, protocol=0)
|
||||
]).encode('utf8')
|
||||
|
||||
def unmarshal(self, data: bytes):
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
Does nothing here also, all data are keeped at environment storage
|
||||
"""
|
||||
@ -144,7 +157,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
:note: This IP is the IP of the "consumed service", so the transport can
|
||||
access it.
|
||||
"""
|
||||
logger.debug('Setting IP to {}'.format(ip))
|
||||
logger.debug('Setting IP to %s', ip)
|
||||
self._ip = ip
|
||||
|
||||
def getUniqueId(self):
|
||||
@ -206,15 +219,9 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
if self._vmid != '':
|
||||
self.service().resetMachine(self._vmid)
|
||||
|
||||
def getConsoleConnection(self):
|
||||
return self.service().getConsoleConnection(self._vmid)
|
||||
|
||||
def desktopLogin(self, username, password, domain=''):
|
||||
return self.service().desktopLogin(self._vmId, username, password, domain)
|
||||
|
||||
def notifyReadyFromOsManager(self, data):
|
||||
# Here we will check for suspending the VM (when full ready)
|
||||
logger.debug('Checking if cache 2 for {0}'.format(self._name))
|
||||
logger.debug('Checking if cache 2 for %s', self._name)
|
||||
if self.__getCurrentOp() == opWait:
|
||||
logger.debug('Machine is ready. Moving to level 2')
|
||||
self.__popCurrentOp() # Remove current state
|
||||
@ -245,7 +252,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
self._queue = [opCreate, opWait, opSuspend, opFinish]
|
||||
|
||||
def __checkMachineState(self, chkState):
|
||||
logger.debug('Checking that state of machine {} ({}) is {}'.format(self._vmid, self._name, chkState))
|
||||
logger.debug('Checking that state of machine %s (%s) is %s', self._vmid, self._name, chkState)
|
||||
status = self.service().getMachineState(self._vmid)
|
||||
|
||||
# If we want to check an state and machine does not exists (except in case that we whant to check this)
|
||||
@ -253,24 +260,20 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
return self.__error('Machine not available. ({})'.format(status))
|
||||
|
||||
ret = State.RUNNING
|
||||
|
||||
if type(chkState) is list:
|
||||
if status in chkState:
|
||||
ret = State.FINISHED
|
||||
else:
|
||||
if status == chkState:
|
||||
ret = State.FINISHED
|
||||
chkState = [chkState] if not isinstance(chkState, (list, tuple)) else chkState
|
||||
if status in chkState:
|
||||
ret = State.FINISHED
|
||||
|
||||
return ret
|
||||
|
||||
def __getCurrentOp(self):
|
||||
if len(self._queue) == 0:
|
||||
if not self._queue:
|
||||
return opFinish
|
||||
|
||||
return self._queue[0]
|
||||
|
||||
def __popCurrentOp(self):
|
||||
if len(self._queue) == 0:
|
||||
if not self._queue:
|
||||
return opFinish
|
||||
|
||||
res = self._queue.pop(0)
|
||||
@ -313,7 +316,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Callable[[], None]] = {
|
||||
opCreate: self.__create,
|
||||
opRetry: self.__retry,
|
||||
opStart: self.__startMachine,
|
||||
@ -323,12 +326,10 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
}
|
||||
|
||||
try:
|
||||
execFnc = fncs.get(op, None)
|
||||
|
||||
if execFnc is None:
|
||||
if op not in fncs:
|
||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
|
||||
execFnc()
|
||||
fncs[op]()
|
||||
|
||||
return State.RUNNING
|
||||
except Exception as e:
|
||||
@ -432,7 +433,7 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Callable[[], str]] = {
|
||||
opCreate: self.__checkCreate,
|
||||
opRetry: self.__retry,
|
||||
opWait: self.__wait,
|
||||
@ -442,12 +443,10 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
}
|
||||
|
||||
try:
|
||||
chkFnc = fncs.get(op, None)
|
||||
if op not in fncs:
|
||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
|
||||
if chkFnc is None:
|
||||
return self.__error('Unknown operation found at check queue ({0})'.format(op))
|
||||
|
||||
state = chkFnc()
|
||||
state = fncs[op]()
|
||||
if state == State.FINISHED:
|
||||
self.__popCurrentOp() # Remove runing op
|
||||
return self.__executeQueue()
|
||||
@ -462,7 +461,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
(No matter wether it is for cache or for an user)
|
||||
"""
|
||||
self.__debug('finish')
|
||||
pass
|
||||
|
||||
def moveToCache(self, newLevel):
|
||||
"""
|
||||
@ -478,25 +476,6 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
|
||||
return self.__executeQueue()
|
||||
|
||||
def userLoggedIn(self, user):
|
||||
"""
|
||||
This method must be available so os managers can invoke it whenever
|
||||
an user get logged into a service.
|
||||
|
||||
The user provided is just an string, that is provided by actor.
|
||||
"""
|
||||
# We store the value at storage, but never get used, just an example
|
||||
pass
|
||||
|
||||
def userLoggedOut(self, user):
|
||||
"""
|
||||
This method must be available so os managers can invoke it whenever
|
||||
an user get logged out if a service.
|
||||
|
||||
The user provided is just an string, that is provided by actor.
|
||||
"""
|
||||
pass
|
||||
|
||||
def reasonOfError(self):
|
||||
"""
|
||||
Returns the reason of the error.
|
||||
@ -553,8 +532,8 @@ class LiveDeployment(UserDeployment): # pylint: disable=too-many-public-methods
|
||||
}.get(op, '????')
|
||||
|
||||
def __debug(self, txt):
|
||||
logger.debug('_name {0}: {1}'.format(txt, self._name))
|
||||
logger.debug('_ip {0}: {1}'.format(txt, self._ip))
|
||||
logger.debug('_mac {0}: {1}'.format(txt, self._mac))
|
||||
logger.debug('_vmid {0}: {1}'.format(txt, self._vmid))
|
||||
logger.debug('Queue at {0}: {1}'.format(txt, [LiveDeployment.__op2str(op) for op in self._queue]))
|
||||
logger.debug('_name %s: %s', txt, self._name)
|
||||
logger.debug('_ip %s: %s', txt, self._ip)
|
||||
logger.debug('_mac %s: %s', txt, self._mac)
|
||||
logger.debug('_vmid %s: %s', txt, self._vmid)
|
||||
logger.debug('Queue at %s: %s', txt, [LiveDeployment.__op2str(op) for op in self._queue])
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,17 +30,11 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from uds.core.services import Publication
|
||||
from uds.core.util.state import State
|
||||
from datetime import datetime
|
||||
|
||||
import six
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2019-02-07'
|
||||
from uds.core.services import Publication
|
||||
from uds.core.util.state import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -49,13 +43,13 @@ class LivePublication(Publication):
|
||||
"""
|
||||
This class provides the publication of a oVirtLinkedService
|
||||
"""
|
||||
_name = ''
|
||||
_reason = ''
|
||||
_templateId = ''
|
||||
_state = 'r'
|
||||
_destroyAfter = 'n'
|
||||
_name: str = ''
|
||||
_reason: str = ''
|
||||
_templateId: str = ''
|
||||
_state: str = 'r'
|
||||
_destroyAfter: str = 'n'
|
||||
|
||||
suggestedTime = 20 # : Suggested recheck time if publication is unfinished in seconds
|
||||
suggestedTime: int = 20 # : Suggested recheck time if publication is unfinished in seconds
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
@ -73,13 +67,13 @@ class LivePublication(Publication):
|
||||
self._state = 'r'
|
||||
self._destroyAfter = 'n'
|
||||
|
||||
def marshal(self):
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
returns data from an instance of Sample Publication serialized
|
||||
"""
|
||||
return '\t'.join(['v1', self._name, self._reason, self._templateId, self._state, self._destroyAfter]).encode('utf8')
|
||||
|
||||
def unmarshal(self, data):
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
deserializes the data and loads it inside instance.
|
||||
"""
|
||||
@ -97,7 +91,7 @@ class LivePublication(Publication):
|
||||
|
||||
try:
|
||||
res = self.service().makeTemplate(self._name)
|
||||
logger.debug('Result: {}'.format(res))
|
||||
logger.debug('Publication result: %s', res)
|
||||
self._templateId = res['id']
|
||||
self._state = res['status']
|
||||
except Exception as e:
|
||||
@ -128,7 +122,6 @@ class LivePublication(Publication):
|
||||
"""
|
||||
In our case, finish does nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
def reasonOfError(self):
|
||||
"""
|
||||
@ -153,7 +146,7 @@ class LivePublication(Publication):
|
||||
"""
|
||||
# We do not do anything else to destroy this instance of publication
|
||||
if self._state == 'error':
|
||||
return # Nothing to cancel
|
||||
return State.ERROR # Nothing to cancel
|
||||
|
||||
if self._state == 'creating':
|
||||
self._destroyAfter = 'y'
|
||||
@ -163,7 +156,7 @@ class LivePublication(Publication):
|
||||
self.service().removeTemplate(self._templateId)
|
||||
except Exception as e:
|
||||
self._state = 'error'
|
||||
self._reason = six.text_type(e)
|
||||
self._reason = str(e)
|
||||
return State.ERROR
|
||||
|
||||
return State.FINISHED
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,22 +30,25 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from django.utils.translation import ugettext_noop as _, ugettext
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.transports import protocols
|
||||
from uds.core.services import Service, types as serviceTypes
|
||||
from uds.core.ui import gui
|
||||
|
||||
from .LivePublication import LivePublication
|
||||
from .LiveDeployment import LiveDeployment
|
||||
from . import helpers
|
||||
|
||||
from uds.core.ui import gui
|
||||
|
||||
import six
|
||||
import logging
|
||||
|
||||
__updated__ = '2018-10-22'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import openStack
|
||||
|
||||
|
||||
class LiveService(Service):
|
||||
"""
|
||||
@ -98,21 +101,29 @@ class LiveService(Service):
|
||||
|
||||
# Now the form part
|
||||
region = gui.ChoiceField(label=_('Region'), order=1, tooltip=_('Service region'), required=True, rdonly=True)
|
||||
project = gui.ChoiceField(label=_('Project'), order=2,
|
||||
project = gui.ChoiceField(
|
||||
label=_('Project'),
|
||||
order=2,
|
||||
fills={
|
||||
'callbackName' : 'osFillResources',
|
||||
'function' : helpers.getResources,
|
||||
'parameters' : ['ov', 'ev', 'project', 'region', 'legacy']
|
||||
},
|
||||
tooltip=_('Project for this service'), required=True, rdonly=True
|
||||
},
|
||||
tooltip=_('Project for this service'),
|
||||
required=True,
|
||||
rdonly=True
|
||||
)
|
||||
availabilityZone = gui.ChoiceField(label=_('Availability Zones'), order=3,
|
||||
availabilityZone = gui.ChoiceField(
|
||||
label=_('Availability Zones'),
|
||||
order=3,
|
||||
fills={
|
||||
'callbackName' : 'osFillVolumees',
|
||||
'function' : helpers.getVolumes,
|
||||
'parameters' : ['ov', 'ev', 'project', 'region', 'availabilityZone', 'legacy']
|
||||
},
|
||||
tooltip=_('Service availability zones'), required=True, rdonly=True
|
||||
},
|
||||
tooltip=_('Service availability zones'),
|
||||
required=True,
|
||||
rdonly=True
|
||||
)
|
||||
volume = gui.ChoiceField(label=_('Volume'), order=4, tooltip=_('Base volume for service (restricted by availability zone)'), required=True, tab=_('Machine'))
|
||||
# volumeType = gui.ChoiceField(label=_('Volume Type'), order=5, tooltip=_('Volume type for service'), required=True)
|
||||
@ -144,6 +155,8 @@ class LiveService(Service):
|
||||
ev = gui.HiddenField(value=None)
|
||||
legacy = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
|
||||
|
||||
_api: typing.Optional['openStack.Client']
|
||||
|
||||
def initialize(self, values):
|
||||
"""
|
||||
We check here form values to see if they are valid.
|
||||
@ -151,7 +164,7 @@ class LiveService(Service):
|
||||
Note that we check them through FROM variables, that already has been
|
||||
initialized by __init__ method of base class, before invoking this.
|
||||
"""
|
||||
if values is not None:
|
||||
if values:
|
||||
length = int(self.lenName.value)
|
||||
if len(self.baseName.value) + length > 15:
|
||||
raise Service.ValidationException(_('The length of basename plus length must not be greater than 15'))
|
||||
@ -182,30 +195,30 @@ class LiveService(Service):
|
||||
self.legacy.setDefValue(self.parent().legacy and 'true' or 'false')
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
if self._api is None:
|
||||
def api(self) -> 'openStack.Client':
|
||||
if not self._api:
|
||||
self._api = self.parent().api(projectId=self.project.value, region=self.region.value)
|
||||
|
||||
return self._api
|
||||
|
||||
def sanitizeVmName(self, name):
|
||||
def sanitizeVmName(self, name: str) -> str:
|
||||
return self.parent().sanitizeVmName(name)
|
||||
|
||||
def makeTemplate(self, templateName, description=None):
|
||||
def makeTemplate(self, templateName: str, description: typing.Optional[str] = None):
|
||||
# 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 = 'UDS Template snapshot' if description is None else description
|
||||
description = description or 'UDS Template snapshot'
|
||||
return self.api.createVolumeSnapshot(self.volume.value, templateName, description)
|
||||
|
||||
def getTemplate(self, snapshotId):
|
||||
def getTemplate(self, snapshotId: str):
|
||||
"""
|
||||
Checks current state of a template (an snapshot)
|
||||
"""
|
||||
return self.api.getSnapshot(snapshotId)
|
||||
|
||||
def deployFromTemplate(self, name, snapshotId):
|
||||
def deployFromTemplate(self, name: str, snapshotId: str) -> str:
|
||||
"""
|
||||
Deploys a virtual machine on selected cluster from selected template
|
||||
|
||||
@ -217,22 +230,24 @@ class LiveService(Service):
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
"""
|
||||
logger.debug('Deploying from template {0} machine {1}'.format(snapshotId, name))
|
||||
logger.debug('Deploying from template %s machine %s', snapshotId, name)
|
||||
# self.datastoreHasSpace()
|
||||
return self.api.createServerFromSnapshot(snapshotId=snapshotId,
|
||||
name=name,
|
||||
availabilityZone=self.availabilityZone.value,
|
||||
flavorId=self.flavor.value,
|
||||
networkId=self.network.value,
|
||||
securityGroupsIdsList=self.securityGroups.value)['id']
|
||||
return self.api.createServerFromSnapshot(
|
||||
snapshotId=snapshotId,
|
||||
name=name,
|
||||
availabilityZone=self.availabilityZone.value,
|
||||
flavorId=self.flavor.value,
|
||||
networkId=self.network.value,
|
||||
securityGroupsIdsList=self.securityGroups.value
|
||||
)['id']
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
def removeTemplate(self, templateId: str) -> None:
|
||||
"""
|
||||
invokes removeTemplate from parent provider
|
||||
"""
|
||||
self.api.deleteSnapshot(templateId)
|
||||
|
||||
def getMachineState(self, machineId):
|
||||
def getMachineState(self, machineId: str) -> str:
|
||||
"""
|
||||
Invokes getServer from openstack client
|
||||
|
||||
@ -259,9 +274,12 @@ class LiveService(Service):
|
||||
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.
|
||||
"""
|
||||
return self.api.getServer(machineId)['status']
|
||||
server = self.api.getServer(machineId)
|
||||
if server['status'] in ('ERROR', 'DELETED'):
|
||||
logger.warning('Got server status %s for %s: %s', server['status'], machineId, server.get('fault'))
|
||||
return server['status']
|
||||
|
||||
def startMachine(self, machineId):
|
||||
def startMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to OpenStack.
|
||||
|
||||
@ -274,7 +292,7 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.startServer(machineId)
|
||||
|
||||
def stopMachine(self, machineId):
|
||||
def stopMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to stop a machine. No check is done, it is simply requested to OpenStack
|
||||
|
||||
@ -285,7 +303,7 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.stopServer(machineId)
|
||||
|
||||
def resetMachine(self, machineId):
|
||||
def resetMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to stop a machine. No check is done, it is simply requested to OpenStack
|
||||
|
||||
@ -296,7 +314,7 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.resetServer(machineId)
|
||||
|
||||
def suspendMachine(self, machineId):
|
||||
def suspendMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to suspend a machine. No check is done, it is simply requested to OpenStack
|
||||
|
||||
@ -307,7 +325,7 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.suspendServer(machineId)
|
||||
|
||||
def resumeMachine(self, machineId):
|
||||
def resumeMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to start a machine. No check is done, it is simply requested to OpenStack
|
||||
|
||||
@ -318,7 +336,7 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.resumeServer(machineId)
|
||||
|
||||
def removeMachine(self, machineId):
|
||||
def removeMachine(self, machineId: str) -> None:
|
||||
"""
|
||||
Tries to delete a machine. No check is done, it is simply requested to OpenStack
|
||||
|
||||
@ -329,21 +347,22 @@ class LiveService(Service):
|
||||
"""
|
||||
self.api.deleteServer(machineId)
|
||||
|
||||
def getNetInfo(self, machineId):
|
||||
def getNetInfo(self, machineId: str) -> typing.Tuple[str, str]:
|
||||
"""
|
||||
Gets the mac address of first nic of the machine
|
||||
"""
|
||||
net = self.api.getServer(machineId)['addresses']
|
||||
vals = six.next(six.itervalues(net))[0] # Returns "any" mac address of any interface. We just need only one interface info
|
||||
vals = next(iter(net.values()))[0] # Returns "any" mac address of any interface. We just need only one interface info
|
||||
# vals = six.next(six.itervalues(net))[0]
|
||||
return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']
|
||||
|
||||
def getBaseName(self):
|
||||
def getBaseName(self) -> str:
|
||||
"""
|
||||
Returns the base name
|
||||
"""
|
||||
return self.baseName.value
|
||||
|
||||
def getLenName(self):
|
||||
def getLenName(self) -> int:
|
||||
"""
|
||||
Returns the length of numbers part
|
||||
"""
|
||||
|
@ -34,7 +34,6 @@ import typing
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core import Module
|
||||
from uds.core.services import ServiceProvider
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators
|
||||
@ -42,6 +41,9 @@ from uds.core.util import validators
|
||||
from .LiveService import LiveService
|
||||
from . import openStack
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core import Module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -114,7 +116,7 @@ class Provider(ServiceProvider):
|
||||
# Own variables
|
||||
_api: typing.Optional[openStack.Client] = None
|
||||
|
||||
def initialize(self, values: Module.ValuesType = None):
|
||||
def initialize(self, values: 'Module.ValuesType' = None):
|
||||
"""
|
||||
We will use the "autosave" feature for form fields
|
||||
"""
|
||||
@ -123,11 +125,14 @@ class Provider(ServiceProvider):
|
||||
if values is not None:
|
||||
self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
|
||||
|
||||
def api(self, projectId=None, region=None):
|
||||
def api(self, projectId=None, region=None) -> openStack.Client:
|
||||
if self._api is None:
|
||||
self._api = openStack.Client(
|
||||
self.endpoint.value, -1,
|
||||
self.domain.value, self.username.value, self.password.value,
|
||||
self.endpoint.value,
|
||||
-1,
|
||||
self.domain.value,
|
||||
self.username.value,
|
||||
self.password.value,
|
||||
legacyVersion=False,
|
||||
useSSL=None,
|
||||
projectId=projectId,
|
||||
@ -136,7 +141,7 @@ class Provider(ServiceProvider):
|
||||
)
|
||||
return self._api
|
||||
|
||||
def sanitizeVmName(self, name: str):
|
||||
def sanitizeVmName(self, name: str) -> str:
|
||||
return openStack.sanitizeName(name)
|
||||
|
||||
def testConnection(self):
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -32,7 +32,8 @@ Created on Jun 22, 2012
|
||||
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.services import ServiceProvider
|
||||
@ -42,9 +43,11 @@ from uds.core.util import validators
|
||||
from .LiveService import LiveService
|
||||
from . import openStack
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2018-10-22'
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core import Module
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -117,9 +120,9 @@ class ProviderLegacy(ServiceProvider):
|
||||
legacy = True
|
||||
|
||||
# Own variables
|
||||
_api = None
|
||||
_api: typing.Optional[openStack.Client] = None
|
||||
|
||||
def initialize(self, values=None):
|
||||
def initialize(self, values: 'Module.ValuesType' = None):
|
||||
'''
|
||||
We will use the "autosave" feature for form fields
|
||||
'''
|
||||
@ -128,14 +131,19 @@ class ProviderLegacy(ServiceProvider):
|
||||
if values is not None:
|
||||
self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
|
||||
|
||||
def api(self, projectId=None, region=None):
|
||||
return openStack.Client(self.host.value, self.port.value,
|
||||
self.domain.value, self.username.value, self.password.value,
|
||||
legacyVersion=True,
|
||||
useSSL=self.ssl.isTrue(),
|
||||
projectId=projectId,
|
||||
region=region,
|
||||
access=self.access.value)
|
||||
def api(self, projectId=None, region=None) -> openStack.Client:
|
||||
return openStack.Client(
|
||||
self.host.value,
|
||||
self.port.value,
|
||||
self.domain.value,
|
||||
self.username.value,
|
||||
self.password.value,
|
||||
legacyVersion=True,
|
||||
useSSL=self.ssl.isTrue(),
|
||||
projectId=projectId,
|
||||
region=region,
|
||||
access=self.access.value
|
||||
)
|
||||
|
||||
def sanitizeVmName(self, name):
|
||||
return openStack.sanitizeName(name)
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2016-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -29,4 +29,3 @@
|
||||
|
||||
from .ProviderLegacy import ProviderLegacy
|
||||
from .Provider import Provider
|
||||
|
||||
|
@ -1,21 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2016-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. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
import logging
|
||||
from uds.core.ui import gui
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def getResources(parameters):
|
||||
def getResources(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
'''
|
||||
This helper is designed as a callback for Project Selector
|
||||
'''
|
||||
@ -24,7 +46,7 @@ def getResources(parameters):
|
||||
else:
|
||||
from .Provider import Provider
|
||||
from uds.core.environment import Environment
|
||||
logger.debug('Parameters received by getResources Helper: {0}'.format(parameters))
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider = Provider(env)
|
||||
provider.unserialize(parameters['ov'])
|
||||
@ -38,16 +60,16 @@ def getResources(parameters):
|
||||
volumeTypes = [gui.choiceItem('-', _('None'))] + [gui.choiceItem(t['id'], t['name']) for t in api.listVolumeTypes()]
|
||||
|
||||
data = [
|
||||
{'name': 'availabilityZone', 'values': zones },
|
||||
{'name': 'network', 'values': networks },
|
||||
{'name': 'flavor', 'values': flavors },
|
||||
{'name': 'securityGroups', 'values': securityGroups },
|
||||
{'name': 'volumeType', 'values': volumeTypes },
|
||||
{'name': 'availabilityZone', 'values': zones},
|
||||
{'name': 'network', 'values': networks},
|
||||
{'name': 'flavor', 'values': flavors},
|
||||
{'name': 'securityGroups', 'values': securityGroups},
|
||||
{'name': 'volumeType', 'values': volumeTypes},
|
||||
]
|
||||
logger.debug('Return data: {}'.format(data))
|
||||
logger.debug('Return data: %s', data)
|
||||
return data
|
||||
|
||||
def getVolumes(parameters):
|
||||
def getVolumes(parameters: typing.Dict[str, str]) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
'''
|
||||
This helper is designed as a callback for Zone Selector
|
||||
'''
|
||||
@ -56,17 +78,19 @@ def getVolumes(parameters):
|
||||
else:
|
||||
from .Provider import Provider
|
||||
from uds.core.environment import Environment
|
||||
logger.debug('Parameters received by getVolumes Helper: {0}'.format(parameters))
|
||||
logger.debug('Parameters received by getVolumes Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider = Provider(env)
|
||||
provider.unserialize(parameters['ov'])
|
||||
|
||||
api = provider.api(parameters['project'], parameters['region'])
|
||||
|
||||
volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']]
|
||||
# Source volumes are all available for us
|
||||
# volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '' and v['availability_zone'] == parameters['availabilityZone']]
|
||||
volumes = [gui.choiceItem(v['id'], v['name']) for v in api.listVolumes() if v['name'] != '']
|
||||
|
||||
data = [
|
||||
{'name': 'volume', 'values': volumes },
|
||||
{'name': 'volume', 'values': volumes},
|
||||
]
|
||||
logger.debug('Return data: {}'.format(data))
|
||||
logger.debug('Return data: %s', data)
|
||||
return data
|
||||
|
@ -32,12 +32,18 @@
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import typing
|
||||
|
||||
import requests
|
||||
# import dateutil.parser
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Required: Authentication v3
|
||||
@ -51,11 +57,12 @@ VERIFY_SSL = False
|
||||
|
||||
|
||||
# Helpers
|
||||
def ensureResponseIsValid(response, errMsg=None):
|
||||
def ensureResponseIsValid(response: requests.Response, errMsg: typing.Optional[str] = None) -> None:
|
||||
if response.ok is False:
|
||||
try:
|
||||
_, err = response.json().popitem() # Extract any key, in case of error is expected to have only one top key so this will work
|
||||
errMsg += ': {message}'.format(**err)
|
||||
msg = ': {message}'.format(**err)
|
||||
errMsg = errMsg + msg if errMsg else msg
|
||||
except Exception:
|
||||
pass # If error geting error message, simply ignore it (will be loged on service log anyway)
|
||||
if errMsg is None:
|
||||
@ -64,7 +71,14 @@ def ensureResponseIsValid(response, errMsg=None):
|
||||
raise Exception(errMsg)
|
||||
|
||||
|
||||
def getRecurringUrlJson(url, headers, key, params=None, errMsg=None, timeout=10):
|
||||
def getRecurringUrlJson(
|
||||
url: str,
|
||||
headers: typing.Dict[str, str],
|
||||
key: str,
|
||||
params: typing.Dict[str, str] = None,
|
||||
errMsg: str = None,
|
||||
timeout: int = 10
|
||||
) -> typing.Iterable[typing.Any]:
|
||||
counter = 0
|
||||
while True:
|
||||
counter += 1
|
||||
@ -86,8 +100,7 @@ def getRecurringUrlJson(url, headers, key, params=None, errMsg=None, timeout=10)
|
||||
|
||||
# Decorators
|
||||
def authRequired(func):
|
||||
|
||||
def ensurer(obj, *args, **kwargs):
|
||||
def ensurer(obj: 'Client', *args, **kwargs):
|
||||
obj.ensureAuthenticated()
|
||||
try:
|
||||
return func(obj, *args, **kwargs)
|
||||
@ -109,13 +122,41 @@ def authProjectRequired(func):
|
||||
return ensurer
|
||||
|
||||
|
||||
class Client(object):
|
||||
class Client: # pylint: disable=too-many-public-methods
|
||||
PUBLIC = 'public'
|
||||
PRIVATE = 'private'
|
||||
INTERNAL = 'url'
|
||||
|
||||
_authenticated: bool
|
||||
_authenticatedProjectId: typing.Optional[str]
|
||||
_authUrl: str
|
||||
_tokenId: typing.Optional[str]
|
||||
_catalog: typing.Optional[typing.List[typing.Dict[str, typing.Any]]]
|
||||
_isLegacy: bool
|
||||
_access: typing.Optional[str]
|
||||
_domain: str
|
||||
_username: str
|
||||
_password: str
|
||||
_userId: typing.Optional[str]
|
||||
_projectId: typing.Optional[str]
|
||||
_project: typing.Optional[str]
|
||||
_region: typing.Optional[str]
|
||||
_timeout: int
|
||||
|
||||
# Legacyversion is True for versions <= Ocata
|
||||
def __init__(self, host, port, domain, username, password, legacyVersion=True, useSSL=False, projectId=None, region=None, access=None):
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: typing.Union[str, int],
|
||||
domain: str,
|
||||
username: str,
|
||||
password: str,
|
||||
legacyVersion: bool = True,
|
||||
useSSL: bool = False,
|
||||
projectId: typing.Optional[str] = None,
|
||||
region: typing.Optional[str] = None,
|
||||
access: typing.Optional[str] = None
|
||||
):
|
||||
self._authenticated = False
|
||||
self._authenticatedProjectId = None
|
||||
self._tokenId = None
|
||||
@ -137,21 +178,22 @@ class Client(object):
|
||||
if self._authUrl[-1] != '/':
|
||||
self._authUrl += '/'
|
||||
|
||||
def _getEndpointFor(self, type_): # If no region is indicatad, first endpoint is returned
|
||||
def _getEndpointFor(self, type_: str) -> str: # If no region is indicatad, first endpoint is returned
|
||||
for i in self._catalog:
|
||||
if i['type'] == type_:
|
||||
for j in i['endpoints']:
|
||||
if j['interface'] == self._access and (self._region is None or j['region'] == self._region):
|
||||
return j['url']
|
||||
raise Exception('No endpoint url found')
|
||||
|
||||
def _requestHeaders(self):
|
||||
def _requestHeaders(self) -> typing.Dict[str, str]:
|
||||
headers = {'content-type': 'application/json'}
|
||||
if self._tokenId is not None:
|
||||
if self._tokenId:
|
||||
headers['X-Auth-Token'] = self._tokenId
|
||||
|
||||
return headers
|
||||
|
||||
def authPassword(self):
|
||||
def authPassword(self) -> None:
|
||||
# logger.debug('Authenticating...')
|
||||
data = {
|
||||
'auth': {
|
||||
@ -189,11 +231,13 @@ class Client(object):
|
||||
|
||||
# logger.debug('Request data: {}'.format(data))
|
||||
|
||||
r = requests.post(self._authUrl + 'v3/auth/tokens',
|
||||
data=json.dumps(data),
|
||||
headers={'content-type': 'application/json'},
|
||||
verify=VERIFY_SSL,
|
||||
timeout=self._timeout)
|
||||
r = requests.post(
|
||||
self._authUrl + 'v3/auth/tokens',
|
||||
data=json.dumps(data),
|
||||
headers={'content-type': 'application/json'},
|
||||
verify=VERIFY_SSL,
|
||||
timeout=self._timeout
|
||||
)
|
||||
|
||||
ensureResponseIsValid(r, 'Invalid Credentials')
|
||||
|
||||
@ -211,12 +255,12 @@ class Client(object):
|
||||
if self._projectId is not None:
|
||||
self._catalog = token['catalog']
|
||||
|
||||
def ensureAuthenticated(self):
|
||||
def ensureAuthenticated(self) -> None:
|
||||
if self._authenticated is False or self._projectId != self._authenticatedProjectId:
|
||||
self.authPassword()
|
||||
|
||||
@authRequired
|
||||
def listProjects(self):
|
||||
def listProjects(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._authUrl + 'v3/users/{user_id}/projects'.format(user_id=self._userId),
|
||||
headers=self._requestHeaders(),
|
||||
@ -226,7 +270,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authRequired
|
||||
def listRegions(self):
|
||||
def listRegions(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._authUrl + 'v3/regions/',
|
||||
headers=self._requestHeaders(),
|
||||
@ -236,7 +280,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listServers(self, detail=False, params=None):
|
||||
def listServers(self, detail: bool = False, params: bool = None) -> typing.Iterable[typing.Any]:
|
||||
path = '/servers/' + 'detail' if detail is True else ''
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('compute') + path,
|
||||
@ -248,7 +292,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listImages(self):
|
||||
def listImages(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('image') + '/v2/images?status=active',
|
||||
headers=self._requestHeaders(),
|
||||
@ -258,7 +302,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listVolumeTypes(self):
|
||||
def listVolumeTypes(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('volumev2') + '/types',
|
||||
headers=self._requestHeaders(),
|
||||
@ -268,7 +312,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listVolumes(self):
|
||||
def listVolumes(self) -> typing.Iterable[typing.Any]:
|
||||
# self._getEndpointFor('volumev2') + '/volumes'
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('volumev2') + '/volumes/detail',
|
||||
@ -279,7 +323,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listVolumeSnapshots(self, volumeId=None):
|
||||
def listVolumeSnapshots(self, volumeId: typing.Optional[typing.Dict[str, typing.Any]] = None) -> typing.Iterable[typing.Any]:
|
||||
for s in getRecurringUrlJson(
|
||||
self._getEndpointFor('volumev2') + '/snapshots',
|
||||
headers=self._requestHeaders(),
|
||||
@ -291,7 +335,7 @@ class Client(object):
|
||||
yield s
|
||||
|
||||
@authProjectRequired
|
||||
def listAvailabilityZones(self):
|
||||
def listAvailabilityZones(self) -> typing.Iterable[typing.Any]:
|
||||
for az in getRecurringUrlJson(
|
||||
self._getEndpointFor('compute') + '/os-availability-zone',
|
||||
headers=self._requestHeaders(),
|
||||
@ -303,7 +347,7 @@ class Client(object):
|
||||
yield az['zoneName']
|
||||
|
||||
@authProjectRequired
|
||||
def listFlavors(self):
|
||||
def listFlavors(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('compute') + '/flavors',
|
||||
headers=self._requestHeaders(),
|
||||
@ -313,7 +357,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listNetworks(self):
|
||||
def listNetworks(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('network') + '/v2.0/networks',
|
||||
headers=self._requestHeaders(),
|
||||
@ -323,7 +367,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listPorts(self, networkId=None, ownerId=None):
|
||||
def listPorts(self, networkId: typing.Optional[str] = None, ownerId: typing.Optional[str] = None) -> typing.Iterable[typing.Any]:
|
||||
params = {}
|
||||
if networkId is not None:
|
||||
params['network_id'] = networkId
|
||||
@ -340,7 +384,7 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def listSecurityGroups(self):
|
||||
def listSecurityGroups(self) -> typing.Iterable[typing.Any]:
|
||||
return getRecurringUrlJson(
|
||||
self._getEndpointFor('compute') + '/os-security-groups',
|
||||
headers=self._requestHeaders(),
|
||||
@ -350,19 +394,18 @@ class Client(object):
|
||||
)
|
||||
|
||||
@authProjectRequired
|
||||
def getServer(self, serverId):
|
||||
def getServer(self, serverId: str) -> typing.Dict[str, typing.Any]:
|
||||
r = requests.get(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}'.format(server_id=serverId),
|
||||
headers=self._requestHeaders(),
|
||||
verify=VERIFY_SSL,
|
||||
timeout=self._timeout
|
||||
)
|
||||
|
||||
ensureResponseIsValid(r, 'Get Server information')
|
||||
return r.json()['server']
|
||||
|
||||
@authProjectRequired
|
||||
def getVolume(self, volumeId):
|
||||
def getVolume(self, volumeId: str) -> typing.Dict[str, typing.Any]:
|
||||
r = requests.get(
|
||||
self._getEndpointFor('volumev2') + '/volumes/{volume_id}'.format(volume_id=volumeId),
|
||||
headers=self._requestHeaders(),
|
||||
@ -372,12 +415,10 @@ class Client(object):
|
||||
|
||||
ensureResponseIsValid(r, 'Get Volume information')
|
||||
|
||||
v = r.json()['volume']
|
||||
|
||||
return v
|
||||
return r.json()['volume']
|
||||
|
||||
@authProjectRequired
|
||||
def getSnapshot(self, snapshotId):
|
||||
def getSnapshot(self, snapshotId: str) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
States are:
|
||||
creating, available, deleting, error, error_deleting
|
||||
@ -391,19 +432,17 @@ class Client(object):
|
||||
|
||||
ensureResponseIsValid(r, 'Get Snaphost information')
|
||||
|
||||
v = r.json()['snapshot']
|
||||
|
||||
return v
|
||||
return r.json()['snapshot']
|
||||
|
||||
@authProjectRequired
|
||||
def updateSnapshot(self, snapshotId, name=None, description=None):
|
||||
def updateSnapshot(self, snapshotId: str, name: typing.Optional[str] = None, description: typing.Optional[str] = None) -> typing.Dict[str, typing.Any]:
|
||||
data = {
|
||||
'snapshot': {}
|
||||
}
|
||||
if name is not None:
|
||||
if name:
|
||||
data['snapshot']['name'] = name
|
||||
|
||||
if description is not None:
|
||||
if description:
|
||||
data['snapshot']['description'] = description
|
||||
|
||||
r = requests.put(
|
||||
@ -416,13 +455,11 @@ class Client(object):
|
||||
|
||||
ensureResponseIsValid(r, 'Update Snaphost information')
|
||||
|
||||
v = r.json()['snapshot']
|
||||
|
||||
return v
|
||||
return r.json()['snapshot']
|
||||
|
||||
@authProjectRequired
|
||||
def createVolumeSnapshot(self, volumeId, name, description=None):
|
||||
description = 'UDS Snapshot' if description is None else description
|
||||
def createVolumeSnapshot(self, volumeId: str, name: str, description: typing.Optional[str] = None) -> typing.Dict[str, typing.Any]:
|
||||
description = description or 'UDS Snapshot'
|
||||
data = {
|
||||
'snapshot': {
|
||||
'name': name,
|
||||
@ -447,8 +484,8 @@ class Client(object):
|
||||
return r.json()['snapshot']
|
||||
|
||||
@authProjectRequired
|
||||
def createVolumeFromSnapshot(self, snapshotId, name, description=None):
|
||||
description = 'UDS Volume' if description is None else description
|
||||
def createVolumeFromSnapshot(self, snapshotId: str, name: str, description: typing.Optional[str] = None) -> typing.Dict[str, typing.Any]:
|
||||
description = description or 'UDS Volume'
|
||||
data = {
|
||||
'volume': {
|
||||
'name': name,
|
||||
@ -468,10 +505,19 @@ class Client(object):
|
||||
|
||||
ensureResponseIsValid(r, 'Cannot create volume from snapshot.')
|
||||
|
||||
return r.json()
|
||||
return r.json()['volume']
|
||||
|
||||
@authProjectRequired
|
||||
def createServerFromSnapshot(self, snapshotId, name, availabilityZone, flavorId, networkId, securityGroupsIdsList, count=1):
|
||||
def createServerFromSnapshot(
|
||||
self,
|
||||
snapshotId: str,
|
||||
name: str,
|
||||
availabilityZone: str,
|
||||
flavorId: str,
|
||||
networkId: str,
|
||||
securityGroupsIdsList: typing.Iterable[str],
|
||||
count: int = 1
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
data = {
|
||||
'server': {
|
||||
'name': name,
|
||||
@ -510,7 +556,7 @@ class Client(object):
|
||||
return r.json()['server']
|
||||
|
||||
@authProjectRequired
|
||||
def deleteServer(self, serverId):
|
||||
def deleteServer(self, serverId: str) -> None:
|
||||
r = requests.post(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"forceDelete": null}',
|
||||
@ -524,7 +570,7 @@ class Client(object):
|
||||
# This does not returns anything
|
||||
|
||||
@authProjectRequired
|
||||
def deleteSnapshot(self, snapshotId):
|
||||
def deleteSnapshot(self, snapshotId: str) -> None:
|
||||
r = requests.delete(
|
||||
self._getEndpointFor('volumev2') + '/snapshots/{snapshot_id}'.format(snapshot_id=snapshotId),
|
||||
headers=self._requestHeaders(),
|
||||
@ -537,7 +583,7 @@ class Client(object):
|
||||
# Does not returns a message body
|
||||
|
||||
@authProjectRequired
|
||||
def startServer(self, serverId):
|
||||
def startServer(self, serverId: str) -> None:
|
||||
r = requests.post(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"os-start": null}',
|
||||
@ -551,7 +597,7 @@ class Client(object):
|
||||
# This does not returns anything
|
||||
|
||||
@authProjectRequired
|
||||
def stopServer(self, serverId):
|
||||
def stopServer(self, serverId: str) -> None:
|
||||
r = requests.post(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"os-stop": null}',
|
||||
@ -563,7 +609,7 @@ class Client(object):
|
||||
ensureResponseIsValid(r, 'Stoping server')
|
||||
|
||||
@authProjectRequired
|
||||
def suspendServer(self, serverId):
|
||||
def suspendServer(self, serverId: str) -> None:
|
||||
r = requests.post(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"suspend": null}',
|
||||
@ -575,7 +621,7 @@ class Client(object):
|
||||
ensureResponseIsValid(r, 'Suspending server')
|
||||
|
||||
@authProjectRequired
|
||||
def resumeServer(self, serverId):
|
||||
def resumeServer(self, serverId: str) -> None:
|
||||
r = requests.post(
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"resume": null}',
|
||||
@ -587,7 +633,7 @@ class Client(object):
|
||||
ensureResponseIsValid(r, 'Resuming server')
|
||||
|
||||
@authProjectRequired
|
||||
def resetServer(self, serverId):
|
||||
def resetServer(self, serverId: str) -> None:
|
||||
r = requests.post( # pylint: disable=unused-variable
|
||||
self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
|
||||
data='{"reboot":{"type":"HARD"}}',
|
||||
@ -599,7 +645,7 @@ class Client(object):
|
||||
# Ignore response for this...
|
||||
# ensureResponseIsValid(r, 'Reseting server')
|
||||
|
||||
def testConnection(self):
|
||||
def testConnection(self) -> bool:
|
||||
# First, ensure requested api is supported
|
||||
# We need api version 3.2 or greater
|
||||
try:
|
||||
|
Loading…
Reference in New Issue
Block a user