forked from shaba/openuds
Fixed OpenGnsys Provider
This commit is contained in:
parent
2dc1634004
commit
99fe68608c
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -28,5 +28,4 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
from .Provider import OGProvider
|
||||
|
||||
from .provider import OGProvider
|
||||
|
@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
@ -32,16 +31,19 @@
|
||||
"""
|
||||
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 uds.models.util import getSqlDatetimeAsUnix
|
||||
|
||||
from . import og
|
||||
|
||||
__updated__ = '2019-02-07'
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
from .service import OGService
|
||||
from .publication import OGPublication
|
||||
from uds.core.util.storage import Storage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -63,18 +65,32 @@ class OGDeployment(UserDeployment):
|
||||
# : Recheck every N seconds by default (for task methods)
|
||||
suggestedTime = 20
|
||||
|
||||
def initialize(self):
|
||||
self._name = 'unknown'
|
||||
self._ip = ''
|
||||
self._mac = ''
|
||||
self._machineId = ''
|
||||
self._stamp = 0
|
||||
self._reason = ''
|
||||
_name: str = 'unknown'
|
||||
_ip: str = ''
|
||||
_mac: str = ''
|
||||
_machineId: str = ''
|
||||
_stamp: int = 0
|
||||
_reason: str = ''
|
||||
|
||||
_queue: typing.List[int] # Do not initialize mutable, just declare and it is initialized on "initialize"
|
||||
_uuid: str
|
||||
|
||||
def initialize(self) -> None:
|
||||
self._queue = []
|
||||
self._uuid = self.dbservice().uuid
|
||||
dbs = self.dbservice()
|
||||
self._uuid = dbs.uuid if dbs else ''
|
||||
|
||||
def service(self) -> 'OGService':
|
||||
return typing.cast('OGService', super().service())
|
||||
|
||||
def publication(self) -> 'OGPublication':
|
||||
pub = super().publication()
|
||||
if pub is None:
|
||||
raise Exception('No publication for this element!')
|
||||
return typing.cast('OGPublication', pub)
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self):
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
Does nothing right here, we will use environment storage in this sample
|
||||
"""
|
||||
@ -89,11 +105,11 @@ class OGDeployment(UserDeployment):
|
||||
pickle.dumps(self._queue, protocol=0)
|
||||
])
|
||||
|
||||
def unmarshal(self, str_):
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
Does nothing here also, all data are kept at environment storage
|
||||
"""
|
||||
vals = str_.split(b'\1')
|
||||
vals = data.split(b'\1')
|
||||
if vals[0] == b'v1':
|
||||
self._name = vals[1].decode('utf8')
|
||||
self._ip = vals[2].decode('utf8')
|
||||
@ -103,27 +119,29 @@ class OGDeployment(UserDeployment):
|
||||
self._stamp = int(vals[6].decode('utf8'))
|
||||
self._queue = pickle.loads(vals[7])
|
||||
|
||||
def getName(self):
|
||||
def getName(self) -> str:
|
||||
return self._name
|
||||
|
||||
def getUniqueId(self):
|
||||
def getUniqueId(self) -> str:
|
||||
return self._mac.upper()
|
||||
|
||||
def getIp(self):
|
||||
def getIp(self) -> str:
|
||||
return self._ip
|
||||
|
||||
def setReady(self):
|
||||
def setReady(self) -> str:
|
||||
"""
|
||||
Notifies the current "deadline" to the user, before accessing by UDS
|
||||
The machine has been already been started.
|
||||
The problem is that currently there is no way that a machine is in FACT started.
|
||||
OpenGnsys will try it best by sending an WOL
|
||||
"""
|
||||
self.service().notifyDeadline(self._machineId, self.dbservice().deployed_service.getDeadline())
|
||||
dbs = self.dbservice()
|
||||
deadline = dbs.deployed_service.getDeadline() if dbs else 0
|
||||
self.service().notifyDeadline(self._machineId, deadline)
|
||||
|
||||
return State.FINISHED
|
||||
|
||||
def deployForUser(self, user):
|
||||
def deployForUser(self, user: 'models.User') -> str:
|
||||
"""
|
||||
Deploys an service instance for an user.
|
||||
"""
|
||||
@ -131,19 +149,18 @@ class OGDeployment(UserDeployment):
|
||||
self.__initQueueForDeploy()
|
||||
return self.__executeQueue()
|
||||
|
||||
def deployForCache(self, cacheLevel):
|
||||
def deployForCache(self, cacheLevel: int) -> str:
|
||||
"""
|
||||
Deploys an service instance for cache
|
||||
"""
|
||||
self.__initQueueForDeploy() # No Level2 Cache possible
|
||||
return self.__executeQueue()
|
||||
|
||||
def __initQueueForDeploy(self):
|
||||
|
||||
def __initQueueForDeploy(self) -> None:
|
||||
self._queue = [opCreate, opFinish]
|
||||
|
||||
def __checkMachineReady(self):
|
||||
logger.debug('Checking that state of machine {} ({}) is ready'.format(self._machineId, self._name))
|
||||
def __checkMachineReady(self) -> str:
|
||||
logger.debug('Checking that state of machine %s (%s) is ready', self._machineId, self._name)
|
||||
|
||||
try:
|
||||
status = self.service().status(self._machineId)
|
||||
@ -157,33 +174,33 @@ class OGDeployment(UserDeployment):
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def __getCurrentOp(self):
|
||||
def __getCurrentOp(self) -> int:
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
return self._queue[0]
|
||||
|
||||
def __popCurrentOp(self):
|
||||
def __popCurrentOp(self) -> int:
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
res = self._queue.pop(0)
|
||||
return res
|
||||
|
||||
def __pushFrontOp(self, op):
|
||||
def __pushFrontOp(self, op: int) -> None:
|
||||
self._queue.insert(0, op)
|
||||
|
||||
def __pushBackOp(self, op):
|
||||
def __pushBackOp(self, op: int) -> None:
|
||||
self._queue.append(op)
|
||||
|
||||
def __error(self, reason):
|
||||
def __error(self, reason: typing.Any) -> str:
|
||||
"""
|
||||
Internal method to set object as error state
|
||||
|
||||
Returns:
|
||||
State.ERROR, so we can do "return self.__error(reason)"
|
||||
"""
|
||||
logger.debug('Setting error state, reason: {0}'.format(reason))
|
||||
logger.debug('Setting error state, reason: %s', reason)
|
||||
self.doLog(log.ERROR, reason)
|
||||
|
||||
# TODO: Unreserve machine?? Maybe it just better to keep it assigned so UDS don't get it again in a while...
|
||||
@ -192,7 +209,7 @@ class OGDeployment(UserDeployment):
|
||||
self._reason = str(reason)
|
||||
return State.ERROR
|
||||
|
||||
def __executeQueue(self):
|
||||
def __executeQueue(self) -> str:
|
||||
self.__debug('executeQueue')
|
||||
op = self.__getCurrentOp()
|
||||
|
||||
@ -202,14 +219,14 @@ class OGDeployment(UserDeployment):
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Optional[typing.Callable[[], str]]] = {
|
||||
opCreate: self.__create,
|
||||
opRetry: self.__retry,
|
||||
opRemove: self.__remove,
|
||||
}
|
||||
|
||||
try:
|
||||
execFnc = fncs.get(op, None)
|
||||
execFnc: typing.Optional[typing.Callable[[], str]] = fncs.get(op, None)
|
||||
|
||||
if execFnc is None:
|
||||
return self.__error('Unknown operation found at execution queue ({0})'.format(op))
|
||||
@ -218,11 +235,11 @@ class OGDeployment(UserDeployment):
|
||||
|
||||
return State.RUNNING
|
||||
except Exception as e:
|
||||
logger.exception('Got Exception')
|
||||
# logger.exception('Got Exception')
|
||||
return self.__error(e)
|
||||
|
||||
# Queue execution methods
|
||||
def __retry(self):
|
||||
def __retry(self) -> str:
|
||||
"""
|
||||
Used to retry an operation
|
||||
In fact, this will not be never invoked, unless we push it twice, because
|
||||
@ -232,7 +249,7 @@ class OGDeployment(UserDeployment):
|
||||
"""
|
||||
return State.FINISHED
|
||||
|
||||
def __create(self):
|
||||
def __create(self) -> str:
|
||||
"""
|
||||
Deploys a machine from template for user/cache
|
||||
"""
|
||||
@ -241,7 +258,7 @@ class OGDeployment(UserDeployment):
|
||||
self.service().notifyEvents(r['id'], self._uuid)
|
||||
except Exception as e:
|
||||
# logger.exception('Creating machine')
|
||||
return self.__error('Error creating reservation: {}'.format(e))
|
||||
raise Exception('Error creating reservation: {}'.format(e))
|
||||
|
||||
self._machineId = r['id']
|
||||
self._name = r['name']
|
||||
@ -250,29 +267,34 @@ class OGDeployment(UserDeployment):
|
||||
self._stamp = getSqlDatetimeAsUnix()
|
||||
|
||||
# Store actor version & Known ip
|
||||
self.dbservice().setProperty('actor_version', '1.0-OpenGnsys')
|
||||
self.dbservice().logIP(self._ip)
|
||||
dbs = self.dbservice()
|
||||
if dbs:
|
||||
dbs.setProperty('actor_version', '1.0-OpenGnsys')
|
||||
dbs.logIP(self._ip)
|
||||
|
||||
def __remove(self):
|
||||
return State.RUNNING
|
||||
|
||||
def __remove(self) -> str:
|
||||
"""
|
||||
Removes a machine from system
|
||||
"""
|
||||
self.service().unreserve(self._machineId)
|
||||
return State.RUNNING
|
||||
|
||||
# Check methods
|
||||
def __checkCreate(self):
|
||||
def __checkCreate(self) -> str:
|
||||
"""
|
||||
Checks the state of a deploy for an user or cache
|
||||
"""
|
||||
return self.__checkMachineReady()
|
||||
|
||||
def __checkRemoved(self):
|
||||
def __checkRemoved(self) -> str:
|
||||
"""
|
||||
Checks if a machine has been removed
|
||||
"""
|
||||
return State.FINISHED # No check at all, always true
|
||||
|
||||
def checkState(self):
|
||||
def checkState(self) -> str:
|
||||
"""
|
||||
Check what operation is going on, and acts acordly to it
|
||||
"""
|
||||
@ -285,14 +307,14 @@ class OGDeployment(UserDeployment):
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
fncs = {
|
||||
fncs: typing.Dict[int, typing.Optional[typing.Callable[[], str]]] = {
|
||||
opCreate: self.__checkCreate,
|
||||
opRetry: self.__retry,
|
||||
opRemove: self.__checkRemoved,
|
||||
}
|
||||
|
||||
try:
|
||||
chkFnc = fncs.get(op, None)
|
||||
chkFnc: typing.Optional[typing.Optional[typing.Callable[[], str]]] = fncs.get(op, None)
|
||||
|
||||
if chkFnc is None:
|
||||
return self.__error('Unknown operation found at check queue ({0})'.format(op))
|
||||
@ -306,15 +328,7 @@ class OGDeployment(UserDeployment):
|
||||
except Exception as e:
|
||||
return self.__error(e)
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Invoked when the core notices that the deployment of a service has finished.
|
||||
(No matter wether it is for cache or for an user)
|
||||
"""
|
||||
self.__debug('finish')
|
||||
pass
|
||||
|
||||
def reasonOfError(self):
|
||||
def reasonOfError(self) -> str:
|
||||
"""
|
||||
Returns the reason of the error.
|
||||
|
||||
@ -324,7 +338,7 @@ class OGDeployment(UserDeployment):
|
||||
"""
|
||||
return self._reason
|
||||
|
||||
def destroy(self):
|
||||
def destroy(self) -> str:
|
||||
"""
|
||||
Invoked for destroying a deployed service
|
||||
"""
|
||||
@ -334,7 +348,7 @@ class OGDeployment(UserDeployment):
|
||||
self._queue = [opRemove, opFinish]
|
||||
return self.__executeQueue()
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self) -> str:
|
||||
"""
|
||||
This is a task method. As that, the excepted return values are
|
||||
State values RUNNING, FINISHED or ERROR.
|
||||
@ -347,7 +361,7 @@ class OGDeployment(UserDeployment):
|
||||
return self.destroy()
|
||||
|
||||
@staticmethod
|
||||
def __op2str(op):
|
||||
def __op2str(op: int) -> str:
|
||||
return {
|
||||
opCreate: 'create',
|
||||
opRemove: 'remove',
|
||||
@ -357,8 +371,4 @@ class OGDeployment(UserDeployment):
|
||||
}.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('_machineId {0}: {1}'.format(txt, self._machineId))
|
||||
logger.debug('Queue at {0}: {1}'.format(txt, [OGDeployment.__op2str(op) for op in self._queue]))
|
||||
logger.debug('State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s', txt, self._name, self._ip, self._mac, self._vmId, [OGDeployment.__op2str(op) for op in self._queue])
|
@ -1,27 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 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 _
|
||||
|
||||
from uds.core.environment import Environment
|
||||
from uds.core.ui import gui
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .provider import OGProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getResources(parameters):
|
||||
"""
|
||||
This helper is designed as a callback for Project Selector
|
||||
"""
|
||||
from .Provider import OGProvider
|
||||
from uds.core.environment import Environment
|
||||
def getResources(parameters: typing.Any) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
from .provider import OGProvider
|
||||
|
||||
logger.debug('Parameters received by getResources Helper: %s', parameters)
|
||||
env = Environment(parameters['ev'])
|
||||
provider = OGProvider(env)
|
||||
@ -37,4 +61,5 @@ def getResources(parameters):
|
||||
{'name': 'image', 'values': images},
|
||||
]
|
||||
logger.debug('Return data: %s', data)
|
||||
|
||||
return data
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,40 +30,39 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import typing
|
||||
|
||||
import requests
|
||||
|
||||
from . import urls
|
||||
from . import fake
|
||||
|
||||
import re
|
||||
|
||||
import logging
|
||||
import six
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# URLS
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.cache import Cache
|
||||
|
||||
# Fake part
|
||||
FAKE = False
|
||||
CACHE_VALIDITY = 180
|
||||
|
||||
RT = typing.TypeVar('RT')
|
||||
|
||||
# Decorator
|
||||
def ensureConnected(fnc):
|
||||
def inner(*args, **kwargs):
|
||||
def ensureConnected(fnc: typing.Callable[..., RT]) -> typing.Callable[..., RT]:
|
||||
def inner(*args, **kwargs) -> RT:
|
||||
args[0].connect()
|
||||
return fnc(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
|
||||
# Result checker
|
||||
def ensureResponseIsValid(response, errMsg=None):
|
||||
if response.ok is False:
|
||||
if errMsg is None:
|
||||
def ensureResponseIsValid(response: requests.Response, errMsg: typing.Optional[str] = None) -> typing.Any:
|
||||
if not response.ok:
|
||||
if not errMsg:
|
||||
errMsg = 'Invalid response'
|
||||
|
||||
try:
|
||||
@ -71,7 +70,7 @@ def ensureResponseIsValid(response, errMsg=None):
|
||||
except Exception:
|
||||
err = response.content
|
||||
errMsg = '{}: {}, ({})'.format(errMsg, err, response.status_code)
|
||||
logger.error('{}: {}'.format(errMsg, response.content))
|
||||
logger.error('%s: %s', errMsg, response.content)
|
||||
raise Exception(errMsg)
|
||||
|
||||
try:
|
||||
@ -80,8 +79,16 @@ def ensureResponseIsValid(response, errMsg=None):
|
||||
raise Exception('Error communicating with OpenGnsys: {}'.format(response.content[:128]))
|
||||
|
||||
|
||||
class OpenGnsysClient(object):
|
||||
def __init__(self, username, password, endpoint, cache, verifyCert=False):
|
||||
class OpenGnsysClient:
|
||||
username: str
|
||||
password: str
|
||||
endpoint: str
|
||||
auth: typing.Optional[str]
|
||||
cache: 'Cache'
|
||||
verifyCert: bool
|
||||
cachedVersion: typing.Optional[str]
|
||||
|
||||
def __init__(self, username: str, password: str, endpoint: str, cache: 'Cache', verifyCert: bool = False):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.endpoint = endpoint
|
||||
@ -91,17 +98,17 @@ class OpenGnsysClient(object):
|
||||
self.cachedVersion = None
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
def headers(self) -> typing.MutableMapping[str, str]:
|
||||
headers = {'content-type': 'application/json'}
|
||||
if self.auth is not None:
|
||||
if self.auth:
|
||||
headers['Authorization'] = self.auth
|
||||
|
||||
return headers
|
||||
|
||||
def _ogUrl(self, path):
|
||||
def _ogUrl(self, path: str) -> str:
|
||||
return self.endpoint + '/' + path
|
||||
|
||||
def _post(self, path, data, errMsg=None):
|
||||
def _post(self, path: str, data: typing.Any, errMsg: typing.Optional[str] = None) -> typing.Any:
|
||||
if not FAKE:
|
||||
return ensureResponseIsValid(
|
||||
requests.post(self._ogUrl(path), data=json.dumps(data), headers=self.headers, verify=self.verifyCert),
|
||||
@ -110,7 +117,7 @@ class OpenGnsysClient(object):
|
||||
# FAKE Connection :)
|
||||
return fake.post(path, data, errMsg)
|
||||
|
||||
def _get(self, path, errMsg=None):
|
||||
def _get(self, path: str, errMsg: typing.Optional[str] = None) -> typing.Any:
|
||||
if not FAKE:
|
||||
return ensureResponseIsValid(
|
||||
requests.get(self._ogUrl(path), headers=self.headers, verify=self.verifyCert),
|
||||
@ -119,7 +126,7 @@ class OpenGnsysClient(object):
|
||||
# FAKE Connection :)
|
||||
return fake.get(path, errMsg)
|
||||
|
||||
def _delete(self, path, errMsg=None):
|
||||
def _delete(self, path: str, errMsg: typing.Optional[str] = None) -> typing.Any:
|
||||
if not FAKE:
|
||||
return ensureResponseIsValid(
|
||||
requests.delete(self._ogUrl(path), headers=self.headers, verify=self.verifyCert),
|
||||
@ -127,13 +134,13 @@ class OpenGnsysClient(object):
|
||||
)
|
||||
return fake.delete(path, errMsg)
|
||||
|
||||
def connect(self):
|
||||
if self.auth is not None:
|
||||
def connect(self) -> None:
|
||||
if self.auth:
|
||||
return
|
||||
|
||||
cacheKey = 'auth{}{}'.format(self.endpoint, self.username)
|
||||
self.auth = self.cache.get(cacheKey)
|
||||
if self.auth is not None:
|
||||
if self.auth:
|
||||
return
|
||||
|
||||
auth = self._post(
|
||||
@ -149,17 +156,17 @@ class OpenGnsysClient(object):
|
||||
self.cache.put(cacheKey, self.auth, CACHE_VALIDITY)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
def version(self) -> str:
|
||||
logger.debug('Getting version')
|
||||
if self.cachedVersion is None:
|
||||
if not self.cachedVersion:
|
||||
# Retrieve Version & keep it
|
||||
info = self._get(urls.INFO, errMsg="Retrieving info")
|
||||
self.cachedVersion = info['version']
|
||||
|
||||
return self.cachedVersion
|
||||
return typing.cast(str, self.cachedVersion)
|
||||
|
||||
@ensureConnected
|
||||
def getOus(self):
|
||||
def getOus(self) -> typing.Any:
|
||||
# Returns an array of elements with:
|
||||
# 'id': OpenGnsys Id
|
||||
# 'name': OU name
|
||||
@ -167,7 +174,7 @@ class OpenGnsysClient(object):
|
||||
return self._get(urls.OUS, errMsg='Getting list of ous')
|
||||
|
||||
@ensureConnected
|
||||
def getLabs(self, ou):
|
||||
def getLabs(self, ou: str) -> typing.List[typing.MutableMapping[str, str]]:
|
||||
# Returns a list of available labs on an ou
|
||||
# /ous/{ouid}/labs
|
||||
# Take into accout that we must exclude the ones with "inremotepc" set to false.
|
||||
@ -175,7 +182,7 @@ class OpenGnsysClient(object):
|
||||
return [{'id': l['id'], 'name': l['name']} for l in self._get(urls.LABS.format(ou=ou), errMsg=errMsg) if l.get('inremotepc', False) is True]
|
||||
|
||||
@ensureConnected
|
||||
def getImages(self, ou):
|
||||
def getImages(self, ou: str) -> typing.List[typing.MutableMapping[str, str]]:
|
||||
# Returns a list of available labs on an ou
|
||||
# /ous/{ouid}/images
|
||||
# Take into accout that we must exclude the ones with "inremotepc" set to false.
|
||||
@ -183,7 +190,7 @@ class OpenGnsysClient(object):
|
||||
return [{'id': l['id'], 'name': l['name']} for l in self._get(urls.IMAGES.format(ou=ou), errMsg=errMsg) if l.get('inremotepc', False) is True]
|
||||
|
||||
@ensureConnected
|
||||
def reserve(self, ou, image, lab=0, maxtime=24):
|
||||
def reserve(self, ou: str, image: str, lab: int = 0, maxtime: int = 24) -> typing.MutableMapping[str, typing.Union[str, int]]:
|
||||
# This method is inteded to "get" a machine from OpenGnsys
|
||||
# The method used is POST
|
||||
# invokes /ous/{ouid}}/images/{imageid}/reserve
|
||||
@ -200,14 +207,14 @@ class OpenGnsysClient(object):
|
||||
'image': image,
|
||||
'lab': lab,
|
||||
'client': res['id'],
|
||||
'id': '.'.join((six.text_type(ou), six.text_type(res['lab']['id']), six.text_type(res['id']))),
|
||||
'id': '.'.join((str(ou), str(res['lab']['id']), str(res['id']))),
|
||||
'name': res['name'],
|
||||
'ip': res['ip'],
|
||||
'mac': ':'.join(re.findall('..', res['mac']))
|
||||
}
|
||||
|
||||
@ensureConnected
|
||||
def unreserve(self, machineId):
|
||||
def unreserve(self, machineId: str) -> typing.Any:
|
||||
# This method releases the previous reservation
|
||||
# Invoked every time we need to release a reservation (i mean, if a reservation is done, this will be called with the obtained id from that reservation)
|
||||
ou, lab, client = machineId.split('.')
|
||||
@ -215,30 +222,29 @@ class OpenGnsysClient(object):
|
||||
return self._delete(urls.UNRESERVE.format(ou=ou, lab=lab, client=client), errMsg=errMsg)
|
||||
|
||||
@ensureConnected
|
||||
def notifyURLs(self, machineId, loginURL, logoutURL):
|
||||
def notifyURLs(self, machineId: str, loginURL: str, logoutURL: str) -> typing.Any:
|
||||
ou, lab, client = machineId.split('.')
|
||||
errMsg = 'Notifying login/logout urls'
|
||||
data = {
|
||||
'urlLogin': loginURL,
|
||||
'urlLogout': logoutURL
|
||||
'urlLogin': loginURL,
|
||||
'urlLogout': logoutURL
|
||||
}
|
||||
|
||||
return self._post(urls.EVENTS.format(ou=ou, lab=lab, client=client), data, errMsg=errMsg)
|
||||
|
||||
@ensureConnected
|
||||
def notifyDeadline(self, machineId, deadLine):
|
||||
def notifyDeadline(self, machineId: str, deadLine: typing.Optional[int]) -> typing.Any:
|
||||
ou, lab, client = machineId.split('.')
|
||||
if deadLine is None:
|
||||
deadLine = 0
|
||||
deadLine = deadLine or 0
|
||||
errMsg = 'Notifying deadline'
|
||||
data = {
|
||||
'deadLine': deadLine
|
||||
'deadLine': deadLine
|
||||
}
|
||||
|
||||
return self._post(urls.SESSIONS.format(ou=ou, lab=lab, client=client), data, errMsg=errMsg)
|
||||
|
||||
@ensureConnected
|
||||
def status(self, id_):
|
||||
def status(self, id_: str) -> typing.Any:
|
||||
# This method gets the status of the machine
|
||||
# /ous/{uoid}/labs/{labid}/clients/{clientid}/status
|
||||
# possible status are ("off", "oglive", "busy", "linux", "windows", "macos" o "unknown").
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,14 +30,13 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from . import urls
|
||||
import copy
|
||||
import random
|
||||
import six
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from . import urls
|
||||
|
||||
__updated__ = '2017-09-29'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -145,7 +144,7 @@ IMAGES = [
|
||||
},
|
||||
]
|
||||
|
||||
RESERVE = {
|
||||
RESERVE: typing.Dict[str, typing.Any] = {
|
||||
"id": 4,
|
||||
"name": "pcpruebas",
|
||||
"mac": "4061860521FE",
|
||||
@ -184,13 +183,14 @@ STATUS_READY_WINDOWS = {
|
||||
|
||||
|
||||
# FAKE post
|
||||
def post(path, data, errMsg):
|
||||
logger.info('FAKE POST request to {} with {} data. ({})'.format(path, data, errMsg))
|
||||
def post(path: str, data: typing.Any, errMsg: typing.Optional[str] = None) -> typing.Any:
|
||||
logger.info('FAKE POST request to %s with %s data. (%s)', path, data, errMsg)
|
||||
if path == urls.LOGIN:
|
||||
return AUTH
|
||||
elif path == urls.RESERVE.format(ou=1, image=1) or path == urls.RESERVE.format(ou=1, image=2):
|
||||
|
||||
if path == urls.RESERVE.format(ou=1, image=1) or path == urls.RESERVE.format(ou=1, image=2):
|
||||
res = copy.deepcopy(RESERVE)
|
||||
res['name'] += six.text_type(random.randint(5000, 100000))
|
||||
res['name'] += str(random.randint(5000, 100000))
|
||||
res['mac'] = ''.join(random.choice('0123456789ABCDEF') for _ in range(12))
|
||||
return res
|
||||
|
||||
@ -198,33 +198,33 @@ def post(path, data, errMsg):
|
||||
|
||||
|
||||
# FAKE get
|
||||
def get(path, errMsg):
|
||||
logger.info('FAKE GET request to {}. ({})'.format(path, errMsg))
|
||||
def get(path, errMsg: typing.Optional[str]) -> typing.Any: # pylint: disable=too-many-return-statements
|
||||
logger.info('FAKE GET request to %s. (%s)', path, errMsg)
|
||||
if path == urls.INFO:
|
||||
return INFO
|
||||
elif path == urls.OUS:
|
||||
if path == urls.OUS:
|
||||
return OUS
|
||||
elif path == urls.LABS.format(ou=1):
|
||||
if path == urls.LABS.format(ou=1):
|
||||
return LABS
|
||||
elif path == urls.LABS.format(ou=2):
|
||||
if path == urls.LABS.format(ou=2):
|
||||
return [] # Empty
|
||||
elif path == urls.IMAGES.format(ou=1):
|
||||
if path == urls.IMAGES.format(ou=1):
|
||||
return IMAGES
|
||||
elif path == urls.IMAGES.format(ou=2):
|
||||
if path == urls.IMAGES.format(ou=2):
|
||||
return []
|
||||
elif path[-6:] == 'status':
|
||||
if path[-6:] == 'status':
|
||||
rnd = random.randint(0, 100)
|
||||
if rnd < 25:
|
||||
return STATUS_READY_LINUX
|
||||
return STATUS_OFF
|
||||
elif path[-6:] == 'events':
|
||||
if path[-6:] == 'events':
|
||||
return ''
|
||||
|
||||
raise Exception('Unknown FAKE URL on GET: {}'.format(path))
|
||||
|
||||
|
||||
def delete(path, errMsg):
|
||||
logger.info('FAKE DELETE request to {}. ({})'.format(path, errMsg))
|
||||
def delete(path: str, errMsg: typing.Optional[str]):
|
||||
logger.info('FAKE DELETE request to %s. (%s)', path, errMsg)
|
||||
# Right now, only "unreserve" uses delete, so simply return
|
||||
return UNRESERVE
|
||||
# raise Exception('Unknown FAKE URL on DELETE: {}'.format(path))
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,8 +30,6 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# API URL 1: https://www.informatica.us.es/~ramon/opengnsys/?url=opengnsys-api.yml
|
||||
# API URL 2: http://opengnsys.es/wiki/ApiRest
|
||||
|
||||
|
@ -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,20 +32,23 @@ 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
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators
|
||||
from defusedxml import minidom
|
||||
|
||||
from .OGService import OGService
|
||||
from .service import OGService
|
||||
from . import og
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core import Module
|
||||
from uds.core.environment import Environment
|
||||
|
||||
import logging
|
||||
import six
|
||||
|
||||
|
||||
__updated__ = '2017-10-16'
|
||||
@ -106,9 +109,9 @@ class OGProvider(ServiceProvider):
|
||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenGnsys'), required=True, tab=gui.ADVANCED_TAB)
|
||||
|
||||
# Own variables
|
||||
_api = None
|
||||
_api: typing.Optional[og.OpenGnsysClient] = None
|
||||
|
||||
def initialize(self, values=None):
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
"""
|
||||
We will use the "autosave" feature for form fields
|
||||
"""
|
||||
@ -116,7 +119,7 @@ class OGProvider(ServiceProvider):
|
||||
# Just reset _api connection variable
|
||||
self._api = None
|
||||
|
||||
if values is not None:
|
||||
if values:
|
||||
self.timeout.value = validators.validateTimeout(self.timeout.value)
|
||||
logger.debug('Endpoint: %s', self.endpoint)
|
||||
|
||||
@ -132,21 +135,21 @@ class OGProvider(ServiceProvider):
|
||||
pass
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
def endpoint(self) -> str:
|
||||
return 'https://{}:{}/opengnsys/rest'.format(self.host.value, self.port.value)
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
if self._api is None:
|
||||
def api(self) -> og.OpenGnsysClient:
|
||||
if not self._api:
|
||||
self._api = og.OpenGnsysClient(self.username.value, self.password.value, self.endpoint, self.cache, self.checkCert.isTrue())
|
||||
|
||||
logger.debug('Api: {}'.format(self._api))
|
||||
logger.debug('Api: %s', self._api)
|
||||
return self._api
|
||||
|
||||
def resetApi(self):
|
||||
def resetApi(self) -> None:
|
||||
self._api = None
|
||||
|
||||
def testConnection(self):
|
||||
def testConnection(self) -> typing.List[typing.Any]:
|
||||
"""
|
||||
Test that conection to OpenGnsys server is fine
|
||||
|
||||
@ -164,7 +167,7 @@ class OGProvider(ServiceProvider):
|
||||
return [True, _('OpenGnsys test connection passed')]
|
||||
|
||||
@staticmethod
|
||||
def test(env, data):
|
||||
def test(env: 'Environment', data: 'Module.ValuesType') -> typing.List[typing.Any]:
|
||||
"""
|
||||
Test ovirt Connectivity
|
||||
|
||||
@ -182,20 +185,20 @@ class OGProvider(ServiceProvider):
|
||||
"""
|
||||
return OGProvider(env, data).testConnection()
|
||||
|
||||
def getUDSServerAccessUrl(self):
|
||||
def getUDSServerAccessUrl(self) -> str:
|
||||
return self.udsServerAccessUrl.value
|
||||
|
||||
def reserve(self, ou, image, lab=0, maxtime=0):
|
||||
def reserve(self, ou: str, image: str, lab: int = 0, maxtime: int = 0) -> typing.Any:
|
||||
return self.api.reserve(ou, image, lab, maxtime)
|
||||
|
||||
def unreserve(self, machineId):
|
||||
def unreserve(self, machineId: str) -> typing.Any:
|
||||
return self.api.unreserve(machineId)
|
||||
|
||||
def notifyEvents(self, machineId, loginURL, logoutURL):
|
||||
def notifyEvents(self, machineId: str, loginURL: str, logoutURL: str) -> typing.Any:
|
||||
return self.api.notifyURLs(machineId, loginURL, logoutURL)
|
||||
|
||||
def notifyDeadline(self, machineId, deadLine):
|
||||
def notifyDeadline(self, machineId: str, deadLine: typing.Optional[int]) -> typing.Any:
|
||||
return self.api.notifyDeadline(machineId, deadLine)
|
||||
|
||||
def status(self, machineId):
|
||||
def status(self, machineId: str) -> typing.Any:
|
||||
return self.api.status(machineId)
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,14 +30,16 @@
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from uds.core.services import Publication
|
||||
from uds.core.util.state import State
|
||||
from uds.models.util import getSqlDatetime
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2019-02-07'
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .service import OGService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -46,29 +48,20 @@ class OGPublication(Publication):
|
||||
"""
|
||||
This class provides the publication of a oVirtLinkedService
|
||||
"""
|
||||
_name = ''
|
||||
_name: str = ''
|
||||
|
||||
suggestedTime = 5 # : Suggested recheck time if publication is unfinished in seconds
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
This method will be invoked by default __init__ of base class, so it gives
|
||||
us the oportunity to initialize whataver we need here.
|
||||
def service(self) -> 'OGService':
|
||||
return typing.cast('OGService', super().service())
|
||||
|
||||
In our case, we setup a few attributes..
|
||||
"""
|
||||
|
||||
# We do not check anything at marshal method, so we ensure that
|
||||
# default values are correctly handled by marshal.
|
||||
self._name = ''
|
||||
|
||||
def marshal(self):
|
||||
def marshal(self) -> bytes:
|
||||
"""
|
||||
returns data from an instance of Sample Publication serialized
|
||||
"""
|
||||
return '\t'.join(['v1', self._name]).encode('utf8')
|
||||
|
||||
def unmarshal(self, data):
|
||||
def unmarshal(self, data: bytes) -> None:
|
||||
"""
|
||||
deserializes the data and loads it inside instance.
|
||||
"""
|
||||
@ -76,50 +69,24 @@ class OGPublication(Publication):
|
||||
if vals[0] == 'v1':
|
||||
self._name = vals[1]
|
||||
|
||||
def publish(self):
|
||||
def publish(self) -> str:
|
||||
"""
|
||||
Realizes the publication of the service
|
||||
"""
|
||||
self._name = 'Publication {}'.format(getSqlDatetime())
|
||||
return State.FINISHED
|
||||
|
||||
def checkState(self):
|
||||
def checkState(self) -> str:
|
||||
"""
|
||||
Checks state of publication creation
|
||||
"""
|
||||
return State.FINISHED
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
In our case, finish does nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
def reasonOfError(self):
|
||||
"""
|
||||
If a publication produces an error, here we must notify the reason why
|
||||
it happened. This will be called just after publish or checkState
|
||||
if they return State.ERROR
|
||||
|
||||
Returns an string, in our case, set at checkState
|
||||
"""
|
||||
def reasonOfError(self) -> str:
|
||||
return 'No error possible :)'
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
This is called once a publication is no more needed.
|
||||
|
||||
This method do whatever needed to clean up things, such as
|
||||
removing created "external" data (environment gets cleaned by core),
|
||||
etc..
|
||||
|
||||
The retunred value is the same as when publishing, State.RUNNING,
|
||||
State.FINISHED or State.ERROR.
|
||||
"""
|
||||
def destroy(self) -> str:
|
||||
return State.FINISHED
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Do same thing as destroy
|
||||
"""
|
||||
def cancel(self) -> str:
|
||||
return self.destroy()
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -30,18 +30,23 @@
|
||||
"""
|
||||
.. 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 .OGDeployment import OGDeployment
|
||||
from .OGPublication import OGPublication
|
||||
from . import helpers
|
||||
|
||||
from uds.core.ui import gui
|
||||
|
||||
import logging
|
||||
from . import helpers
|
||||
from .deployment import OGDeployment
|
||||
from .publication import OGPublication
|
||||
|
||||
__updated__ = '2018-11-08'
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .provider import OGProvider
|
||||
from uds.core import Module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -134,17 +139,7 @@ class OGService(Service):
|
||||
ov = gui.HiddenField(value=None)
|
||||
ev = gui.HiddenField(value=None) # We need to keep the env so we can instantiate the Provider
|
||||
|
||||
def initialize(self, values):
|
||||
"""
|
||||
We check here form values to see if they are valid.
|
||||
|
||||
Note that we check them FROM variables, that already has been
|
||||
initialized by __init__ method of base class, before invoking this.
|
||||
"""
|
||||
if values is not None:
|
||||
pass
|
||||
|
||||
def initGui(self):
|
||||
def initGui(self) -> None:
|
||||
"""
|
||||
Loads required values inside
|
||||
"""
|
||||
@ -154,22 +149,25 @@ class OGService(Service):
|
||||
self.ov.setDefValue(self.parent().serialize())
|
||||
self.ev.setDefValue(self.parent().env.key)
|
||||
|
||||
def status(self, machineId):
|
||||
def parent(self) -> 'OGProvider':
|
||||
return typing.cast('OGProvider', super().parent())
|
||||
|
||||
def status(self, machineId: str) -> typing.Any:
|
||||
return self.parent().status(machineId)
|
||||
|
||||
def reserve(self):
|
||||
def reserve(self) -> typing.Any:
|
||||
return self.parent().reserve(self.ou.value, self.image.value, self.lab.value, self.maxReservationTime.num())
|
||||
|
||||
def unreserve(self, machineId):
|
||||
def unreserve(self, machineId: str) -> typing.Any:
|
||||
return self.parent().unreserve(machineId)
|
||||
|
||||
def notifyEvents(self, machineId, serviceUUID):
|
||||
def notifyEvents(self, machineId: str, serviceUUID: str) -> typing.Any:
|
||||
return self.parent().notifyEvents(machineId, self.getLoginNotifyURL(serviceUUID), self.getLogoutNotifyURL(serviceUUID))
|
||||
|
||||
def notifyDeadline(self, machineId, deadLine):
|
||||
def notifyDeadline(self, machineId: str, deadLine: typing.Optional[int]) -> typing.Any:
|
||||
return self.parent().notifyDeadline(machineId, deadLine)
|
||||
|
||||
def _notifyURL(self, uuid, message):
|
||||
def _notifyURL(self, uuid: str, message: str) -> str:
|
||||
# The URL is "GET messages URL".
|
||||
return '{accessURL}rest/actor/PostThoughGet/{uuid}/{message}'.format(
|
||||
accessURL=self.parent().getUDSServerAccessUrl(),
|
||||
@ -177,8 +175,8 @@ class OGService(Service):
|
||||
message=message
|
||||
)
|
||||
|
||||
def getLoginNotifyURL(self, serviceUUID):
|
||||
def getLoginNotifyURL(self, serviceUUID: str) -> str:
|
||||
return self._notifyURL(serviceUUID, 'login')
|
||||
|
||||
def getLogoutNotifyURL(self, serviceUUID):
|
||||
def getLogoutNotifyURL(self, serviceUUID: str) -> str:
|
||||
return self._notifyURL(serviceUUID, 'logout')
|
Loading…
Reference in New Issue
Block a user