├── .pylintrc ├── DESCRIPTION.rst ├── LICENSE ├── README.md ├── ceph_flocker_driver ├── __init__.py ├── ceph_rbd.py ├── test_ceph_rbd.py └── test_func.py ├── delete-flocker-volumes.sh └── setup.py /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | init-hook="import flocker" 9 | 10 | # Add files or directories to the blacklist. They should be base names, not 11 | # paths. 12 | ignore= 13 | 14 | # Pickle collected data for later comparisons. 15 | persistent=no 16 | 17 | # List of plugins (as comma separated values of python modules names) to load, 18 | # usually to register additional checkers. 19 | load-plugins= 20 | 21 | # Use multiple processes to speed up Pylint. 22 | # DO NOT CHANGE THIS VALUES >1 HIDE RESULTS!!!!! 23 | jobs=1 24 | 25 | # Allow loading of arbitrary C extensions. Extensions are imported into the 26 | # active Python interpreter and may run arbitrary code. 27 | unsafe-load-any-extension=no 28 | 29 | # A comma-separated list of package or module names from where C extensions may 30 | # be loaded. Extensions are loading into the active Python interpreter and may 31 | # run arbitrary code 32 | extension-pkg-whitelist= 33 | 34 | # Allow optimization of some AST trees. This will activate a peephole AST 35 | # optimizer, which will apply various small optimizations. For instance, it can 36 | # be used to obtain the result of joining multiple strings with the addition 37 | # operator. Joining a lot of strings can lead to a maximum recursion error in 38 | # Pylint and this flag can prevent that. It has one side effect, the resulting 39 | # AST will be different than the one from reality. 40 | optimize-ast=no 41 | 42 | 43 | [MESSAGES CONTROL] 44 | 45 | # Only show warnings with the listed confidence levels. Leave empty to show 46 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 47 | confidence= 48 | 49 | # Enable the message, report, category or checker with the given id(s). You can 50 | # either give multiple identifier separated by comma (,) or put this option 51 | # multiple time. See also the "--disable" option for examples. 52 | disable=all 53 | 54 | enable=import-error, 55 | import-self, 56 | reimported, 57 | wildcard-import, 58 | misplaced-future, 59 | relative-import, 60 | deprecated-module, 61 | unpacking-non-sequence, 62 | invalid-all-object, 63 | undefined-all-variable, 64 | used-before-assignment, 65 | cell-var-from-loop, 66 | global-variable-undefined, 67 | redefined-builtin, 68 | redefine-in-handler, 69 | unused-import, 70 | unused-wildcard-import, 71 | global-variable-not-assigned, 72 | undefined-loop-variable, 73 | global-statement, 74 | global-at-module-level, 75 | bad-open-mode, 76 | redundant-unittest-assert, 77 | # Has common issues with our style due to 78 | # https://github.com/PyCQA/pylint/issues/210 79 | unused-variable, 80 | boolean-datetime 81 | 82 | # Things we'd like to enable someday: 83 | # redefined-outer-name (requires a bunch of work to clean up our code first) 84 | # undefined-variable (re-enable when pylint fixes https://github.com/PyCQA/pylint/issues/760) 85 | # no-name-in-module (giving us spurious warnings https://github.com/PyCQA/pylint/issues/73) 86 | # unused-argument (need to clean up or code a lot, e.g. prefix unused_?) 87 | 88 | # Things we'd like to try. 89 | # Procedure: 90 | # 1. Enable a bunch. 91 | # 2. See if there's spurious ones; if so disable. 92 | # 3. Record above. 93 | # 4. Remove from this list. 94 | # deprecated-method, 95 | # anomalous-unicode-escape-in-string, 96 | # anomalous-backslash-in-string, 97 | # not-in-loop, 98 | # function-redefined, 99 | # continue-in-finally, 100 | # abstract-class-instantiated, 101 | # star-needs-assignment-target, 102 | # duplicate-argument-name, 103 | # return-in-init, 104 | # too-many-star-expressions, 105 | # nonlocal-and-global, 106 | # return-outside-function, 107 | # return-arg-in-generator, 108 | # invalid-star-assignment-target, 109 | # bad-reversed-sequence, 110 | # nonexistent-operator, 111 | # yield-outside-function, 112 | # init-is-generator, 113 | # nonlocal-without-binding, 114 | # lost-exception, 115 | # assert-on-tuple, 116 | # dangerous-default-value, 117 | # duplicate-key, 118 | # useless-else-on-loop, 119 | # expression-not-assigned, 120 | # confusing-with-statement, 121 | # unnecessary-lambda, 122 | # pointless-statement, 123 | # pointless-string-statement, 124 | # unnecessary-pass, 125 | # unreachable, 126 | # eval-used, 127 | # exec-used, 128 | # bad-builtin, 129 | # using-constant-test, 130 | # deprecated-lambda, 131 | # bad-super-call, 132 | # missing-super-argument, 133 | # slots-on-old-class, 134 | # super-on-old-class, 135 | # property-on-old-class, 136 | # not-an-iterable, 137 | # not-a-mapping, 138 | # format-needs-mapping, 139 | # truncated-format-string, 140 | # missing-format-string-key, 141 | # mixed-format-string, 142 | # too-few-format-args, 143 | # bad-str-strip-call, 144 | # too-many-format-args, 145 | # bad-format-character, 146 | # format-combined-specification, 147 | # bad-format-string-key, 148 | # bad-format-string, 149 | # missing-format-attribute, 150 | # missing-format-argument-key, 151 | # unused-format-string-argument, 152 | # unused-format-string-key, 153 | # invalid-format-index, 154 | # bad-indentation, 155 | # mixed-indentation, 156 | # unnecessary-semicolon, 157 | # lowercase-l-suffix, 158 | # fixme, 159 | # invalid-encoded-data, 160 | # unpacking-in-except, 161 | # import-star-module-level, 162 | # parameter-unpacking, 163 | # long-suffix, 164 | # old-octal-literal, 165 | # old-ne-operator, 166 | # backtick, 167 | # old-raise-syntax, 168 | # print-statement, 169 | # metaclass-assignment, 170 | # next-method-called, 171 | # dict-iter-method, 172 | # dict-view-method, 173 | # indexing-exception, 174 | # raising-string, 175 | # standarderror-builtin, 176 | # using-cmp-argument, 177 | # cmp-method, 178 | # coerce-method, 179 | # delslice-method, 180 | # getslice-method, 181 | # hex-method, 182 | # nonzero-method, 183 | # oct-method, 184 | # setslice-method, 185 | # apply-builtin, 186 | # basestring-builtin, 187 | # buffer-builtin, 188 | # cmp-builtin, 189 | # coerce-builtin, 190 | # old-division, 191 | # execfile-builtin, 192 | # file-builtin, 193 | # filter-builtin-not-iterating, 194 | # no-absolute-import, 195 | # input-builtin, 196 | # intern-builtin, 197 | # long-builtin, 198 | # map-builtin-not-iterating, 199 | # range-builtin-not-iterating, 200 | # raw_input-builtin, 201 | # reduce-builtin, 202 | # reload-builtin, 203 | # round-builtin, 204 | # unichr-builtin, 205 | # unicode-builtin, 206 | # xrange-builtin, 207 | # zip-builtin-not-iterating, 208 | # logging-format-truncated, 209 | # logging-too-few-args, 210 | # logging-too-many-args, 211 | # logging-unsupported-format, 212 | # logging-not-lazy, 213 | # logging-format-interpolation, 214 | # invalid-unary-operand-type, 215 | # unsupported-binary-operation, 216 | # no-member, 217 | # not-callable, 218 | # redundant-keyword-arg, 219 | # assignment-from-no-return, 220 | # assignment-from-none, 221 | # not-context-manager, 222 | # repeated-keyword, 223 | # missing-kwoa, 224 | # no-value-for-parameter, 225 | # invalid-sequence-index, 226 | # invalid-slice-index, 227 | # too-many-function-args, 228 | # unexpected-keyword-arg, 229 | # unsupported-membership-test, 230 | # unsubscriptable-object, 231 | # access-member-before-definition, 232 | # method-hidden, 233 | # assigning-non-slot, 234 | # duplicate-bases, 235 | # inconsistent-mro, 236 | # inherit-non-class, 237 | # invalid-slots, 238 | # invalid-slots-object, 239 | # no-method-argument, 240 | # no-self-argument, 241 | # unexpected-special-method-signature, 242 | # non-iterator-returned, 243 | # protected-access, 244 | # arguments-differ, 245 | # attribute-defined-outside-init, 246 | # no-init, 247 | # abstract-method, 248 | # signature-differs, 249 | # bad-staticmethod-argument, 250 | # non-parent-init-called, 251 | # super-init-not-called, 252 | # bad-except-order, 253 | # catching-non-exception, 254 | # bad-exception-context, 255 | # notimplemented-raised, 256 | # raising-bad-type, 257 | # raising-non-exception, 258 | # misplaced-bare-raise, 259 | # duplicate-except, 260 | # broad-except, 261 | # nonstandard-exception, 262 | # binary-op-exception, 263 | # bare-except, 264 | # not-async-context-manager, 265 | # yield-inside-async-function, 266 | 267 | # ... 268 | [REPORTS] 269 | 270 | # Set the output format. Available formats are text, parseable, colorized, msvs 271 | # (visual studio) and html. You can also give a reporter class, eg 272 | # mypackage.mymodule.MyReporterClass. 273 | output-format=parseable 274 | 275 | # Put messages in a separate file for each module / package specified on the 276 | # command line instead of printing them on stdout. Reports (if any) will be 277 | # written in a file name "pylint_global.[txt|html]". 278 | files-output=no 279 | 280 | # Tells whether to display a full report or only the messages 281 | reports=no 282 | 283 | # Python expression which should return a note less than 10 (10 is the highest 284 | # note). You have access to the variables errors warning, statement which 285 | # respectively contain the number of errors / warnings messages and the total 286 | # number of statements analyzed. This is used by the global evaluation report 287 | # (RP0004). 288 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 289 | 290 | # Template used to display messages. This is a python new-style format string 291 | # used to format the message information. See doc for all details 292 | #msg-template= 293 | 294 | 295 | [LOGGING] 296 | 297 | # Logging modules to check that the string format arguments are in logging 298 | # function parameter format 299 | logging-modules=logging 300 | 301 | 302 | [FORMAT] 303 | 304 | # Maximum number of characters on a single line. 305 | max-line-length=100 306 | 307 | # Regexp for a line that is allowed to be longer than the limit. 308 | ignore-long-lines=^\s*(# )??$ 309 | 310 | # Allow the body of an if to be on the same line as the test if there is no 311 | # else. 312 | single-line-if-stmt=no 313 | 314 | # List of optional constructs for which whitespace checking is disabled. `dict- 315 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 316 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 317 | # `empty-line` allows space-only lines. 318 | no-space-check=trailing-comma,dict-separator 319 | 320 | # Maximum number of lines in a module 321 | max-module-lines=1000 322 | 323 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 324 | # tab). 325 | indent-string=' ' 326 | 327 | # Number of spaces of indent required inside a hanging or continued line. 328 | indent-after-paren=4 329 | 330 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 331 | expected-line-ending-format= 332 | 333 | 334 | [TYPECHECK] 335 | 336 | # Tells whether missing members accessed in mixin class should be ignored. A 337 | # mixin class is detected if its name ends with "mixin" (case insensitive). 338 | ignore-mixin-members=yes 339 | 340 | # List of module names for which member attributes should not be checked 341 | # (useful for modules/projects where namespaces are manipulated during runtime 342 | # and thus existing member attributes cannot be deduced by static analysis. It 343 | # supports qualified module names, as well as Unix pattern matching. 344 | ignored-modules= 345 | 346 | # List of classes names for which member attributes should not be checked 347 | # (useful for classes with attributes dynamically set). This supports can work 348 | # with qualified names. 349 | ignored-classes= 350 | 351 | # List of members which are set dynamically and missed by pylint inference 352 | # system, and so shouldn't trigger E1101 when accessed. Python regular 353 | # expressions are accepted. 354 | generated-members= 355 | 356 | 357 | [VARIABLES] 358 | 359 | # Tells whether we should check for unused import in __init__ files. 360 | init-import=no 361 | 362 | # A regular expression matching the name of dummy variables (i.e. expectedly 363 | # not used). 364 | dummy-variables-rgx=_$|dummy 365 | 366 | # List of additional names supposed to be defined in builtins. Remember that 367 | # you should avoid to define new builtins when possible. 368 | additional-builtins= 369 | 370 | # List of strings which can identify a callback function by name. A callback 371 | # name must start or end with one of those strings. 372 | callbacks=cb_,_cb 373 | 374 | 375 | [SIMILARITIES] 376 | 377 | # Minimum lines number of a similarity. 378 | min-similarity-lines=4 379 | 380 | # Ignore comments when computing similarities. 381 | ignore-comments=yes 382 | 383 | # Ignore docstrings when computing similarities. 384 | ignore-docstrings=yes 385 | 386 | # Ignore imports when computing similarities. 387 | ignore-imports=no 388 | 389 | 390 | [SPELLING] 391 | 392 | # Spelling dictionary name. Available dictionaries: none. To make it working 393 | # install python-enchant package. 394 | spelling-dict= 395 | 396 | # List of comma separated words that should not be checked. 397 | spelling-ignore-words= 398 | 399 | # A path to a file that contains private dictionary; one word per line. 400 | spelling-private-dict-file= 401 | 402 | # Tells whether to store unknown words to indicated private dictionary in 403 | # --spelling-private-dict-file option instead of raising a message. 404 | spelling-store-unknown-words=no 405 | 406 | 407 | [MISCELLANEOUS] 408 | 409 | # List of note tags to take in consideration, separated by a comma. 410 | notes=FIXME,XXX,TODO 411 | 412 | 413 | [BASIC] 414 | 415 | # List of builtins function names that should not be used, separated by a comma 416 | bad-functions=map,filter,input 417 | 418 | # Good variable names which should always be accepted, separated by a comma 419 | good-names=i,j,k,ex,Run,_ 420 | 421 | # Bad variable names which should always be refused, separated by a comma 422 | bad-names=foo,bar,baz,toto,tutu,tata 423 | 424 | # Colon-delimited sets of names that determine each other's naming style when 425 | # the name regexes allow several styles. 426 | name-group= 427 | 428 | # Include a hint for the correct naming format with invalid-name 429 | include-naming-hint=no 430 | 431 | # Regular expression matching correct function names 432 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 433 | 434 | # Naming hint for function names 435 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 436 | 437 | # Regular expression matching correct variable names 438 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 439 | 440 | # Naming hint for variable names 441 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 442 | 443 | # Regular expression matching correct constant names 444 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 445 | 446 | # Naming hint for constant names 447 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 448 | 449 | # Regular expression matching correct attribute names 450 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 451 | 452 | # Naming hint for attribute names 453 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 454 | 455 | # Regular expression matching correct argument names 456 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 457 | 458 | # Naming hint for argument names 459 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 460 | 461 | # Regular expression matching correct class attribute names 462 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 463 | 464 | # Naming hint for class attribute names 465 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 466 | 467 | # Regular expression matching correct inline iteration names 468 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 469 | 470 | # Naming hint for inline iteration names 471 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 472 | 473 | # Regular expression matching correct class names 474 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 475 | 476 | # Naming hint for class names 477 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 478 | 479 | # Regular expression matching correct module names 480 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 481 | 482 | # Naming hint for module names 483 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 484 | 485 | # Regular expression matching correct method names 486 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 487 | 488 | # Naming hint for method names 489 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 490 | 491 | # Regular expression which should only match function or class names that do 492 | # not require a docstring. 493 | no-docstring-rgx=^_ 494 | 495 | # Minimum line length for functions/classes that require docstrings, shorter 496 | # ones are exempt. 497 | docstring-min-length=-1 498 | 499 | 500 | [ELIF] 501 | 502 | # Maximum number of nested blocks for function / method body 503 | max-nested-blocks=5 504 | 505 | 506 | [IMPORTS] 507 | 508 | # Deprecated modules which should not be used, separated by a comma 509 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 510 | 511 | # Create a graph of every (i.e. internal and external) dependencies in the 512 | # given file (report RP0402 must not be disabled) 513 | import-graph= 514 | 515 | # Create a graph of external dependencies in the given file (report RP0402 must 516 | # not be disabled) 517 | ext-import-graph= 518 | 519 | # Create a graph of internal dependencies in the given file (report RP0402 must 520 | # not be disabled) 521 | int-import-graph= 522 | 523 | 524 | [DESIGN] 525 | 526 | # Maximum number of arguments for function / method 527 | max-args=5 528 | 529 | # Argument names that match this expression will be ignored. Default to name 530 | # with leading underscore 531 | ignored-argument-names=_.* 532 | 533 | # Maximum number of locals for function / method body 534 | max-locals=15 535 | 536 | # Maximum number of return / yield for function / method body 537 | max-returns=6 538 | 539 | # Maximum number of branch for function / method body 540 | max-branches=12 541 | 542 | # Maximum number of statements in function / method body 543 | max-statements=50 544 | 545 | # Maximum number of parents for a class (see R0901). 546 | max-parents=7 547 | 548 | # Maximum number of attributes for a class (see R0902). 549 | max-attributes=7 550 | 551 | # Minimum number of public methods for a class (see R0903). 552 | min-public-methods=2 553 | 554 | # Maximum number of public methods for a class (see R0904). 555 | max-public-methods=20 556 | 557 | # Maximum number of boolean expressions in a if statement 558 | max-bool-expr=5 559 | 560 | 561 | [CLASSES] 562 | 563 | # List of method names used to declare (i.e. assign) instance attributes. 564 | defining-attr-methods=__init__,__new__,setUp 565 | 566 | # List of valid names for the first argument in a class method. 567 | valid-classmethod-first-arg=cls 568 | 569 | # List of valid names for the first argument in a metaclass class method. 570 | valid-metaclass-classmethod-first-arg=mcs 571 | 572 | # List of member names, which should be excluded from the protected access 573 | # warning. 574 | exclude-protected=_asdict,_fields,_replace,_source,_make 575 | 576 | 577 | [EXCEPTIONS] 578 | 579 | # Exceptions that will emit a warning when being caught. Defaults to 580 | # "Exception" 581 | overgeneral-exceptions=Exception 582 | -------------------------------------------------------------------------------- /DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | Ceph driver for flocker 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ceph RBD Plugin for ClusterHQ/flocker 2 | 3 | This a Ceph Rados Block Device driver for Flocker, a container data orchestration system. 4 | 5 | ### Note: This driver is WIP. 6 | 7 | You may use the Ceph driver from ClusterHQ but keep in mind it is still being worked on. 8 | Thank you for your interest and please file issues and PRs to help us work toward a release. 9 | 10 | ## Installation 11 | 12 | Make sure you have Flocker already installed. If not visit [Install Flocker](https://docs.clusterhq.com/en/latest/using/installing/index.html) 13 | 14 | **_Be sure to use /opt/flocker/bin/python as this will install the driver into the right python environment_** 15 | 16 | Install using python 17 | ```bash 18 | git clone https://github.com/ClusterHQ/flocker-ceph-driver 19 | cd flocker-ceph-driver/ 20 | sudo /opt/flocker/bin/python setup.py install 21 | ``` 22 | 23 | **_Be sure to use /opt/flocker/bin/pip as this will install the driver into the right python environment_** 24 | 25 | Install using pip 26 | ```bash 27 | git clone https://github.com/ClusterHQ/flocker-ceph-driver 28 | cd flocker-ceph-driver/ 29 | /opt/flocker/bin/pip install flocker-ceph-driver/ 30 | ``` 31 | 32 | **Note:** Thank you for EMC for providing their ScaleIO driver on which this work was based: https://github.com/emccorp/scaleio-flocker-driver 33 | 34 | ## Configuration 35 | 36 | Reminder that this project is still under development, but if you want to 37 | install and use it from the master branch, do the following: 38 | 39 | This assumes you already have a ceph cluster set up, and that rbd can be run 40 | from the command line on all of your agent nodes. 41 | 42 | On every agent node you need to install this driver into the python environment 43 | in which flocker runs: 44 | 45 | ```bash 46 | /opt/flocker/bin/pip install git+https://github.com/ClusterHQ/ceph-flocker-driver.git@master 47 | ``` 48 | 49 | Then you need to set up your `agent.yml` to be configured to use this driver: 50 | 51 | 52 | ```yaml 53 | version: 1 54 | control-service: 55 | hostname: "user.controlserver.example.com" 56 | dataset: 57 | backend: "ceph_flocker_driver" 58 | cluster_name: "Ceph cluster name, defaults to ''" 59 | user_id: "Ceph user ID, defaults to 'admin'" 60 | ceph_conf_path: "path to ceph conf for rados, defaults to '/etc/ceph/ceph.conf'" 61 | storage_pool: "name of storage pool to use, defaults to 'rbd'" 62 | ``` 63 | -------------------------------------------------------------------------------- /ceph_flocker_driver/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ceph driver for Flocker. 3 | """ 4 | 5 | from flocker.node import BackendDescription, DeployerType 6 | from .ceph_rbd import ( 7 | rbd_from_configuration, DEFAULT_CLUSTER_NAME, DEFAULT_USER_ID, 8 | DEFAULT_CEPF_CONF_PATH, DEFAULT_STORAGE_POOL 9 | ) 10 | 11 | 12 | def api_factory(cluster_id, test_case=None, **kwargs): 13 | """ 14 | Create a ``IBlockDeviceAPI`` for ceph. 15 | """ 16 | cluster_name = DEFAULT_CLUSTER_NAME 17 | if "cluster_name" in kwargs: 18 | cluster_name = kwargs["cluster_name"] 19 | 20 | user_id = DEFAULT_USER_ID 21 | if "user_id" in kwargs: 22 | user_id = kwargs["user_id"] 23 | 24 | ceph_conf_path = DEFAULT_CEPF_CONF_PATH 25 | if "ceph_conf_path" in kwargs: 26 | ceph_conf_path = kwargs["ceph_conf_path"] 27 | 28 | storage_pool = DEFAULT_STORAGE_POOL 29 | if "storage_pool" in kwargs: 30 | storage_pool = kwargs["storage_pool"] 31 | 32 | return rbd_from_configuration( 33 | cluster_name=cluster_name, user_id=user_id, 34 | ceph_conf_path=ceph_conf_path, storage_pool=storage_pool) 35 | 36 | FLOCKER_BACKEND = BackendDescription( 37 | name=u"ceph_flocker_driver", 38 | needs_reactor=False, needs_cluster_id=True, 39 | api_factory=api_factory, deployer_type=DeployerType.block) 40 | -------------------------------------------------------------------------------- /ceph_flocker_driver/ceph_rbd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | from uuid import UUID 16 | 17 | # Conditionally import Ceph modules so that tests can be run without them. 18 | import rados 19 | import rbd 20 | 21 | from subprocess import check_output 22 | 23 | from eliot import Logger 24 | from zope.interface import implementer 25 | from twisted.python.filepath import FilePath 26 | 27 | from flocker.node.agents.blockdevice import ( 28 | AlreadyAttachedVolume, IBlockDeviceAPI, 29 | BlockDeviceVolume, UnknownVolume, UnattachedVolume 30 | ) 31 | 32 | _logger = Logger() 33 | 34 | DEFAULT_CLUSTER_NAME = "" 35 | 36 | # All Ceph clusters have a user "client.admin" 37 | DEFAULT_USER_ID = "admin" 38 | 39 | # On a freshly installed cluster, only the rbd pool exists. 40 | DEFAULT_STORAGE_POOL = "rbd" 41 | 42 | # We will look for ceph.conf in /etc/ceph 43 | DEFAULT_CEPF_CONF_PATH = "/etc/ceph/ceph.conf" 44 | 45 | 46 | class ImageExists(Exception): 47 | """ 48 | An RBD image with the requested image name already exists. 49 | """ 50 | def __init__(self, blockdevice_id): 51 | Exception.__init__(self, blockdevice_id) 52 | self.blockdevice_id = blockdevice_id 53 | 54 | 55 | class ExternalBlockDeviceId(Exception): 56 | """ 57 | The ``blockdevice_id`` was not a Flocker-controlled volume. 58 | """ 59 | def __init__(self, blockdevice_id): 60 | Exception.__init__(self, blockdevice_id) 61 | self.blockdevice_id = blockdevice_id 62 | 63 | 64 | def _blockdevice_id(dataset_id): 65 | """ 66 | A blockdevice_id is the unicode representation of a Flocker dataset_id 67 | according to the storage system. 68 | """ 69 | return u"flocker-%s" % (dataset_id,) 70 | 71 | 72 | def _rbd_blockdevice_id(blockdevice_id): 73 | """ 74 | The ``rbd`` module only accepts ``blockdevice_id``s of type ``bytes``. 75 | 76 | XXX think about the problems here 77 | """ 78 | return bytes(blockdevice_id) 79 | 80 | 81 | def _dataset_id(blockdevice_id): 82 | if not blockdevice_id.startswith(b"flocker-"): 83 | raise ExternalBlockDeviceId(blockdevice_id) 84 | return UUID(blockdevice_id[8:]) 85 | 86 | 87 | @implementer(IBlockDeviceAPI) 88 | class CephRBDBlockDeviceAPI(object): 89 | """ 90 | A ``IBlockDeviceAPI`` which uses Ceph Rados Block Devices. 91 | """ 92 | 93 | def __init__(self, connection, ioctx, pool, command_runner): 94 | """ 95 | XXX TODO 96 | """ 97 | self._connection = connection 98 | self._ioctx = ioctx 99 | self._pool = pool 100 | self._check_output = command_runner 101 | 102 | def _check_exists(self, blockdevice_id): 103 | """ 104 | Check if the image indicated by ``blockdevice_id`` already exists, and 105 | raise ``UnknownVolume`` if so. 106 | """ 107 | rbd_inst = rbd.RBD() 108 | all_images = rbd_inst.list(self._ioctx) 109 | if blockdevice_id not in all_images: 110 | raise UnknownVolume(unicode(blockdevice_id)) 111 | 112 | def _list_maps(self): 113 | """ 114 | Return a ``dict`` mapping unicode RBD image names to mounted block 115 | device ``FilePath``s for the active pool only. 116 | 117 | This information only applies to this host. 118 | """ 119 | maps = dict() 120 | showmapped_output = self._check_output( 121 | [b"rbd", "-p", self._pool, b"showmapped"]).strip() 122 | if not len(showmapped_output): 123 | return maps 124 | u_showmapped_output = showmapped_output.decode("utf-8") 125 | lines = u_showmapped_output.split(b"\n") 126 | if len(lines) == 1: 127 | raise Exception( 128 | "Unexpecetd `rbd showmapped` output: %r" 129 | % (showmapped_output,)) 130 | lines.pop(0) 131 | for line in lines: 132 | image_id, pool, blockdevice_id, snap, mountpoint = line.split() 133 | if pool != self._pool: 134 | continue 135 | try: 136 | _dataset_id(blockdevice_id) 137 | except ExternalBlockDeviceId: 138 | continue 139 | maps[blockdevice_id] = FilePath(mountpoint) 140 | return maps 141 | 142 | def _is_already_mapped(self, blockdevice_id): 143 | """ 144 | Return ``True`` or ``False`` if requested _blockdevice_id is already 145 | mapped anywhere in the cluster. 146 | """ 147 | self._check_exists(blockdevice_id) 148 | output = self._check_output([b"rbd", b"status", blockdevice_id]) 149 | return output.strip() != "Watchers: none" 150 | 151 | def allocation_unit(self): 152 | """ 153 | The minimum Ceph RBD allocatio quanta is 1MB 154 | """ 155 | return 1024 * 1024 156 | 157 | def compute_instance_id(self): 158 | """ 159 | Get the hostname for this node. Ceph identifies nodes by hostname. 160 | 161 | :returns: A ``unicode`` object representing this node. 162 | """ 163 | return self._check_output([b"hostname", b"-s"]).strip().decode("utf8") 164 | 165 | def create_volume(self, dataset_id, size): 166 | """ 167 | Create a new volume as an RBD image. 168 | 169 | :param UUID dataset_id: The Flocker dataset ID of the dataset on this 170 | volume. 171 | :param int size: The size of the new volume in bytes. 172 | :returns: A ``BlockDeviceVolume``. 173 | """ 174 | blockdevice_id = _blockdevice_id(dataset_id) 175 | rbd_inst = rbd.RBD() 176 | all_images = rbd_inst.list(self._ioctx) 177 | if blockdevice_id in all_images: 178 | raise ImageExists(blockdevice_id) 179 | rbd_inst.create(self._ioctx, _rbd_blockdevice_id(blockdevice_id), size) 180 | return BlockDeviceVolume( 181 | blockdevice_id=blockdevice_id, size=size, dataset_id=dataset_id) 182 | 183 | def destroy_volume(self, blockdevice_id): 184 | """ 185 | Destroy an existing RBD image. 186 | 187 | :param unicode blockdevice_id: The unique identifier for the volume to 188 | destroy. 189 | :raises UnknownVolume: If the supplied ``blockdevice_id`` does not 190 | exist. 191 | :return: ``None`` 192 | """ 193 | ascii_blockdevice_id = blockdevice_id.encode() 194 | self._check_exists(ascii_blockdevice_id) 195 | rbd_inst = rbd.RBD() 196 | rbd_inst.remove(self._ioctx, ascii_blockdevice_id) 197 | 198 | def attach_volume(self, blockdevice_id, attach_to): 199 | """ 200 | Attach ``blockdevice_id`` to the node indicated by ``attach_to``. 201 | 202 | :param unicode blockdevice_id: The unique identifier for the block 203 | device being attached. 204 | :param unicode attach_to: An identifier like the one returned by the 205 | ``compute_instance_id`` method indicating the node to which to 206 | attach the volume. 207 | 208 | :raises UnknownVolume: If the supplied ``blockdevice_id`` does not 209 | exist. 210 | :raises AlreadyAttachedVolume: If the supplied ``blockdevice_id`` is 211 | already attached. 212 | :returns: A ``BlockDeviceVolume`` with a ``attached_to`` attribute set 213 | to ``attach_to``. 214 | """ 215 | # This also checks if it exists 216 | if self._is_already_mapped(blockdevice_id): 217 | raise AlreadyAttachedVolume(blockdevice_id) 218 | 219 | if attach_to != self.compute_instance_id(): 220 | # TODO log this. 221 | return 222 | 223 | self._check_output([ 224 | b"rbd", b"-p", self._pool, b"map", blockdevice_id]) 225 | 226 | rbd_image = rbd.Image(self._ioctx, _rbd_blockdevice_id(blockdevice_id)) 227 | size = int(rbd_image.stat()["size"]) 228 | return BlockDeviceVolume( 229 | blockdevice_id=blockdevice_id, size=size, 230 | attached_to=self.compute_instance_id(), 231 | dataset_id=_dataset_id(blockdevice_id)) 232 | 233 | def detach_volume(self, blockdevice_id): 234 | """ 235 | Detach ``blockdevice_id`` from whatever host it is attached to. 236 | 237 | :param unicode blockdevice_id: The unique identifier for the block 238 | 239 | device being detached. 240 | :raises UnknownVolume: If the supplied ``blockdevice_id`` does not 241 | exist. 242 | :raises UnattachedVolume: If the supplied ``blockdevice_id`` is 243 | not attached to anything. 244 | :returns: ``None`` 245 | """ 246 | self._check_exists(blockdevice_id) 247 | device_path = self.get_device_path(blockdevice_id).path 248 | self._check_output([ 249 | b"rbd", b"-p", self._pool, b"unmap", device_path]) 250 | 251 | def list_volumes(self): 252 | """ 253 | List all the block devices available via the back end API. 254 | :returns: A ``list`` of ``BlockDeviceVolume``s. 255 | """ 256 | rbd_inst = rbd.RBD() 257 | volumes = [] 258 | all_images = rbd_inst.list(self._ioctx) 259 | all_maps = self._list_maps() 260 | for blockdevice_id in all_images: 261 | blockdevice_id = blockdevice_id.decode() 262 | try: 263 | dataset_id = _dataset_id(blockdevice_id) 264 | except ExternalBlockDeviceId: 265 | # This is an external volume 266 | continue 267 | rbd_image = rbd.Image( 268 | self._ioctx, _rbd_blockdevice_id(blockdevice_id)) 269 | size = int(rbd_image.stat()["size"]) 270 | if blockdevice_id in all_maps: 271 | attached_to = self.compute_instance_id() 272 | else: 273 | attached_to = None 274 | volumes.append(BlockDeviceVolume( 275 | blockdevice_id=unicode(blockdevice_id), 276 | size=size, attached_to=attached_to, 277 | dataset_id=dataset_id)) 278 | return volumes 279 | 280 | def get_device_path(self, blockdevice_id): 281 | """ 282 | Return the device path that has been allocated to the block device on 283 | the host to which it is currently attached. 284 | 285 | :param unicode blockdevice_id: The unique identifier for the block 286 | device. 287 | :raises UnknownVolume: If the supplied ``blockdevice_id`` does not 288 | exist. 289 | :raises UnattachedVolume: If the supplied ``blockdevice_id`` is 290 | not attached to a host. 291 | :returns: A ``FilePath`` for the device. 292 | """ 293 | self._check_exists(blockdevice_id) 294 | maps = self._list_maps() 295 | try: 296 | return maps[blockdevice_id] 297 | except KeyError: 298 | raise UnattachedVolume(blockdevice_id) 299 | 300 | def destroy_all_flocker_volumes(self): 301 | """ 302 | Search for and destroy all Flocker volumes. 303 | """ 304 | maps = self._list_maps() 305 | for blockdevice_id in maps: 306 | self.detach_volume(blockdevice_id) 307 | for blockdevicevolume in self.list_volumes(): 308 | self.destroy_volume(blockdevicevolume.blockdevice_id) 309 | 310 | 311 | def rbd_from_configuration( 312 | cluster_name, user_id, ceph_conf_path, storage_pool 313 | ): 314 | try: 315 | cluster = rados.Rados(conffile=ceph_conf_path) 316 | except TypeError as e: 317 | # XXX eliot 318 | raise e 319 | 320 | try: 321 | cluster.connect() 322 | except Exception as e: 323 | # XXX eliot 324 | raise e 325 | 326 | if not cluster.pool_exists(storage_pool): 327 | raise Exception("Pool does not exist") # XXX eliot 328 | ioctx = cluster.open_ioctx(storage_pool) 329 | 330 | return CephRBDBlockDeviceAPI(cluster, ioctx, storage_pool, check_output) 331 | -------------------------------------------------------------------------------- /ceph_flocker_driver/test_ceph_rbd.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from uuid import uuid4 3 | from twisted.python.filepath import FilePath 4 | from twisted.trial.unittest import TestCase, SkipTest 5 | 6 | from .ceph_rbd import CephRBDBlockDeviceAPI, rbd_from_configuration 7 | 8 | from flocker.node.agents.blockdevice import AlreadyAttachedVolume 9 | from flocker.node.agents.test.test_blockdevice import ( 10 | make_iblockdeviceapi_tests, 11 | ) 12 | 13 | 14 | class FakeCommandRunner(object): 15 | """ 16 | Test helper to simulate ``subprocess.check_output``. 17 | """ 18 | def __init__(self): 19 | self._outputs = dict() 20 | 21 | def add_command(self, command_spec, output): 22 | """ 23 | Add a command output. 24 | 25 | :param list command_spec: A list of lists, the inner list is a command 26 | list which would be provided to ``check_output``. 27 | :param str output: The output of the command. 28 | """ 29 | self._outputs[tuple(command_spec)] = output 30 | 31 | def check_output(self, command_spec): 32 | """ 33 | Return the command output. 34 | 35 | :param list command_spec: A list of lists, the inner list is a command 36 | list which would be provided to ``check_output``. 37 | """ 38 | return self._outputs[tuple(command_spec)] 39 | 40 | 41 | class CephRBDBlockDeviceAPITests(TestCase): 42 | """ 43 | Tests for ``CephRBDBlockDeviceAPI``. 44 | """ 45 | def get_api_and_runner(self): 46 | runner = FakeCommandRunner() 47 | return ( 48 | CephRBDBlockDeviceAPI(None, None, b"rbd", runner.check_output), 49 | runner, 50 | ) 51 | 52 | def _basic_output(self): 53 | """ 54 | Get basic test api and runner. 55 | """ 56 | api, runner = self.get_api_and_runner() 57 | runner.add_command([b"hostname", b"-s"], "ceph-node-1\n") 58 | runner.add_command( 59 | [b"rbd", b"status", b"foo"], 60 | """Watchers: 61 | watcher=172.31.14.48:0/1953528273 client.5153 cookie=2\n""") 62 | runner.add_command( 63 | [b"rbd", b"-p", b"rbd", b"showmapped"], 64 | 'id pool image snap device \n1 rbd foo - /dev/rbd1 \n2 rbd flocker-foo - /dev/rbd2 \n3 rbd \xf0\x9f\x90\xb3 - /dev/rbd3 \n4 other_pool some_other_image - /dev/rbd4 \n') # noqa 65 | return api, runner 66 | 67 | def test_compute_instance_id(self): 68 | """ 69 | The instance_id is the current node hostname. 70 | """ 71 | api, runner = self._basic_output() 72 | self.assertEquals(api.compute_instance_id(), "ceph-node-1") 73 | 74 | def test_attach_already_attached(self): 75 | """ 76 | ``attach_volume`` raises ``AlreadyAttachedVolume``. 77 | """ 78 | api, runner = self._basic_output() 79 | self.assertRaises( 80 | AlreadyAttachedVolume, 81 | api.attach_volume, u"foo", api.compute_instance_id()) 82 | 83 | 84 | class ListMapsTests(TestCase): 85 | """ 86 | Tests for ``CephRBDBlockDeviceAPI._list_maps``. 87 | """ 88 | 89 | def get_api_and_runner(self): 90 | runner = FakeCommandRunner() 91 | return ( 92 | CephRBDBlockDeviceAPI(None, None, b"rbd", runner.check_output), 93 | runner, 94 | ) 95 | 96 | def _basic_output(self): 97 | """ 98 | Get basic test api and runner. 99 | """ 100 | 101 | api, runner = self.get_api_and_runner() 102 | runner.add_command( 103 | [b"rbd", b"-p", b"rbd", b"showmapped"], 104 | 'id pool image snap device \n1 rbd foo - /dev/rbd1 \n2 rbd flocker-foo - /dev/rbd2 \n3 rbd \xf0\x9f\x90\xb3 - /dev/rbd3 \n4 other_pool some_other_image - /dev/rbd4 \n') # noqa 105 | return api, runner 106 | 107 | def test_empty(self): 108 | """ 109 | There are no maps, return an empty dict. 110 | """ 111 | api, runner = self.get_api_and_runner() 112 | runner.add_command([b"rbd", b"-p", b"rbd", b"showmapped"], "\n") 113 | maps = api._list_maps() 114 | self.assertEquals(maps, dict()) 115 | 116 | def test_list_all(self): 117 | """ 118 | Exactly the right list of mapped images. 119 | """ 120 | api, runner = self._basic_output() 121 | maps = api._list_maps() 122 | images = maps.keys() 123 | self.assertEquals( 124 | images, 125 | [u'flocker-foo', u'foo', u'\U0001f433']) 126 | 127 | def test_list_bytes(self): 128 | """ 129 | Images with ``bytes`` names are found. 130 | """ 131 | api, runner = self._basic_output() 132 | maps = api._list_maps() 133 | self.assertEquals(maps[u"flocker-foo"], FilePath("/dev/rbd2")) 134 | 135 | def test_list_unicode(self): 136 | """ 137 | Images with ``unicode`` names are found. 138 | """ 139 | api, runner = self._basic_output() 140 | maps = api._list_maps() 141 | self.assertEquals(maps[u'\U0001f433'], FilePath("/dev/rbd3")) 142 | 143 | def test_foreign_pools_not_found(self): 144 | """ 145 | Images in other pools are not found. 146 | """ 147 | api, runner = self._basic_output() 148 | maps = api._list_maps() 149 | self.assertNotIn(u"other_pool", maps) 150 | 151 | 152 | def api_factory(test_case): 153 | """ 154 | :param test: A twisted.trial.unittest.TestCase instance 155 | """ 156 | flocker_functional_test = environ.get('FLOCKER_FUNCTIONAL_TEST') 157 | if flocker_functional_test is None: 158 | raise SkipTest( 159 | 'Please set FLOCKER_FUNCTIONAL_TEST environment variable to ' 160 | 'run storage backend functional tests.' 161 | ) 162 | api = rbd_from_configuration( 163 | "flocker", "client.admin", "/etc/ceph/ceph.conf", "rbd") 164 | test_case.addCleanup(api.destroy_all_flocker_volumes) 165 | return api 166 | 167 | 168 | class CephRBDRealTests(make_iblockdeviceapi_tests( 169 | api_factory, 1024 * 1024 * 32, 1024 * 1024, 170 | lambda test: unicode(uuid4())) 171 | ): # XXX this is a guess 172 | """ 173 | Acceptance tests for the ceph_rbd driver. 174 | """ 175 | -------------------------------------------------------------------------------- /ceph_flocker_driver/test_func.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimal functional tests. 3 | """ 4 | from uuid import uuid4 5 | from flocker.node.agents.test.test_blockdevice import ( 6 | make_iblockdeviceapi_tests, 7 | ) 8 | from ceph_flocker_driver.ceph_rbd import rbd_from_configuration 9 | 10 | 11 | def api_factory(test): 12 | # Return an instance of your IBlockDeviceAPI implementation class, given 13 | # a twisted.trial.unittest.TestCase instance. 14 | return rbd_from_configuration("flocker", "ceph.admin", None, "rbd") 15 | 16 | # Smallest volume to create in tests, e.g. 1GiB: 17 | MIN_ALLOCATION_SIZE = 1024 * 1024 * 1024 18 | 19 | # Minimal unit of volume allocation, e.g. 1MiB: 20 | MIN_ALLOCATION_UNIT = 1024 * 1024 21 | 22 | 23 | class YourStorageTests(make_iblockdeviceapi_tests( 24 | api_factory, MIN_ALLOCATION_SIZE, MIN_ALLOCATION_UNIT, 25 | # Factory for valid but unknown volume id specific to your backend: 26 | lambda test: unicode(uuid4())) 27 | ): 28 | """ 29 | Tests for Ceph RBD driver. 30 | """ 31 | -------------------------------------------------------------------------------- /delete-flocker-volumes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /dev/rbd/rbd && for X in `ls --color=never | egrep ^flocker-`; do rbd unmap $X; done 3 | for X in `rbd ls | egrep ^flocker-`; do rbd rm $X; done 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import codecs # To use a consistent encoding 3 | 4 | # Get the long description from the relevant file 5 | with codecs.open('DESCRIPTION.rst', encoding='utf-8') as f: 6 | long_description = f.read() 7 | 8 | setup( 9 | name='ceph_flocker_driver', 10 | version='0.1', 11 | description='Ceph RBD driver for ClusterHQ/Flocker', 12 | long_description=long_description, 13 | author='ClusterHQ Team', 14 | author_email='support@clusterhq.com', 15 | url='https://github.com/ClusterHQ/flocker-ceph-driver', 16 | license='Apache 2.0', 17 | 18 | keywords='backend, plugin, flocker, docker, python', 19 | packages=find_packages(exclude=['test*']), 20 | install_requires=['python-cephlibs'], 21 | ) 22 | --------------------------------------------------------------------------------