Statistics
| Branch: | Tag: | Revision:

root / doc / design-impexp2.rst @ 2a50e2e8

History | View | Annotate | Download (22.2 kB)

1
==================================
2
Design for import/export version 2
3
==================================
4

    
5
.. contents:: :depth: 4
6

    
7
Current state and shortcomings
8
------------------------------
9

    
10
Ganeti 2.2 introduced :doc:`inter-cluster instance moves <design-2.2>`
11
and replaced the import/export mechanism with the same technology. It's
12
since shown that the chosen implementation was too complicated and and
13
can be difficult to debug.
14

    
15
The old implementation is henceforth called "version 1". It used
16
``socat`` in combination with a rather complex tree of ``bash`` and
17
Python utilities to move instances between clusters and import/export
18
them inside the cluster. Due to protocol limitations, the master daemon
19
starts a daemon on the involved nodes and then keeps polling a status
20
file for updates. A non-trivial number of timeouts ensures that jobs
21
don't freeze.
22

    
23
In version 1, the destination node would start a daemon listening on a
24
random TCP port. Upon receiving the destination information, the source
25
node would temporarily stop the instance, create snapshots, and start
26
exporting the data by connecting to the destination. The random TCP port
27
is chosen by the operating system by binding the socket to port 0.
28
While this is a somewhat elegant solution, it causes problems in setups
29
with restricted connectivity (e.g. iptables).
30

    
31
Another issue encountered was with dual-stack IPv6 setups. ``socat`` can
32
only listen on one protocol, IPv4 or IPv6, at a time. The connecting
33
node can not simply resolve the DNS name, but it must be told the exact
34
IP address.
35

    
36
Instance OS definitions can provide custom import/export scripts. They
37
were working well in the early days when a filesystem was usually
38
created directly on the block device. Around Ganeti 2.0 there was a
39
transition to using partitions on the block devices. Import/export
40
scripts could no longer use simple ``dump`` and ``restore`` commands,
41
but usually ended up doing raw data dumps.
42

    
43

    
44
Proposed changes
45
----------------
46

    
47
Unlike in version 1, in version 2 the destination node will connect to
48
the source. The active side is swapped. This design assumes the
49
following design documents have been implemented:
50

    
51
- :doc:`design-x509-ca`
52
- :doc:`design-http-server`
53

    
54
The following design is mostly targetted at inter-cluster instance
55
moves. Intra-cluster import and export use the same technology, but do
56
so in a less complicated way (e.g. reusing the node daemon certificate
57
in version 1).
58

    
59
Support for instance OS import/export scripts, which have been in Ganeti
60
since the beginning, will be dropped with this design. Should the need
61
arise, they can be re-added later.
62

    
63

    
64
Software requirements
65
+++++++++++++++++++++
66

    
67
- HTTP client: cURL/pycURL (already used for inter-node RPC and RAPI
68
  client)
69
- Authentication: X509 certificates (server and client)
70

    
71

    
72
Transport
73
+++++++++
74

    
75
Instead of a home-grown, mostly raw protocol the widely used HTTP
76
protocol will be used. Ganeti already uses HTTP for its :doc:`Remote API
77
<rapi>` and inter-node communication. Encryption and authentication will
78
be implemented using SSL and X509 certificates.
79

    
80

    
81
SSL certificates
82
++++++++++++++++
83

    
84
The source machine will identify connecting clients by their SSL
85
certificate. Unknown certificates will be refused.
86

    
87
Version 1 created a new self-signed certificate per instance
88
import/export, allowing the certificate to be used as a Certificate
89
Authority (CA). This worked by means of starting a new ``socat``
90
instance per instance import/export.
91

    
92
Under the version 2 model, a continously running HTTP server will be
93
used. This disallows the use of self-signed certificates for
94
authentication as the CA needs to be the same for all issued
95
certificates.
96

    
97
See the :doc:`separate design document for more details on how the
98
certificate authority will be implemented <design-x509-ca>`.
99

    
100
Local imports/exports will, like version 1, use the node daemon's
101
certificate/key. Doing so allows the verification of local connections.
102
The client's certificate can be exported to the CGI/FastCGI handler
103
using lighttpd's ``ssl.verifyclient.exportcert`` setting. If a
104
cluster-local import/export is being done, the handler verifies if the
105
used certificate matches with the local node daemon key.
106

    
107

    
108
Source
109
++++++
110

    
111
The source can be the same physical machine as the destination, another
112
node in the same cluster, or a node in another cluster. A
113
physical-to-virtual migration mechanism could be implemented as an
114
alternative source.
115

    
116
In the case of a traditional import, the source is usually a file on the
117
source machine. For exports and remote imports, the source is an
118
instance's raw disk data. In all cases the transported data is opaque to
119
Ganeti.
120

    
121
All nodes of a cluster will run an instance of Lighttpd. The
122
configuration is automatically generated when starting Ganeti. The HTTP
123
server is configured to listen on IPv4 and IPv6 simultaneously.
124
Imports/exports will use a dedicated TCP port, similar to the Remote
125
API.
126

    
127
See the separate :ref:`HTTP server design document
128
<http-srv-shortcomings>` for why Ganeti's existing, built-in HTTP server
129
is not a good choice.
130

    
131
The source cluster is provided with a X509 Certificate Signing Request
132
(CSR) for a key private to the destination cluster.
133

    
134
After shutting down the instance, creating snapshots and restarting the
135
instance the master will sign the destination's X509 certificate using
136
the :doc:`X509 CA <design-x509-ca>` once per instance disk. Instead of
137
using another identifier, the certificate's serial number (:ref:`never
138
reused <x509-ca-serial>`) and fingerprint are used to identify incoming
139
requests. Once ready, the master will call an RPC method on the source
140
node and provide it with the input information (e.g. file paths or block
141
devices) and the certificate identities.
142

    
143
The RPC method will write the identities to a place accessible by the
144
HTTP request handler, generate unique transfer IDs and return them to
145
the master. The transfer ID could be a filename containing the
146
certificate's serial number, fingerprint and some disk information. The
147
file containing the per-transfer information is signed using the node
148
daemon key and the signature written to a separate file.
149

    
150
Once everything is in place, the master sends the certificates, the data
151
and notification URLs (which include the transfer IDs) and the public
152
part of the source's CA to the job submitter. Like in version 1,
153
everything will be signed using the cluster domain secret.
154

    
155
Upon receiving a request, the handler verifies the identity and
156
continues to stream the instance data. The serial number and fingerprint
157
contained in the transfer ID should be matched with the certificate
158
used. If a cluster-local import/export was requested, the remote's
159
certificate is verified with the local node daemon key. The signature of
160
the information file from which the handler takes the path of the block
161
device (and more) is verified using the local node daemon certificate.
162
There are two options for handling requests, :ref:`CGI
163
<lighttpd-cgi-opt>` and :ref:`FastCGI <lighttpd-fastcgi-opt>`.
164

    
165
To wait for all requests to finish, the master calls another RPC method.
166
The destination should notify the source once it's done with downloading
167
the data. Since this notification may never arrive (e.g. network
168
issues), an additional timeout needs to be used.
169

    
170
There is no good way to avoid polling as the HTTP requests will be
171
handled asynchronously in another process. Once, and if, implemented
172
:ref:`RPC feedback <rpc-feedback>` could be used to combine the two RPC
173
methods.
174

    
175
Upon completion of the transfer requests, the instance is removed if
176
requested.
177

    
178

    
179
.. _lighttpd-cgi-opt:
180

    
181
Option 1: CGI
182
~~~~~~~~~~~~~
183

    
184
While easier to implement, this option requires the HTTP server to
185
either run as "root" or a so-called SUID binary to elevate the started
186
process to run as "root".
187

    
188
The export data can be sent directly to the HTTP server without any
189
further processing.
190

    
191

    
192
.. _lighttpd-fastcgi-opt:
193

    
194
Option 2: FastCGI
195
~~~~~~~~~~~~~~~~~
196

    
197
Unlike plain CGI, FastCGI scripts are run separately from the webserver.
198
The webserver talks to them via a Unix socket. Webserver and scripts can
199
run as separate users. Unlike for CGI, there are almost no bootstrap
200
costs attached to each request.
201

    
202
The FastCGI protocol requires data to be sent in length-prefixed
203
packets, something which wouldn't be very efficient to do in Python for
204
large amounts of data (instance imports/exports can be hundreds of
205
gigabytes). For this reason the proposal is to use a wrapper program
206
written in C (e.g. `fcgiwrap
207
<http://nginx.localdomain.pl/wiki/FcgiWrap>`_) and to write the handler
208
like an old-style CGI program with standard input/output. If data should
209
be copied from a file, ``cat``, ``dd`` or ``socat`` can be used (see
210
note about :ref:`sendfile(2)/splice(2) with Python <python-sendfile>`).
211

    
212
The bootstrap cost associated with starting a Python interpreter for
213
a disk export is expected to be negligible.
214

    
215
The `spawn-fcgi <http://cgit.stbuehler.de/gitosis/spawn-fcgi/about/>`_
216
program will be used to start the CGI wrapper as "root".
217

    
218
FastCGI is, in the author's opinion, the better choice as it allows user
219
separation. As a first implementation step the export handler can be run
220
as a standard CGI program. User separation can be implemented as a
221
second step.
222

    
223

    
224
Destination
225
+++++++++++
226

    
227
The destination can be the same physical machine as the source, another
228
node in the same cluster, or a node in another cluster. While not
229
considered in this design document, instances could be exported from the
230
cluster by implementing an external client for exports.
231

    
232
For traditional exports the destination is usually a file on the
233
destination machine. For imports and remote exports, the destination is
234
an instance's disks. All transported data is opaque to Ganeti.
235

    
236
Before an import can be started, an RSA key and corresponding
237
Certificate Signing Request (CSR) must be generated using the new opcode
238
``OpInstanceImportPrepare``. The returned information is signed using
239
the cluster domain secret. The RSA key backing the CSR must not leave
240
the destination cluster. After being passed through a third party, the
241
source cluster will generate signed certificates from the CSR.
242

    
243
Once the request for creating the instance arrives at the master daemon,
244
it'll create the instance and call an RPC method on the instance's
245
primary node to download all data. The RPC method does not return until
246
the transfer is complete or failed (see :ref:`EXP_SIZE_FD <exp-size-fd>`
247
and :ref:`RPC feedback <rpc-feedback>`).
248

    
249
The node will use pycURL to connect to the source machine and identify
250
itself with the signed certificate received. pycURL will be configured
251
to write directly to a file descriptor pointing to either a regular file
252
or block device. The file descriptor needs to point to the correct
253
offset for resuming downloads.
254

    
255
Using cURL's multi interface, more than one transfer can be made at the
256
same time. While parallel transfers are used by the version 1
257
import/export, it can be decided at a later time whether to use them in
258
version 2 too. More investigation is necessary to determine whether
259
``CURLOPT_MAXCONNECTS`` is enough to limit the number of connections or
260
whether more logic is necessary.
261

    
262
If a transfer fails before it's finished (e.g. timeout or network
263
issues) it should be retried using an exponential backoff delay. The
264
opcode submitter can specify for how long the transfer should be
265
retried.
266

    
267
At the end of a transfer, succssful or not, the source cluster must be
268
notified. A the same time the RSA key needs to be destroyed.
269

    
270
Support for HTTP proxies can be implemented by setting
271
``CURLOPT_PROXY``. Proxies could be used for moving instances in/out of
272
restricted network environments or across protocol borders (e.g. IPv4
273
networks unable to talk to IPv6 networks).
274

    
275

    
276
The big picture for instance moves
277
----------------------------------
278

    
279
#. ``OpInstanceImportPrepare`` (destination cluster)
280

    
281
  Create RSA key and CSR (certificate signing request), return signed
282
  with cluster domain secret.
283

    
284
#. ``OpBackupPrepare`` (source cluster)
285

    
286
  Becomes a no-op in version 2, but see :ref:`backwards-compat`.
287

    
288
#. ``OpBackupExport`` (source cluster)
289

    
290
  - Receives destination cluster's CSR, verifies signature using
291
    cluster domain secret.
292
  - Creates certificates using CSR and :doc:`cluster CA
293
    <design-x509-ca>`, one for each disk
294
  - Stop instance, create snapshots, start instance
295
  - Prepare HTTP resources on node
296
  - Send certificates, URLs and CA certificate to job submitter using
297
    feedback mechanism
298
  - Wait for all transfers to finish or fail (with timeout)
299
  - Remove snapshots
300

    
301
#. ``OpInstanceCreate`` (destination cluster)
302

    
303
  - Receives certificates signed by destination cluster, verifies
304
    certificates and URLs using cluster domain secret
305

    
306
    Note that the parameters should be implemented in a generic way
307
    allowing future extensions, e.g. to download disk images from a
308
    public, remote server. The cluster domain secret allows Ganeti to
309
    check data received from a third party, but since this won't work
310
    with such extensions, other checks will have to be designed.
311

    
312
  - Create block devices
313
  - Download every disk from source, verified using remote's CA and
314
    authenticated using signed certificates
315
  - Destroy RSA key and certificates
316
  - Start instance
317

    
318
.. TODO: separate create from import?
319

    
320

    
321
.. _impexp2-http-resources:
322

    
323
HTTP resources on source
324
------------------------
325

    
326
The HTTP resources listed below will be made available by the source
327
machine. The transfer ID is generated while preparing the export and is
328
unique per disk and instance. No caching should be used and the
329
``Pragma`` (HTTP/1.0) and ``Cache-Control`` (HTTP/1.1) headers set
330
accordingly by the server.
331

    
332
``GET /transfers/[transfer_id]/contents``
333
  Dump disk contents. Important request headers:
334

    
335
  ``Accept`` (:rfc:`2616`, section 14.1)
336
    Specify preferred media types. Only one type is supported in the
337
    initial implementation:
338

    
339
    ``application/octet-stream``
340
      Request raw disk content.
341

    
342
    If support for more media types were to be implemented in the
343
    future, the "q" parameter used for "indicating a relative quality
344
    factor" needs to be used. In the meantime parameters need to be
345
    expected, but can be ignored.
346

    
347
    If support for OS scripts were to be re-added in the future, the
348
    MIME type ``application/x-ganeti-instance-export`` is hereby
349
    reserved for disk dumps using an export script.
350

    
351
    If the source can not satisfy the request the response status code
352
    will be 406 (Not Acceptable). Successful requests will specify the
353
    used media type using the ``Content-Type`` header. Unless only
354
    exactly one media type is requested, the client must handle the
355
    different response types.
356

    
357
  ``Accept-Encoding`` (:rfc:`2616`, section 14.3)
358
    Specify desired content coding. Supported are ``identity`` for
359
    uncompressed data, ``gzip`` for compressed data and ``*`` for any.
360
    The response will include a ``Content-Encoding`` header with the
361
    actual coding used. If the client specifies an unknown coding, the
362
    response status code will be 406 (Not Acceptable).
363

    
364
    If the client specifically needs compressed data (see
365
    :ref:`impexp2-compression`) but only gets ``identity``, it can
366
    either compress locally or abort the request.
367

    
368
  ``Range`` (:rfc:`2616`, section 14.35)
369
    Raw disk dumps can be resumed using this header (e.g. after a
370
    network issue).
371

    
372
    If this header was given in the request and the source supports
373
    resuming, the status code of the response will be 206 (Partial
374
    Content) and it'll include the ``Content-Range`` header as per
375
    :rfc:`2616`. If it does not support resuming or the request was not
376
    specifying a range, the status code will be 200 (OK).
377

    
378
    Only a single byte range is supported. cURL does not support
379
    ``multipart/byteranges`` responses by itself. Even if they could be
380
    somehow implemented, doing so would be of doubtful benefit for
381
    import/export.
382

    
383
    For raw data dumps handling ranges is pretty straightforward by just
384
    dumping the requested range.
385

    
386
    cURL will fail with the error code ``CURLE_RANGE_ERROR`` if a
387
    request included a range but the server can't handle it. The request
388
    must be retried without a range.
389

    
390
``POST /transfers/[transfer_id]/done``
391
  Use this resource to notify the source when transfer is finished (even
392
  if not successful). The status code will be 204 (No Content).
393

    
394

    
395
Code samples
396
------------
397

    
398
pycURL to file
399
++++++++++++++
400

    
401
.. highlight:: python
402

    
403
The following code sample shows how to write downloaded data directly to
404
a file without pumping it through Python::
405

    
406
  curl = pycurl.Curl()
407
  curl.setopt(pycurl.URL, "http://www.google.com/")
408
  curl.setopt(pycurl.WRITEDATA, open("googlecom.html", "w"))
409
  curl.perform()
410

    
411
This works equally well if the file descriptor is a pipe to another
412
process.
413

    
414

    
415
.. _backwards-compat:
416

    
417
Backwards compatibility
418
-----------------------
419

    
420
.. _backwards-compat-v1:
421

    
422
Version 1
423
+++++++++
424

    
425
The old inter-cluster import/export implementation described in the
426
:doc:`Ganeti 2.2 design document <design-2.2>` will be supported for at
427
least one minor (2.x) release. Intra-cluster imports/exports will use
428
the new version right away.
429

    
430

    
431
.. _exp-size-fd:
432

    
433
``EXP_SIZE_FD``
434
+++++++++++++++
435

    
436
Together with the improved import/export infrastructure Ganeti 2.2
437
allowed instance export scripts to report the expected data size. This
438
was then used to provide the user with an estimated remaining time.
439
Version 2 no longer supports OS import/export scripts and therefore
440
``EXP_SIZE_FD`` is no longer needed.
441

    
442

    
443
.. _impexp2-compression:
444

    
445
Compression
446
+++++++++++
447

    
448
Version 1 used explicit compression using ``gzip`` for transporting
449
data, but the dumped files didn't use any compression. Version 2 will
450
allow the destination to specify which encoding should be used. This way
451
the transported data is already compressed and can be directly used by
452
the client (see :ref:`impexp2-http-resources`). The cURL option
453
``CURLOPT_ENCODING`` can be used to set the ``Accept-Encoding`` header.
454
cURL will not decompress received data when
455
``CURLOPT_HTTP_CONTENT_DECODING`` is set to zero (if another HTTP client
456
library were used which doesn't support disabling transparent
457
compression, a custom content-coding type could be defined, e.g.
458
``x-ganeti-gzip``).
459

    
460

    
461
Notes
462
-----
463

    
464
The HTTP/1.1 protocol (:rfc:`2616`) defines trailing headers for chunked
465
transfers in section 3.6.1. This could be used to transfer a checksum at
466
the end of an import/export. cURL supports trailing headers since
467
version 7.14.1. Lighttpd doesn't seem to support them for FastCGI, but
468
they appear to be usable in combination with an NPH CGI (No Parsed
469
Headers).
470

    
471
.. _lighttp-sendfile:
472

    
473
Lighttpd allows FastCGI applications to send the special headers
474
``X-Sendfile`` and ``X-Sendfile2`` (the latter with a range). Using
475
these headers applications can send response headers and tell the
476
webserver to serve regular file stored on the file system as a response
477
body. The webserver will then take care of sending that file.
478
Unfortunately this mechanism is restricted to regular files and can not
479
be used for data from programs, neither direct nor via named pipes,
480
without writing to a file first. The latter is not an option as instance
481
data can be very large. Theoretically ``X-Sendfile`` could be used for
482
sending the input for a file-based instance import, but that'd require
483
the webserver to run as "root".
484

    
485
.. _python-sendfile:
486

    
487
Python does not include interfaces for the ``sendfile(2)`` or
488
``splice(2)`` system calls. The latter can be useful for faster copying
489
of data between file descriptors. There are some 3rd-party modules (e.g.
490
http://pypi.python.org/pypi/py-sendfile/) and discussions
491
(http://bugs.python.org/issue10882) for including support for
492
``sendfile(2)``, but the later is certainly not going to happen for the
493
Python versions supported by Ganeti. Calling the function using the
494
``ctypes`` module might be possible.
495

    
496

    
497
Performance considerations
498
--------------------------
499

    
500
The design described above was confirmed to be one of the better choices
501
in terms of download performance with bigger block sizes. All numbers
502
were gathered on the same physical machine with a single CPU and 1 GB of
503
RAM while downloading 2 GB of zeros read from ``/dev/zero``. ``wget``
504
(version 1.10.2) was used as the client, ``lighttpd`` (version 1.4.28)
505
as the server. The numbers in the first line are in megabytes per
506
second. The second line in each row is the CPU time spent in userland
507
respective system (measured for the CGI/FastCGI program using ``time
508
-v``).
509

    
510
::
511

    
512
  ----------------------------------------------------------------------
513
  Block size                      4 KB    64 KB   128 KB    1 MB    4 MB
514
  ======================================================================
515
  Plain CGI script reading          83      174      180     122     120
516
  from ``/dev/zero``
517
                               0.6/3.9  0.1/2.4  0.1/2.2 0.0/1.9 0.0/2.1
518
  ----------------------------------------------------------------------
519
  FastCGI with ``fcgiwrap``,        86      167      170     177     174
520
  ``dd`` reading from
521
  ``/dev/zero``                  1.1/5  0.5/2.9  0.5/2.7 0.7/3.1 0.7/2.8
522
  ----------------------------------------------------------------------
523
  FastCGI with ``fcgiwrap``,        68      146      150     170     170
524
  Python script copying from
525
  ``/dev/zero`` to stdout
526
                               1.3/5.1  0.8/3.7  0.7/3.3  0.9/2.9  0.8/3
527
  ----------------------------------------------------------------------
528
  FastCGI, Python script using      31       48       47       5       1
529
  ``flup`` library (version
530
  1.0.2) reading from
531
  ``/dev/zero``
532
                              23.5/9.8 14.3/8.5   16.1/8       -       -
533
  ----------------------------------------------------------------------
534

    
535

    
536
It should be mentioned that the ``flup`` library is not implemented in
537
the most efficient way, but even with some changes it doesn't get much
538
faster. It is fine for small amounts of data, but not for huge
539
transfers.
540

    
541

    
542
Other considered solutions
543
--------------------------
544

    
545
Another possible solution considered was to use ``socat`` like version 1
546
did. Due to the changing model, a large part of the code would've
547
required a rewrite anyway, while still not fixing all shortcomings. For
548
example, ``socat`` could still listen on only one protocol, IPv4 or
549
IPv6. Running two separate instances might have fixed that, but it'd get
550
more complicated. Using an existing HTTP server will provide us with a
551
number of other benefits as well, such as easier user separation between
552
server and backend.
553

    
554

    
555
.. vim: set textwidth=72 :
556
.. Local Variables:
557
.. mode: rst
558
.. fill-column: 72
559
.. End: