resources.py 36 KB

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