resources.py 40 KB

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