protocol_client.py 5.35 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# Copyright (C) 2015 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from ws4py.client.threadedclient import WebSocketClient
import json
import time
import logging
20
import random
21
from protocol import STATUS
22 23 24 25 26


LOG = logging.getLogger(__name__)


27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
class UIClientError(Exception):
    """UIClient Exception class"""


class TimeOutError(UIClientError):
    """A client request timed out"""

class UnexpectedResponseError(UIClientError):
    """The protocol server response was not as expected"""

    def __init__(self, *args, **kw):
        """:param response: a keyword argument containing the repsonse"""
        self.response = kw.pop('response', None)
        super(UnexpectedResponseError, self).__init__(*args, **kw)


43
class UIClient(WebSocketClient):
44
    """Web Socket Client for Agkyra"""
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    buf, authenticated, ready = {}, False, False

    def __init__(self, session):
        self.ui_id = session['ui_id']
        super(UIClient, self).__init__(session['address'])

    def opened(self):
        """On connection, authenticate or close"""
        if self.ui_id:
            self.send(json.dumps(dict(method='post', ui_id=self.ui_id)))
        else:
            self.close()

    def closed(self, code, reason):
        """After the client is closed"""
60
        LOG.debug('Client exits with %s, %s' % (code, reason))
61 62 63

    def wait_until_ready(self, timeout=20):
        """Wait until client is connected"""
64
        while timeout and not self.ready:
65
            time.sleep(1)
66
            timeout -= 1
67 68
        if not self.ready:
            raise TimeOutError('UI client timed out while waiting to be ready')
69 70
        return self.ready

71 72 73
    def wait_until_syncing(self, timeout=20):
        """Wait until session reaches syncing status"""
        status = self.get_status()
74
        while timeout and status['code'] != STATUS['SYNCING']:
75 76 77
            time.sleep(1)
            status = self.get_status()
            timeout -= 1
78 79
        if status['code'] != STATUS['SYNCING']:
            raise TimeOutError('Still not syncing')
80 81 82 83

    def wait_until_paused(self, timeout=20):
        """Wait until session reaches paused status"""
        status = self.get_status()
84
        while timeout and status['code'] != STATUS['PAUSED']:
85 86 87
            time.sleep(1)
            status = self.get_status()
            timeout -= 1
88 89
        if status['code'] != STATUS['PAUSED']:
            raise TimeOutError('Still not paused')
90

91 92 93 94 95
    def received_message(self, m):
        """handle server responces according to the protocol"""
        msg = json.loads('%s' % m)
        {
            'post ui_id': self.recv_authenticate,
96
            'post init': self.recv_init,
97 98
            'post start': self.recv_start,
            'post pause': self.recv_pause,
99
            'get status': self.recv_get_status,
100 101 102 103 104 105 106 107 108 109
        }[msg['action']](msg)

    # Request handlers
    def send_get_status(self):
        """Request: GET STATUS"""
        self.send(json.dumps(dict(method='get', path='status')))

    # Receive handlers
    def recv_authenticate(self, msg):
        """Receive: client authentication response"""
110 111 112
        if 'ACCEPTED' not in msg:
            raise UnexpectedResponseError(
                'Client authentication failed', response=msg)
113 114
        self.ready = True

115 116 117 118 119
    def recv_init(self, msg):
        """Receive: init response"""
        if 'OK' not in msg:
            raise UnexpectedResponseError('Init failed', response=msg)

120 121
    def recv_start(self, msg):
        """Receive: start response"""
122 123
        if 'OK' not in msg:
            raise UnexpectedResponseError('Start failed', response=msg)
124 125 126

    def recv_pause(self, msg):
        """Receive: start response"""
127 128
        if 'OK' not in msg:
            raise UnexpectedResponseError('Pause failed', response=msg)
129

130 131
    def recv_get_status(self, msg):
        """Receive: GET STATUS"""
132 133
        if 'code' not in msg:
            raise UnexpectedResponseError('Get status failed', response=msg)
134 135 136 137 138 139 140 141
        self.buf[msg['action']] = msg

    # API methods
    def get_status(self):
        """Ask server for status, return status"""
        self.wait_until_ready()
        self.send_get_status()
        while 'get status' not in self.buf:
142
            time.sleep(random.random())
143
        return self.buf.pop('get status')
144

145 146 147 148
    def _post(self, path):
        """send json with action=POST and path=path"""
        self.send(json.dumps(dict(method='post', path=path)))

149 150 151
    def shutdown(self):
        """Request: POST SHUTDOWN"""
        self.wait_until_ready()
152 153
        self._post('shutdown')

154 155 156 157 158
    def init(self):
        """Request: POST INIT"""
        self.wait_until_ready()
        self._post('init')

159 160 161 162 163 164 165 166 167
    def start(self):
        """Request: POST START"""
        self.wait_until_ready()
        self._post('start')

    def pause(self):
        """Request: POST PAUSE"""
        self.wait_until_ready()
        self._post('pause')