resources.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. """
  2. Base implementation for data objects exposed through a provider or service
  3. """
  4. import inspect
  5. import itertools
  6. import logging
  7. import os
  8. import re
  9. import shutil
  10. import time
  11. from cloudbridge.cloud.interfaces.exceptions \
  12. import InvalidConfigurationException
  13. from cloudbridge.cloud.interfaces.exceptions import WaitStateException
  14. from cloudbridge.cloud.interfaces.resources import AttachmentInfo
  15. from cloudbridge.cloud.interfaces.resources import Bucket
  16. from cloudbridge.cloud.interfaces.resources import BucketObject
  17. from cloudbridge.cloud.interfaces.resources import CloudResource
  18. from cloudbridge.cloud.interfaces.resources import FloatingIP
  19. from cloudbridge.cloud.interfaces.resources import Instance
  20. from cloudbridge.cloud.interfaces.resources import InstanceState
  21. from cloudbridge.cloud.interfaces.resources import InstanceType
  22. from cloudbridge.cloud.interfaces.resources import KeyPair
  23. from cloudbridge.cloud.interfaces.resources import LaunchConfig
  24. from cloudbridge.cloud.interfaces.resources import MachineImage
  25. from cloudbridge.cloud.interfaces.resources import MachineImageState
  26. from cloudbridge.cloud.interfaces.resources import Network
  27. from cloudbridge.cloud.interfaces.resources import NetworkState
  28. from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
  29. from cloudbridge.cloud.interfaces.resources import PageableObjectMixin
  30. from cloudbridge.cloud.interfaces.resources import PlacementZone
  31. from cloudbridge.cloud.interfaces.resources import Region
  32. from cloudbridge.cloud.interfaces.resources import ResultList
  33. from cloudbridge.cloud.interfaces.resources import Router
  34. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  35. from cloudbridge.cloud.interfaces.resources import SecurityGroupRule
  36. from cloudbridge.cloud.interfaces.resources import Snapshot
  37. from cloudbridge.cloud.interfaces.resources import SnapshotState
  38. from cloudbridge.cloud.interfaces.resources import Subnet
  39. from cloudbridge.cloud.interfaces.resources import SubnetState
  40. from cloudbridge.cloud.interfaces.resources import Volume
  41. from cloudbridge.cloud.interfaces.resources import VolumeState
  42. import six
  43. log = logging.getLogger(__name__)
  44. class BaseCloudResource(CloudResource):
  45. """
  46. Base implementation of a CloudBridge Resource.
  47. """
  48. # Regular expression for valid cloudbridge resource names.
  49. # They, must match the same criteria as GCE labels.
  50. # as discussed here: https://github.com/gvlproject/cloudbridge/issues/55
  51. #
  52. # NOTE: The following regex is based on GCEs internal validation logic,
  53. # and is significantly complex to allow for international characters.
  54. CB_NAME_PATTERN = re.compile(six.u(
  55. r"^[\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105"
  56. "\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B"
  57. "\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131"
  58. "\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146"
  59. "\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B"
  60. "\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171"
  61. "\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C"
  62. "\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA"
  63. "\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9"
  64. "\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF"
  65. "\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5"
  66. "\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D"
  67. "\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223"
  68. "\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F"
  69. "\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371"
  70. "\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-"
  71. "\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB"
  72. "\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463"
  73. "\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479"
  74. "\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497"
  75. "\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD"
  76. "\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4"
  77. "\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9"
  78. "\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF"
  79. "\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505"
  80. "\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B"
  81. "\u051D\u051F\u0521\u0523\u0525\u0527\u0561-\u0587\u1D00-\u1D2B"
  82. "\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D"
  83. "\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23"
  84. "\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39"
  85. "\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F"
  86. "\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65"
  87. "\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B"
  88. "\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91"
  89. "\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD"
  90. "\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3"
  91. "\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9"
  92. "\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF"
  93. "\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15"
  94. "\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67"
  95. "\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4"
  96. "\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7"
  97. "\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u210A\u210E\u210F\u2113\u212F"
  98. "\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61"
  99. "\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7B\u2C81"
  100. "\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97"
  101. "\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD"
  102. "\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3"
  103. "\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9"
  104. "\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25"
  105. "\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651"
  106. "\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667"
  107. "\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F"
  108. "\uA691\uA693\uA695\uA697\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-"
  109. "\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745"
  110. "\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B"
  111. "\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-"
  112. "\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791"
  113. "\uA793\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7FA\uFB00-\uFB06\uFB13-\uFB17"
  114. "\uFF41-\uFF5A\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA"
  115. "\u05F0-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3"
  116. "\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-"
  117. "\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u08A0\u08A2-"
  118. "\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0977\u0979-"
  119. "\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2"
  120. "\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1"
  121. "\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33"
  122. "\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-"
  123. "\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-"
  124. "\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-"
  125. "\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D"
  126. "\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95"
  127. "\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-"
  128. "\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33"
  129. "\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-"
  130. "\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0"
  131. "\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D"
  132. "\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-"
  133. "\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45"
  134. "\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-"
  135. "\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2"
  136. "\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-"
  137. "\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D"
  138. "\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10D0-\u10FA"
  139. "\u10FD-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-"
  140. "\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0"
  141. "\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A"
  142. "\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A"
  143. "\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751"
  144. "\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-"
  145. "\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D"
  146. "\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54"
  147. "\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5"
  148. "\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1"
  149. "\u1CF5\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6"
  150. "\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE"
  151. "\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-"
  152. "\u30FA\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF"
  153. "\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7"
  154. "\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA7FB-"
  155. "\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-"
  156. "\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C"
  157. "\uA984-\uA9B2\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F"
  158. "\uAA71-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD"
  159. "\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-"
  160. "\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-"
  161. "\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D"
  162. "\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43"
  163. "\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-"
  164. "\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-"
  165. "\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u0030-"
  166. "\u0039\u00B2\u00B3\u00B9\u00BC-\u00BE\u0660-\u0669\u06F0-\u06F9"
  167. "\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F"
  168. "\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F"
  169. "\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0E50-\u0E59\u0ED0-\u0ED9"
  170. "\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0"
  171. "\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA"
  172. "\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49"
  173. "\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-"
  174. "\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-"
  175. "\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-"
  176. "\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-"
  177. "\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uAA50-\uAA59\uABF0-"
  178. "\uABF9\uFF10-\uFF19_-]{0,63}$"), re.UNICODE)
  179. def __init__(self, provider):
  180. self.__provider = provider
  181. def is_valid_resource_name(self, name):
  182. return True if self.CB_NAME_PATTERN.match(name) else False
  183. @property
  184. def _provider(self):
  185. return self.__provider
  186. def to_json(self):
  187. # Get all attributes but filter methods and private/magic ones
  188. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  189. js = {k: v for(k, v) in attr if not k.startswith('_')}
  190. return js
  191. class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
  192. """
  193. A base implementation of an ObjectLifeCycleMixin.
  194. This base implementation has an implementation of wait_for
  195. which refreshes the object's state till the desired ready states
  196. are reached. Subclasses must still implement the wait_till_ready
  197. method, since the desired ready states are object specific.
  198. """
  199. def wait_for(self, target_states, terminal_states=None, timeout=None,
  200. interval=None):
  201. if timeout is None:
  202. timeout = self._provider.config.default_wait_timeout
  203. if interval is None:
  204. interval = self._provider.config.default_wait_interval
  205. assert timeout >= 0
  206. assert interval >= 0
  207. assert timeout >= interval
  208. end_time = time.time() + timeout
  209. while self.state not in target_states:
  210. if self.state in (terminal_states or []):
  211. raise WaitStateException(
  212. "Object: {0} is in state: {1} which is a terminal state"
  213. " and cannot be waited on.".format(self, self.state))
  214. else:
  215. log.debug(
  216. "Object %s is in state: %s. Waiting another %s"
  217. " seconds to reach target state(s): %s...",
  218. self,
  219. self.state,
  220. int(end_time - time.time()),
  221. target_states)
  222. time.sleep(interval)
  223. if time.time() > end_time:
  224. raise WaitStateException(
  225. "Waited too long for object: {0} to become ready. It's"
  226. " still in state: {1}".format(self, self.state))
  227. self.refresh()
  228. log.debug("Object: %s successfully reached target state: %s",
  229. self, self.state)
  230. return True
  231. class BaseResultList(ResultList):
  232. def __init__(
  233. self, is_truncated, marker, supports_total, total=None, data=None):
  234. # call list constructor
  235. super(BaseResultList, self).__init__(data or [])
  236. self._marker = marker
  237. self._is_truncated = is_truncated
  238. self._supports_total = True if supports_total else False
  239. self._total = total
  240. @property
  241. def marker(self):
  242. return self._marker
  243. @property
  244. def is_truncated(self):
  245. return self._is_truncated
  246. @property
  247. def supports_total(self):
  248. return self._supports_total
  249. @property
  250. def total_results(self):
  251. return self._total
  252. class ServerPagedResultList(BaseResultList):
  253. """
  254. This is a convenience class that extends the :class:`BaseResultList` class
  255. and provides a server side implementation of paging. It is meant for use by
  256. provider developers and is not meant for direct use by end-users.
  257. This class can be used to wrap a partial result list when an operation
  258. supports server side paging.
  259. """
  260. @property
  261. def supports_server_paging(self):
  262. return True
  263. @property
  264. def data(self):
  265. raise NotImplementedError(
  266. "ServerPagedResultLists do not support the data property")
  267. class ClientPagedResultList(BaseResultList):
  268. """
  269. This is a convenience class that extends the :class:`BaseResultList` class
  270. and provides a client side implementation of paging. It is meant for use by
  271. provider developers and is not meant for direct use by end-users.
  272. This class can be used to wrap a full result list when an operation does
  273. not support server side paging. This class will then provide a paged view
  274. of the full result set entirely on the client side.
  275. """
  276. def __init__(self, provider, objects, limit=None, marker=None):
  277. self._objects = objects
  278. limit = limit or provider.config.default_result_limit
  279. total_size = len(objects)
  280. if marker:
  281. from_marker = itertools.dropwhile(
  282. lambda obj: not obj.id == marker, objects)
  283. # skip one past the marker
  284. next(from_marker, None)
  285. objects = list(from_marker)
  286. is_truncated = len(objects) > limit
  287. results = list(itertools.islice(objects, limit))
  288. super(ClientPagedResultList, self).__init__(
  289. is_truncated,
  290. results[-1].id if is_truncated else None,
  291. True, total=total_size,
  292. data=results)
  293. @property
  294. def supports_server_paging(self):
  295. return False
  296. @property
  297. def data(self):
  298. return self._objects
  299. class BasePageableObjectMixin(PageableObjectMixin):
  300. """
  301. A mixin to provide iteration capability for a class
  302. that support a list(limit, marker) method.
  303. """
  304. def __iter__(self):
  305. result_list = self.list()
  306. if result_list.supports_server_paging:
  307. for result in result_list:
  308. yield result
  309. while result_list.is_truncated:
  310. result_list = self.list(marker=result_list.marker)
  311. for result in result_list:
  312. yield result
  313. else:
  314. for result in result_list.data:
  315. yield result
  316. class BaseInstanceType(BaseCloudResource, InstanceType):
  317. def __init__(self, provider):
  318. super(BaseInstanceType, self).__init__(provider)
  319. def __eq__(self, other):
  320. return (isinstance(other, InstanceType) and
  321. # pylint:disable=protected-access
  322. self._provider == other._provider and
  323. self.id == other.id)
  324. @property
  325. def size_total_disk(self):
  326. return self.size_root_disk + self.size_ephemeral_disks
  327. def __repr__(self):
  328. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  329. self.name, self.id)
  330. class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
  331. def __init__(self, provider):
  332. super(BaseInstance, self).__init__(provider)
  333. def __eq__(self, other):
  334. return (isinstance(other, Instance) and
  335. # pylint:disable=protected-access
  336. self._provider == other._provider and
  337. self.id == other.id and
  338. # check from most to least likely mutables
  339. self.state == other.state and
  340. self.name == other.name and
  341. self.security_groups == other.security_groups and
  342. self.public_ips == other.public_ips and
  343. self.private_ips == other.private_ips and
  344. self.image_id == other.image_id)
  345. def wait_till_ready(self, timeout=None, interval=None):
  346. self.wait_for(
  347. [InstanceState.RUNNING],
  348. terminal_states=[InstanceState.TERMINATED, InstanceState.ERROR],
  349. timeout=timeout,
  350. interval=interval)
  351. def __repr__(self):
  352. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  353. self.name, self.id)
  354. class BaseLaunchConfig(LaunchConfig):
  355. def __init__(self, provider):
  356. self.provider = provider
  357. self.block_devices = []
  358. class BlockDeviceMapping(object):
  359. """
  360. Represents a block device mapping
  361. """
  362. def __init__(self, is_volume=False, source=None, is_root=None,
  363. size=None, delete_on_terminate=None):
  364. self.is_volume = is_volume
  365. self.source = source
  366. self.is_root = is_root
  367. self.size = size
  368. self.delete_on_terminate = delete_on_terminate
  369. def add_ephemeral_device(self):
  370. block_device = BaseLaunchConfig.BlockDeviceMapping()
  371. self.block_devices.append(block_device)
  372. def add_volume_device(self, source=None, is_root=None, size=None,
  373. delete_on_terminate=None):
  374. block_device = self._validate_volume_device(
  375. source=source, is_root=is_root, size=size,
  376. delete_on_terminate=delete_on_terminate)
  377. self.block_devices.append(block_device)
  378. def _validate_volume_device(self, source=None, is_root=None,
  379. size=None, delete_on_terminate=None):
  380. """
  381. Validates a volume based device and throws an
  382. InvalidConfigurationException if the configuration is incorrect.
  383. """
  384. if source is None and not size:
  385. raise InvalidConfigurationException(
  386. "A size must be specified for a blank new volume")
  387. if source and \
  388. not isinstance(source, (Snapshot, Volume, MachineImage)):
  389. raise InvalidConfigurationException(
  390. "Source must be a Snapshot, Volume, MachineImage or None")
  391. if size:
  392. if not isinstance(size, six.integer_types) or not size > 0:
  393. raise InvalidConfigurationException(
  394. "The size must be None or a number greater than 0")
  395. if is_root:
  396. for bd in self.block_devices:
  397. if bd.is_root:
  398. raise InvalidConfigurationException(
  399. "An existing block device: {0} has already been"
  400. " marked as root. There can only be one root device.")
  401. return BaseLaunchConfig.BlockDeviceMapping(
  402. is_volume=True, source=source, is_root=is_root, size=size,
  403. delete_on_terminate=delete_on_terminate)
  404. class BaseMachineImage(
  405. BaseCloudResource, BaseObjectLifeCycleMixin, MachineImage):
  406. def __init__(self, provider):
  407. super(BaseMachineImage, self).__init__(provider)
  408. def __eq__(self, other):
  409. return (isinstance(other, MachineImage) and
  410. # pylint:disable=protected-access
  411. self._provider == other._provider and
  412. self.id == other.id and
  413. # check from most to least likely mutables
  414. self.state == other.state and
  415. self.name == other.name and
  416. self.description == other.description)
  417. def wait_till_ready(self, timeout=None, interval=None):
  418. self.wait_for(
  419. [MachineImageState.AVAILABLE],
  420. terminal_states=[MachineImageState.ERROR],
  421. timeout=timeout,
  422. interval=interval)
  423. def __repr__(self):
  424. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  425. self.name, self.id)
  426. class BaseAttachmentInfo(AttachmentInfo):
  427. def __init__(self, volume, instance_id, device):
  428. self._volume = volume
  429. self._instance_id = instance_id
  430. self._device = device
  431. @property
  432. def volume(self):
  433. return self._volume
  434. @property
  435. def instance_id(self):
  436. return self._instance_id
  437. @property
  438. def device(self):
  439. return self._device
  440. class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
  441. def __init__(self, provider):
  442. super(BaseVolume, self).__init__(provider)
  443. def __eq__(self, other):
  444. return (isinstance(other, Volume) and
  445. # pylint:disable=protected-access
  446. self._provider == other._provider and
  447. self.id == other.id and
  448. # check from most to least likely mutables
  449. self.state == other.state and
  450. self.name == other.name)
  451. def wait_till_ready(self, timeout=None, interval=None):
  452. self.wait_for(
  453. [VolumeState.AVAILABLE],
  454. terminal_states=[VolumeState.ERROR, VolumeState.DELETED],
  455. timeout=timeout,
  456. interval=interval)
  457. def __repr__(self):
  458. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  459. self.name, self.id)
  460. class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
  461. def __init__(self, provider):
  462. super(BaseSnapshot, self).__init__(provider)
  463. def __eq__(self, other):
  464. return (isinstance(other, Snapshot) and
  465. # pylint:disable=protected-access
  466. self._provider == other._provider and
  467. self.id == other.id and
  468. # check from most to least likely mutables
  469. self.state == other.state and
  470. self.name == other.name)
  471. def wait_till_ready(self, timeout=None, interval=None):
  472. self.wait_for(
  473. [SnapshotState.AVAILABLE],
  474. terminal_states=[SnapshotState.ERROR],
  475. timeout=timeout,
  476. interval=interval)
  477. def __repr__(self):
  478. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  479. self.name, self.id)
  480. class BaseKeyPair(BaseCloudResource, KeyPair):
  481. def __init__(self, provider, key_pair):
  482. super(BaseKeyPair, self).__init__(provider)
  483. self._key_pair = key_pair
  484. def __eq__(self, other):
  485. return (isinstance(other, KeyPair) and
  486. # pylint:disable=protected-access
  487. self._provider == other._provider and
  488. self.name == other.name)
  489. @property
  490. def id(self):
  491. """
  492. Return the id of this key pair.
  493. """
  494. return self._key_pair.name
  495. @property
  496. def name(self):
  497. """
  498. Return the name of this key pair.
  499. """
  500. return self._key_pair.name
  501. def delete(self):
  502. """
  503. Delete this KeyPair.
  504. :rtype: bool
  505. :return: True if successful, otherwise False.
  506. """
  507. # This implementation assumes the `delete` method exists across
  508. # multiple providers.
  509. self._key_pair.delete()
  510. def __repr__(self):
  511. return "<CBKeyPair: {0}>".format(self.name)
  512. class BaseSecurityGroup(BaseCloudResource, SecurityGroup):
  513. def __init__(self, provider, security_group):
  514. super(BaseSecurityGroup, self).__init__(provider)
  515. self._security_group = security_group
  516. def __eq__(self, other):
  517. """
  518. Check if all the defined rules match across both security groups.
  519. """
  520. return (isinstance(other, SecurityGroup) and
  521. # pylint:disable=protected-access
  522. self._provider == other._provider and
  523. len(self.rules) == len(other.rules) and # Shortcut
  524. set(self.rules) == set(other.rules))
  525. def __ne__(self, other):
  526. return not self.__eq__(other)
  527. @property
  528. def id(self):
  529. """
  530. Get the ID of this security group.
  531. :rtype: str
  532. :return: Security group ID
  533. """
  534. return self._security_group.id
  535. @property
  536. def name(self):
  537. """
  538. Return the name of this security group.
  539. """
  540. return self._security_group.name
  541. @property
  542. def description(self):
  543. """
  544. Return the description of this security group.
  545. """
  546. return self._security_group.description
  547. def delete(self):
  548. """
  549. Delete this security group.
  550. """
  551. return self._security_group.delete()
  552. def __repr__(self):
  553. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  554. self.id, self.name)
  555. class BaseSecurityGroupRule(BaseCloudResource, SecurityGroupRule):
  556. def __init__(self, provider, rule, parent):
  557. super(BaseSecurityGroupRule, self).__init__(provider)
  558. self._rule = rule
  559. self.parent = parent
  560. def name(self):
  561. """
  562. Security group rules don't support names, so pass
  563. """
  564. pass
  565. def __repr__(self):
  566. return ("<CBSecurityGroupRule: IP: {0}; from: {1}; to: {2}; grp: {3}>"
  567. .format(self.ip_protocol, self.from_port, self.to_port,
  568. self.group))
  569. def __eq__(self, other):
  570. return self.ip_protocol == other.ip_protocol and \
  571. self.from_port == other.from_port and \
  572. self.to_port == other.to_port and \
  573. self.cidr_ip == other.cidr_ip
  574. def __ne__(self, other):
  575. return not self.__eq__(other)
  576. def __hash__(self):
  577. """
  578. Return a hash-based interpretation of all of the object's field values.
  579. This is requried for operations on hashed collections including
  580. ``set``, ``frozenset``, and ``dict``.
  581. """
  582. return hash("{0}{1}{2}{3}{4}".format(self.ip_protocol, self.from_port,
  583. self.to_port, self.cidr_ip,
  584. self.group))
  585. class BasePlacementZone(BaseCloudResource, PlacementZone):
  586. def __init__(self, provider):
  587. super(BasePlacementZone, self).__init__(provider)
  588. def __repr__(self):
  589. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  590. self.id)
  591. def __eq__(self, other):
  592. return (isinstance(other, PlacementZone) and
  593. # pylint:disable=protected-access
  594. self._provider == other._provider and
  595. self.id == other.id)
  596. class BaseRegion(BaseCloudResource, Region):
  597. def __init__(self, provider):
  598. super(BaseRegion, self).__init__(provider)
  599. def __repr__(self):
  600. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  601. self.id)
  602. def __eq__(self, other):
  603. return (isinstance(other, Region) and
  604. # pylint:disable=protected-access
  605. self._provider == other._provider and
  606. self.id == other.id)
  607. def to_json(self):
  608. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  609. js = {k: v for(k, v) in attr if not k.startswith('_')}
  610. js['zones'] = [z.name for z in self.zones]
  611. return js
  612. class BaseBucketObject(BaseCloudResource, BucketObject):
  613. # Regular expression for valid bucket keys.
  614. # They, must match the following criteria: http://docs.aws.amazon.com/"
  615. # AmazonS3/latest/dev/UsingMetadata.html#object-key-guidelines
  616. #
  617. # Note: The following regex is based on: https://stackoverflow.com/question
  618. # s/537772/what-is-the-most-correct-regular-expression-for-a-unix-file-path
  619. CB_NAME_PATTERN = re.compile(r"[^\0]+")
  620. def __init__(self, provider):
  621. super(BaseBucketObject, self).__init__(provider)
  622. def is_valid_resource_name(self, name):
  623. return True if self.CB_NAME_PATTERN.match(name) else False
  624. def save_content(self, target_stream):
  625. """
  626. Download this object and write its
  627. contents to the target_stream.
  628. """
  629. shutil.copyfileobj(self.iter_content(), target_stream)
  630. def __eq__(self, other):
  631. return (isinstance(other, BucketObject) and
  632. # pylint:disable=protected-access
  633. self._provider == other._provider and
  634. self.id == other.id and
  635. # check from most to least likely mutables
  636. self.name == other.name)
  637. def __repr__(self):
  638. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  639. self.name)
  640. class BaseBucket(BaseCloudResource, BasePageableObjectMixin, Bucket):
  641. # Regular expression for valid bucket names.
  642. # They, must match the following criteria: http://docs.aws.amazon.com/aws
  643. # cloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
  644. #
  645. # NOTE: The following regex is based on: https://stackoverflow.com/questio
  646. # ns/2063213/regular-expression-for-validating-dns-label-host-name
  647. CB_NAME_PATTERN = re.compile(r"^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$")
  648. def __init__(self, provider):
  649. super(BaseBucket, self).__init__(provider)
  650. def is_valid_resource_name(self, name):
  651. return True if self.CB_NAME_PATTERN.match(name) else False
  652. def __eq__(self, other):
  653. return (isinstance(other, Bucket) and
  654. # pylint:disable=protected-access
  655. self._provider == other._provider and
  656. self.id == other.id and
  657. # check from most to least likely mutables
  658. self.name == other.name)
  659. def __repr__(self):
  660. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  661. self.name)
  662. class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
  663. CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',
  664. 'CloudBridgeNet')
  665. def __init__(self, provider):
  666. super(BaseNetwork, self).__init__(provider)
  667. def __repr__(self):
  668. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  669. self.id, self.name)
  670. def wait_till_ready(self, timeout=None, interval=None):
  671. self.wait_for(
  672. [NetworkState.AVAILABLE],
  673. terminal_states=[NetworkState.ERROR],
  674. timeout=timeout,
  675. interval=interval)
  676. def __eq__(self, other):
  677. return (isinstance(other, Network) and
  678. # pylint:disable=protected-access
  679. self._provider == other._provider and
  680. self.id == other.id)
  681. class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
  682. CB_DEFAULT_SUBNET_NAME = os.environ.get('CB_DEFAULT_SUBNET_NAME',
  683. 'CloudBridgeSubnet')
  684. def __init__(self, provider):
  685. super(BaseSubnet, self).__init__(provider)
  686. def __repr__(self):
  687. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  688. self.id, self.name)
  689. def __eq__(self, other):
  690. return (isinstance(other, Subnet) and
  691. # pylint:disable=protected-access
  692. self._provider == other._provider and
  693. self.id == other.id)
  694. def wait_till_ready(self, timeout=None, interval=None):
  695. self.wait_for(
  696. [SubnetState.AVAILABLE],
  697. terminal_states=[SubnetState.ERROR],
  698. timeout=timeout,
  699. interval=interval)
  700. class BaseFloatingIP(BaseCloudResource, FloatingIP):
  701. def __init__(self, provider):
  702. super(BaseFloatingIP, self).__init__(provider)
  703. def name(self):
  704. """
  705. Security group rules don't support names, so pass
  706. """
  707. pass
  708. def __repr__(self):
  709. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  710. self.id, self.public_ip)
  711. def __eq__(self, other):
  712. return (isinstance(other, FloatingIP) and
  713. # pylint:disable=protected-access
  714. self._provider == other._provider and
  715. self.id == other.id)
  716. class BaseRouter(BaseCloudResource, Router):
  717. CB_DEFAULT_ROUTER_NAME = os.environ.get('CB_DEFAULT_ROUTER_NAME',
  718. 'CloudBridgeRouter')
  719. def __init__(self, provider):
  720. super(BaseRouter, self).__init__(provider)
  721. def __repr__(self):
  722. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
  723. self.name)
  724. def __eq__(self, other):
  725. return (isinstance(other, Router) and
  726. # pylint:disable=protected-access
  727. self._provider == other._provider and
  728. self.id == other.id)