Statistics
| Branch: | Tag: | Revision:

root / commissioning / clients / http.py @ 9f1a1bd0

History | View | Annotate | Download (7.8 kB)

1
#!/usr/bin/env python
2

    
3
from httplib import HTTPConnection, HTTPException
4
from urlparse import urlparse
5
from commissioning import Callpoint
6

    
7
import logging
8

    
9
from json import loads as json_loads, dumps as json_dumps
10

    
11

    
12
def init_logger_file(name, level='DEBUG'):
13
    logger = logging.getLogger(name)
14
    handler = logging.FileHandler(name + '.log')
15
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
16
    handler.setFormatter(formatter)
17
    logger.addHandler(handler)
18
    level = getattr(logging, level, logging.DEBUG)
19
    logger.setLevel(level)
20
    return logger
21

    
22
def init_logger_stdout(name, level='DEBUG'):
23
    logger = logging.getLogger(name)
24
    from sys import stdout
25
    handler = logging.StreamHandler(stdout)
26
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
27
    handler.setFormatter(formatter)
28
    logger.addHandler(handler)
29
    level = getattr(logging, level, logging.DEBUG)
30
    logger.setLevel(level)
31
    return logger
32

    
33

    
34
class NULLConnection(object):
35

    
36
    def __init__(self, host, port):
37
        self.host = host
38
        self.port = port
39

    
40
    def request(self, *args):
41
        return None
42

    
43
    def getresponse(self, *args):
44
        return None
45

    
46

    
47
class quota_holder_allocation(object):
48
    client = None
49
    serial = None
50

    
51
    def __init__(self, *allocations, **pre_allocations):
52
        allocations = canonify_list_of_allocations(allocations)
53
        pre_allocations = pre_allocations.items()
54
        pre_allocations = [(name, pre, 0) for name, pre in pre_allocations]
55
        pre_allocations = canonify_list_of_allocations(pre_allocations)
56
        allocations.extend(pre_allocations)
57
        self.allocations = allocations
58

    
59
        if self.client is None:
60
            self.client = QuotaholderClient.instance()
61

    
62
    def __call__(self, func):
63
        def wrapped(*args, **kw):
64
            client = self.client
65
            serial = client.get_provision(self.allocations)
66
            try:
67
                ret = func(*args, **kw)
68
            except Exception:
69
                client.recall_provision(serial)
70
                raise
71
            else:
72
                client.commit_provision(serial)
73

    
74
            return ret
75

    
76
        return wrapped
77

    
78
    def __enter__(self):
79
        self.serial = self.client.get_provision(self.allocations)
80

    
81
    def __exit__(self, exctype, exc, traceback):
82
        serial = self.serial
83
        client = self.client
84

    
85
        if exctype is None:
86
            client.commit_provision(serial)
87
        else:
88
            client.recall_provision(serial)
89
            raise
90

    
91

    
92
class GenericHTTPClient(Callpoint):
93
    """Synchronous http client for quota holder API"""
94

    
95
    appname = 'http'
96
    _http_client = None
97
    quota_holder_allocation = quota_holder_allocation
98

    
99
    def init_connection(self, connection):
100
        self.url = connection
101

    
102
        class quota_holder_allocator_class(quota_holder_allocation):
103
            client = self
104

    
105
        self.quota_holder_allocation = quota_holder_allocator_class
106
        self.logger = logging.getLogger(self.appname)
107

    
108
    @classmethod
109
    def instance(cls):
110
        client = cls._http_client
111
        if client is None:
112
            url = "http://127.0.0.1:8000/%s/%s" % (cls.appname, cls.version)
113
            client = cls(url)
114
            self._http_client = client
115

    
116
        return client
117

    
118
    def commit(self):
119
        return
120

    
121
    def rollback(self):
122
        return
123

    
124
    def debug(self, fmt, *args):
125
        self.logger.debug(fmt % args)
126

    
127
    def do_make_call(self, api_call, data):
128
        url = urlparse(self.url)
129
        scheme = url.scheme
130
        Connection = HTTPConnection
131
        if scheme == 'http':
132
            port = 80
133
        elif scheme == 'https':
134
            port = 443
135
        elif scheme == 'null':
136
            Connection = NULLConnection
137
        else:
138
            raise ValueError("Unsupported scheme %s" % (scheme,))
139

    
140
        path = url.path.strip('/')
141
        path = ('/' + path + '/' + api_call) if path else ('/' + api_Call)
142

    
143
        netloc = url.netloc.rsplit(':', 1)
144
        netloclen = len(netloc)
145
        if netloclen == 1:
146
            host = netloc[0]
147
        elif netloclen == 2:
148
            host, port = netloc
149
        else:
150
            msg = "Unsupported network location type '%s'" % (netloc,)
151
            raise ValueError(msg)
152

    
153
        self.debug("Connecting to %s:%s\n>>>", host, port)
154
        conn = Connection(host, port)
155

    
156
        if (api_call.startswith('list') or
157
            api_call.startswith('get') or
158
            api_call.startswith('read')):
159

    
160
                method = 'GET'
161
        else:
162
                method = 'POST'
163

    
164
        json_data = self.json_dumps(data)
165
        self.debug("%s %s\n%s\n<<<\n", method, path, json_data)
166

    
167
        req = conn.request(method, path, body=json_data)
168
        resp = conn.getresponse()
169
        self.debug(">>>\nStatus: %s", resp.status)
170

    
171
        for name, value in resp.getheaders():
172
            self.debug("%s: %s", name, value)
173

    
174
        body = ''
175
        while 1:
176
            s = resp.read() 
177
            if not s:
178
                break
179
            body += s
180

    
181
        self.debug("\n%s\n<<<\n", body)
182

    
183
        status = int(resp.status)
184
        if status == 200:
185
            if body:
186
                body = json_loads(body)
187
            return body
188
        else:
189
            return body
190

    
191
        raise IOError("Call Failed", str(resp.status))
192

    
193
API_Callpoint = GenericHTTPClient
194

    
195

    
196
def main():
197
    from sys import argv, stdout
198
    from os.path import basename, expanduser
199
    from time import time
200
    from commissioning import get_callpoint
201

    
202
    progname = basename(argv[0])
203
    if progname == 'http.py':
204
        if len(argv) < 2:
205
            usage = "./http.py <appname> <app args...>"
206
            print(usage)
207
            raise SystemExit
208

    
209
        argv = argv[1:]
210
        progname = basename(argv[0])
211

    
212
    init_logger_stdout(progname)
213

    
214
    pointname = 'clients.' + progname
215
    API_Callpoint = get_callpoint(pointname, automake='http')
216
    api = API_Callpoint.api_spec
217

    
218
    usage = "API Calls:\n\n"
219

    
220
    for call_name in sorted(api.call_names()):
221
        canonical = api.input_canonical(call_name)
222
        argstring = canonical.tostring(multiline=1, showopts=0)
223
        usage += call_name + '.' + argstring + '\n\n'
224

    
225
    import argparse
226
    parser = argparse.ArgumentParser    (
227
            formatter_class =   argparse.RawDescriptionHelpFormatter,
228
            description     =   "%s http client" % (progname,),
229
            epilog          =   usage,
230
    )
231

    
232
    urlhelp = 'set %s server base url' % (progname,)
233
    parser.add_argument('--url', type=str, dest='url',
234
                        action='store', help=urlhelp)
235

    
236
    jsonhelp = 'intepret data as json'
237
    parser.add_argument('--json', dest='json_data', action='store_false',
238
                        default=True, help=jsonhelp)
239

    
240
    callhelp = 'api call to perform'
241
    parser.add_argument('api_call', type=str, action='store', nargs=1,
242
                        help=callhelp)
243

    
244
    arghelp = 'data to provide to api call'
245
    parser.add_argument('data', type=str, action='store', nargs='?',
246
                        help=callhelp)
247

    
248
    urlfilepath = expanduser('~/.qholderrc')
249

    
250
    def get_url():
251
        try:
252
            with open(urlfilepath) as f:
253
                url = f.read()
254
        except Exception:
255
            m = "Cannot load url from %s. Try --url." % (urlfilepath,)
256
            raise ValueError(m)
257
        return url
258

    
259
    def set_url(url):
260
        url = url.strip('/')
261
        with open(urlfilepath, "w") as f:
262
            f.write(url)
263
        print "Base URL set to '%s'" % (url,)
264

    
265
    args = parser.parse_args(argv[1:])
266

    
267
    api_call = args.api_call[0]
268
    api.input_canonical(api_call)
269

    
270
    if args.url:
271
        set_url(args.url)
272

    
273
    url = get_url()
274

    
275
    data = args.data
276

    
277
    if data == '-':
278
        from sys import stdin
279
        data = stdin.read()
280

    
281
    if not data:
282
        data = None
283

    
284
    client = API_Callpoint(url)
285
    print(client.make_call_from_json(api_call, data))
286

    
287

    
288
if __name__ == '__main__':
289
    main()
290