resources.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  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. log.debug("InvalidNameException raised on %s", name, exc_info=True)
  193. raise InvalidNameException(
  194. u"Invalid name: %s. Name must be at most 63 characters "
  195. "long and consist of lowercase letters, numbers, "
  196. "underscores, dashes or international characters" % name)
  197. @property
  198. def _provider(self):
  199. return self.__provider
  200. def to_json(self):
  201. # Get all attributes but filter methods and private/magic ones
  202. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  203. js = {k: v for(k, v) in attr if not k.startswith('_')}
  204. return js
  205. class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
  206. """
  207. A base implementation of an ObjectLifeCycleMixin.
  208. This base implementation has an implementation of wait_for
  209. which refreshes the object's state till the desired ready states
  210. are reached. Subclasses must still implement the wait_till_ready
  211. method, since the desired ready states are object specific.
  212. """
  213. def wait_for(self, target_states, terminal_states=None, timeout=None,
  214. interval=None):
  215. if timeout is None:
  216. timeout = self._provider.config.default_wait_timeout
  217. if interval is None:
  218. interval = self._provider.config.default_wait_interval
  219. assert timeout >= 0
  220. assert interval >= 0
  221. assert timeout >= interval
  222. end_time = time.time() + timeout
  223. while self.state not in target_states:
  224. if self.state in (terminal_states or []):
  225. raise WaitStateException(
  226. "Object: {0} is in state: {1} which is a terminal state"
  227. " and cannot be waited on.".format(self, self.state))
  228. else:
  229. log.debug(
  230. "Object %s is in state: %s. Waiting another %s"
  231. " seconds to reach target state(s): %s...",
  232. self,
  233. self.state,
  234. int(end_time - time.time()),
  235. target_states)
  236. time.sleep(interval)
  237. if time.time() > end_time:
  238. raise WaitStateException(
  239. "Waited too long for object: {0} to become ready. It's"
  240. " still in state: {1}".format(self, self.state))
  241. self.refresh()
  242. log.debug("Object: %s successfully reached target state: %s",
  243. self, self.state)
  244. return True
  245. class BaseResultList(ResultList):
  246. def __init__(
  247. self, is_truncated, marker, supports_total, total=None, data=None):
  248. # call list constructor
  249. super(BaseResultList, self).__init__(data or [])
  250. self._marker = marker
  251. self._is_truncated = is_truncated
  252. self._supports_total = True if supports_total else False
  253. self._total = total
  254. @property
  255. def marker(self):
  256. return self._marker
  257. @property
  258. def is_truncated(self):
  259. return self._is_truncated
  260. @property
  261. def supports_total(self):
  262. return self._supports_total
  263. @property
  264. def total_results(self):
  265. return self._total
  266. class ServerPagedResultList(BaseResultList):
  267. """
  268. This is a convenience class that extends the :class:`BaseResultList` class
  269. and provides a server side implementation of paging. It is meant for use by
  270. provider developers and is not meant for direct use by end-users.
  271. This class can be used to wrap a partial result list when an operation
  272. supports server side paging.
  273. """
  274. @property
  275. def supports_server_paging(self):
  276. return True
  277. @property
  278. def data(self):
  279. raise NotImplementedError(
  280. "ServerPagedResultLists do not support the data property")
  281. class ClientPagedResultList(BaseResultList):
  282. """
  283. This is a convenience class that extends the :class:`BaseResultList` class
  284. and provides a client side implementation of paging. It is meant for use by
  285. provider developers and is not meant for direct use by end-users.
  286. This class can be used to wrap a full result list when an operation does
  287. not support server side paging. This class will then provide a paged view
  288. of the full result set entirely on the client side.
  289. """
  290. def __init__(self, provider, objects, limit=None, marker=None):
  291. self._objects = objects
  292. limit = limit or provider.config.default_result_limit
  293. total_size = len(objects)
  294. if marker:
  295. from_marker = itertools.dropwhile(
  296. lambda obj: not obj.id == marker, objects)
  297. # skip one past the marker
  298. next(from_marker, None)
  299. objects = list(from_marker)
  300. is_truncated = len(objects) > limit
  301. results = list(itertools.islice(objects, limit))
  302. super(ClientPagedResultList, self).__init__(
  303. is_truncated,
  304. results[-1].id if is_truncated else None,
  305. True, total=total_size,
  306. data=results)
  307. @property
  308. def supports_server_paging(self):
  309. return False
  310. @property
  311. def data(self):
  312. return self._objects
  313. class BasePageableObjectMixin(PageableObjectMixin):
  314. """
  315. A mixin to provide iteration capability for a class
  316. that support a list(limit, marker) method.
  317. """
  318. def __iter__(self):
  319. result_list = self.list()
  320. if result_list.supports_server_paging:
  321. for result in result_list:
  322. yield result
  323. while result_list.is_truncated:
  324. result_list = self.list(marker=result_list.marker)
  325. for result in result_list:
  326. yield result
  327. else:
  328. for result in result_list.data:
  329. yield result
  330. class BaseVMType(BaseCloudResource, VMType):
  331. def __init__(self, provider):
  332. super(BaseVMType, self).__init__(provider)
  333. def __eq__(self, other):
  334. return (isinstance(other, VMType) and
  335. # pylint:disable=protected-access
  336. self._provider == other._provider and
  337. self.id == other.id)
  338. @property
  339. def size_total_disk(self):
  340. return self.size_root_disk + self.size_ephemeral_disks
  341. def __repr__(self):
  342. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  343. self.name, self.id)
  344. class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
  345. def __init__(self, provider):
  346. super(BaseInstance, self).__init__(provider)
  347. def __eq__(self, other):
  348. return (isinstance(other, Instance) and
  349. # pylint:disable=protected-access
  350. self._provider == other._provider and
  351. self.id == other.id and
  352. # check from most to least likely mutables
  353. self.state == other.state and
  354. self.name == other.name and
  355. self.vm_firewalls == other.vm_firewalls and
  356. self.public_ips == other.public_ips and
  357. self.private_ips == other.private_ips and
  358. self.image_id == other.image_id)
  359. def wait_till_ready(self, timeout=None, interval=None):
  360. self.wait_for(
  361. [InstanceState.RUNNING],
  362. terminal_states=[InstanceState.DELETED, InstanceState.ERROR],
  363. timeout=timeout,
  364. interval=interval)
  365. def __repr__(self):
  366. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  367. self.name, self.id)
  368. class BaseLaunchConfig(LaunchConfig):
  369. def __init__(self, provider):
  370. self.provider = provider
  371. self.block_devices = []
  372. class BlockDeviceMapping(object):
  373. """
  374. Represents a block device mapping
  375. """
  376. def __init__(self, is_volume=False, source=None, is_root=None,
  377. size=None, delete_on_terminate=None):
  378. self.is_volume = is_volume
  379. self.source = source
  380. self.is_root = is_root
  381. self.size = size
  382. self.delete_on_terminate = delete_on_terminate
  383. def add_ephemeral_device(self):
  384. block_device = BaseLaunchConfig.BlockDeviceMapping()
  385. self.block_devices.append(block_device)
  386. def add_volume_device(self, source=None, is_root=None, size=None,
  387. delete_on_terminate=None):
  388. block_device = self._validate_volume_device(
  389. source=source, is_root=is_root, size=size,
  390. delete_on_terminate=delete_on_terminate)
  391. log.debug("Appending %s to the block_devices list",
  392. block_device)
  393. self.block_devices.append(block_device)
  394. def _validate_volume_device(self, source=None, is_root=None,
  395. size=None, delete_on_terminate=None):
  396. """
  397. Validates a volume based device and throws an
  398. InvalidConfigurationException if the configuration is incorrect.
  399. """
  400. if source is None and not size:
  401. log.exception("Raised InvalidConfigurationException, no"
  402. " size argument specified.")
  403. raise InvalidConfigurationException(
  404. "A size must be specified for a blank new volume")
  405. if source and \
  406. not isinstance(source, (Snapshot, Volume, MachineImage)):
  407. log.exception("InvalidConfigurationException raised, "
  408. "source argument not specified correctly.")
  409. raise InvalidConfigurationException(
  410. "Source must be a Snapshot, Volume, MachineImage or None")
  411. if size:
  412. if not isinstance(size, six.integer_types) or not size > 0:
  413. log.exception("InvalidConfigurationException raised, "
  414. " size argument must be greater than 0.")
  415. raise InvalidConfigurationException(
  416. "The size must be None or a number greater than 0")
  417. if is_root:
  418. for bd in self.block_devices:
  419. if bd.is_root:
  420. log.exception("InvalidConfigurationException raised,"
  421. "%s has already been marked as root", bd)
  422. raise InvalidConfigurationException(
  423. "An existing block device: {0} has already been"
  424. " marked as root. There can only be one root device.")
  425. return BaseLaunchConfig.BlockDeviceMapping(
  426. is_volume=True, source=source, is_root=is_root, size=size,
  427. delete_on_terminate=delete_on_terminate)
  428. class BaseMachineImage(
  429. BaseCloudResource, BaseObjectLifeCycleMixin, MachineImage):
  430. def __init__(self, provider):
  431. super(BaseMachineImage, self).__init__(provider)
  432. def __eq__(self, other):
  433. return (isinstance(other, MachineImage) and
  434. # pylint:disable=protected-access
  435. self._provider == other._provider and
  436. self.id == other.id and
  437. # check from most to least likely mutables
  438. self.state == other.state and
  439. self.name == other.name and
  440. self.description == other.description)
  441. def wait_till_ready(self, timeout=None, interval=None):
  442. self.wait_for(
  443. [MachineImageState.AVAILABLE],
  444. terminal_states=[MachineImageState.ERROR],
  445. timeout=timeout,
  446. interval=interval)
  447. def __repr__(self):
  448. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  449. self.name, self.id)
  450. class BaseAttachmentInfo(AttachmentInfo):
  451. def __init__(self, volume, instance_id, device):
  452. self._volume = volume
  453. self._instance_id = instance_id
  454. self._device = device
  455. @property
  456. def volume(self):
  457. return self._volume
  458. @property
  459. def instance_id(self):
  460. return self._instance_id
  461. @property
  462. def device(self):
  463. return self._device
  464. class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
  465. def __init__(self, provider):
  466. super(BaseVolume, self).__init__(provider)
  467. def __eq__(self, other):
  468. return (isinstance(other, Volume) and
  469. # pylint:disable=protected-access
  470. self._provider == other._provider and
  471. self.id == other.id and
  472. # check from most to least likely mutables
  473. self.state == other.state and
  474. self.name == other.name)
  475. def wait_till_ready(self, timeout=None, interval=None):
  476. self.wait_for(
  477. [VolumeState.AVAILABLE],
  478. terminal_states=[VolumeState.ERROR, VolumeState.DELETED],
  479. timeout=timeout,
  480. interval=interval)
  481. def __repr__(self):
  482. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  483. self.name, self.id)
  484. class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
  485. def __init__(self, provider):
  486. super(BaseSnapshot, self).__init__(provider)
  487. def __eq__(self, other):
  488. return (isinstance(other, Snapshot) and
  489. # pylint:disable=protected-access
  490. self._provider == other._provider and
  491. self.id == other.id and
  492. # check from most to least likely mutables
  493. self.state == other.state and
  494. self.name == other.name)
  495. def wait_till_ready(self, timeout=None, interval=None):
  496. self.wait_for(
  497. [SnapshotState.AVAILABLE],
  498. terminal_states=[SnapshotState.ERROR],
  499. timeout=timeout,
  500. interval=interval)
  501. def __repr__(self):
  502. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  503. self.name, self.id)
  504. class BaseKeyPair(BaseCloudResource, KeyPair):
  505. def __init__(self, provider, key_pair):
  506. super(BaseKeyPair, self).__init__(provider)
  507. self._key_pair = key_pair
  508. def __eq__(self, other):
  509. return (isinstance(other, KeyPair) and
  510. # pylint:disable=protected-access
  511. self._provider == other._provider and
  512. self.name == other.name)
  513. @property
  514. def id(self):
  515. """
  516. Return the id of this key pair.
  517. """
  518. return self._key_pair.name
  519. @property
  520. def name(self):
  521. """
  522. Return the name of this key pair.
  523. """
  524. return self._key_pair.name
  525. def delete(self):
  526. """
  527. Delete this KeyPair.
  528. :rtype: bool
  529. :return: True if successful, otherwise False.
  530. """
  531. # This implementation assumes the `delete` method exists across
  532. # multiple providers.
  533. self._key_pair.delete()
  534. def __repr__(self):
  535. return "<CBKeyPair: {0}>".format(self.name)
  536. class BaseVMFirewall(BaseCloudResource, VMFirewall):
  537. def __init__(self, provider, vm_firewall):
  538. super(BaseVMFirewall, self).__init__(provider)
  539. self._vm_firewall = vm_firewall
  540. def __eq__(self, other):
  541. """
  542. Check if all the defined rules match across both VM firewalls.
  543. """
  544. return (isinstance(other, VMFirewall) and
  545. # pylint:disable=protected-access
  546. self._provider == other._provider and
  547. set(self.rules) == set(other.rules))
  548. def __ne__(self, other):
  549. return not self.__eq__(other)
  550. @property
  551. def id(self):
  552. """
  553. Get the ID of this VM firewall.
  554. :rtype: str
  555. :return: VM firewall ID
  556. """
  557. return self._vm_firewall.id
  558. @property
  559. def name(self):
  560. """
  561. Return the name of this VM firewall.
  562. """
  563. return self._vm_firewall.name
  564. @property
  565. def description(self):
  566. """
  567. Return the description of this VM firewall.
  568. """
  569. return self._vm_firewall.description
  570. def delete(self):
  571. """
  572. Delete this VM firewall.
  573. """
  574. return self._vm_firewall.delete()
  575. def __repr__(self):
  576. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  577. self.id, self.name)
  578. class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
  579. VMFirewallRuleContainer):
  580. def __init__(self, provider, firewall):
  581. self.__provider = provider
  582. self.firewall = firewall
  583. @property
  584. def _provider(self):
  585. return self.__provider
  586. def get(self, rule_id):
  587. matches = [rule for rule in self if rule.id == rule_id]
  588. if matches:
  589. return matches[0]
  590. else:
  591. return None
  592. def find(self, **kwargs):
  593. matches = self
  594. def filter_by(prop_name, rules):
  595. prop_val = kwargs.pop(prop_name, None)
  596. if prop_val:
  597. match = [r for r in rules if getattr(r, prop_name) == prop_val]
  598. return match
  599. return rules
  600. matches = filter_by('name', matches)
  601. matches = filter_by('direction', matches)
  602. matches = filter_by('protocol', matches)
  603. matches = filter_by('from_port', matches)
  604. matches = filter_by('to_port', matches)
  605. matches = filter_by('cidr', matches)
  606. matches = filter_by('src_dest_fw', matches)
  607. matches = filter_by('src_dest_fw_id', matches)
  608. limit = kwargs.pop('limit', None)
  609. marker = kwargs.pop('marker', None)
  610. return ClientPagedResultList(self._provider, matches,
  611. limit=limit, marker=marker)
  612. def delete(self, rule_id):
  613. rule = self.get(rule_id)
  614. if rule:
  615. rule.delete()
  616. class BaseVMFirewallRule(BaseCloudResource, VMFirewallRule):
  617. def __init__(self, parent_fw, rule):
  618. # pylint:disable=protected-access
  619. super(BaseVMFirewallRule, self).__init__(
  620. parent_fw._provider)
  621. self.firewall = parent_fw
  622. self._rule = rule
  623. # Cache name
  624. self._name = "{0}-{1}-{2}-{3}-{4}-{5}".format(
  625. self.direction, self.protocol, self.from_port, self.to_port,
  626. self.cidr, self.src_dest_fw_id).lower()
  627. @property
  628. def name(self):
  629. return self._name
  630. def __repr__(self):
  631. return ("<{0}: id: {1}; direction: {2}; protocol: {3}; from: {4};"
  632. " to: {5}; cidr: {6}, src_dest_fw: {7}>"
  633. .format(self.__class__.__name__, self.id, self.direction,
  634. self.protocol, self.from_port, self.to_port, self.cidr,
  635. self.src_dest_fw_id))
  636. def __eq__(self, other):
  637. return (isinstance(other, VMFirewallRule) and
  638. self.direction == other.direction and
  639. self.protocol == other.protocol and
  640. self.from_port == other.from_port and
  641. self.to_port == other.to_port and
  642. self.cidr == other.cidr and
  643. self.src_dest_fw_id == other.src_dest_fw_id)
  644. def __ne__(self, other):
  645. return not self.__eq__(other)
  646. def __hash__(self):
  647. """
  648. Return a hash-based interpretation of all of the object's field values.
  649. This is requeried for operations on hashed collections including
  650. ``set``, ``frozenset``, and ``dict``.
  651. """
  652. return hash("{0}{1}{2}{3}{4}{5}".format(
  653. self.direction, self.protocol, self.from_port, self.to_port,
  654. self.cidr, self.src_dest_fw_id))
  655. def to_json(self):
  656. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  657. js = {k: v for (k, v) in attr if not k.startswith('_')}
  658. js['src_dest_fw'] = self.src_dest_fw_id
  659. js['firewall'] = self.firewall.id
  660. return js
  661. class BasePlacementZone(BaseCloudResource, PlacementZone):
  662. def __init__(self, provider):
  663. super(BasePlacementZone, 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, PlacementZone) and
  669. # pylint:disable=protected-access
  670. self._provider == other._provider and
  671. self.id == other.id)
  672. class BaseRegion(BaseCloudResource, Region):
  673. def __init__(self, provider):
  674. super(BaseRegion, self).__init__(provider)
  675. def __repr__(self):
  676. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  677. self.id)
  678. def __eq__(self, other):
  679. return (isinstance(other, Region) and
  680. # pylint:disable=protected-access
  681. self._provider == other._provider and
  682. self.id == other.id)
  683. def to_json(self):
  684. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  685. js = {k: v for(k, v) in attr if not k.startswith('_')}
  686. js['zones'] = [z.name for z in self.zones]
  687. return js
  688. class BaseBucketObject(BaseCloudResource, BucketObject):
  689. # Regular expression for valid bucket keys.
  690. # They, must match the following criteria: http://docs.aws.amazon.com/"
  691. # AmazonS3/latest/dev/UsingMetadata.html#object-key-guidelines
  692. #
  693. # Note: The following regex is based on: https://stackoverflow.com/question
  694. # s/537772/what-is-the-most-correct-regular-expression-for-a-unix-file-path
  695. CB_NAME_PATTERN = re.compile(r"[^\0]+")
  696. def __init__(self, provider):
  697. super(BaseBucketObject, self).__init__(provider)
  698. @staticmethod
  699. def is_valid_resource_name(name):
  700. return True if BaseBucketObject.CB_NAME_PATTERN.match(name) else False
  701. @staticmethod
  702. def assert_valid_resource_name(name):
  703. if not BaseBucketObject.is_valid_resource_name(name):
  704. log.debug("InvalidNameException raised on %s", name, exc_info=True)
  705. raise InvalidNameException(
  706. u"Invalid object name: %s. Name must match criteria defined "
  707. "in: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMeta"
  708. "data.html#object-key-guidelines" % name)
  709. def save_content(self, target_stream):
  710. """
  711. Download this object and write its
  712. contents to the target_stream.
  713. """
  714. shutil.copyfileobj(self.iter_content(), target_stream)
  715. def __eq__(self, other):
  716. return (isinstance(other, BucketObject) and
  717. # pylint:disable=protected-access
  718. self._provider == other._provider and
  719. self.id == other.id and
  720. # check from most to least likely mutables
  721. self.name == other.name)
  722. def __repr__(self):
  723. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  724. self.name)
  725. class BaseBucket(BaseCloudResource, Bucket):
  726. # Regular expression for valid bucket names.
  727. # They, must match the following criteria: http://docs.aws.amazon.com/aws
  728. # cloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
  729. #
  730. # NOTE: The following regex is based on: https://stackoverflow.com/questio
  731. # ns/2063213/regular-expression-for-validating-dns-label-host-name
  732. CB_NAME_PATTERN = re.compile(r"^(?![0-9]+$)(?!-)[a-z0-9-]{3,63}(?<!-)$")
  733. def __init__(self, provider):
  734. super(BaseBucket, self).__init__(provider)
  735. @staticmethod
  736. def is_valid_resource_name(name):
  737. return True if BaseBucket.CB_NAME_PATTERN.match(name) else False
  738. @staticmethod
  739. def assert_valid_resource_name(name):
  740. if not BaseBucket.is_valid_resource_name(name):
  741. log.debug("Invalid resource name %s", name, exc_info=True)
  742. raise InvalidNameException(
  743. u"Invalid bucket name: %s. Name must match criteria defined "
  744. "in: http://docs.aws.amazon.com/awscloudtrail/latest/userguide"
  745. "/cloudtrail-s3-bucket-naming-requirements.html" % name)
  746. def __eq__(self, other):
  747. return (isinstance(other, Bucket) and
  748. # pylint:disable=protected-access
  749. self._provider == other._provider and
  750. self.id == other.id and
  751. # check from most to least likely mutables
  752. self.name == other.name)
  753. def __repr__(self):
  754. return "<CB-{0}: {1}>".format(self.__class__.__name__,
  755. self.name)
  756. class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
  757. def __init__(self, provider, bucket):
  758. self.__provider = provider
  759. self.bucket = bucket
  760. @property
  761. def _provider(self):
  762. return self.__provider
  763. class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
  764. CB_DEFAULT_NETWORK_NAME = os.environ.get('CB_DEFAULT_NETWORK_NAME',
  765. 'cloudbridge-net')
  766. def __init__(self, provider):
  767. super(BaseNetwork, self).__init__(provider)
  768. def __repr__(self):
  769. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  770. self.id, self.name)
  771. def wait_till_ready(self, timeout=None, interval=None):
  772. self.wait_for(
  773. [NetworkState.AVAILABLE],
  774. terminal_states=[NetworkState.ERROR],
  775. timeout=timeout,
  776. interval=interval)
  777. def create_subnet(self, name, cidr_block, zone=None):
  778. return self._provider.networking.subnets.create(
  779. name=name, network=self, cidr_block=cidr_block, zone=zone)
  780. def __eq__(self, other):
  781. return (isinstance(other, Network) and
  782. # pylint:disable=protected-access
  783. self._provider == other._provider and
  784. self.id == other.id)
  785. class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
  786. CB_DEFAULT_SUBNET_NAME = os.environ.get('CB_DEFAULT_SUBNET_NAME',
  787. 'cloudbridge-subnet')
  788. def __init__(self, provider):
  789. super(BaseSubnet, self).__init__(provider)
  790. def __repr__(self):
  791. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  792. self.id, self.name)
  793. def __eq__(self, other):
  794. return (isinstance(other, Subnet) and
  795. # pylint:disable=protected-access
  796. self._provider == other._provider and
  797. self.id == other.id)
  798. def wait_till_ready(self, timeout=None, interval=None):
  799. self.wait_for(
  800. [SubnetState.AVAILABLE],
  801. terminal_states=[SubnetState.ERROR],
  802. timeout=timeout,
  803. interval=interval)
  804. class BaseFloatingIP(BaseCloudResource, FloatingIP):
  805. def __init__(self, provider):
  806. super(BaseFloatingIP, self).__init__(provider)
  807. @property
  808. def name(self):
  809. """
  810. VM firewall rules don't support names, so pass
  811. """
  812. return self.public_ip
  813. def __repr__(self):
  814. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
  815. self.id, self.public_ip)
  816. def __eq__(self, other):
  817. return (isinstance(other, FloatingIP) and
  818. # pylint:disable=protected-access
  819. self._provider == other._provider and
  820. self.id == other.id)
  821. class BaseRouter(BaseCloudResource, Router):
  822. CB_DEFAULT_ROUTER_NAME = os.environ.get('CB_DEFAULT_ROUTER_NAME',
  823. 'cloudbridge-router')
  824. def __init__(self, provider):
  825. super(BaseRouter, self).__init__(provider)
  826. def __repr__(self):
  827. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
  828. self.name)
  829. def __eq__(self, other):
  830. return (isinstance(other, Router) and
  831. # pylint:disable=protected-access
  832. self._provider == other._provider and
  833. self.id == other.id)
  834. class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
  835. InternetGateway):
  836. CB_DEFAULT_INET_GATEWAY_NAME = os.environ.get(
  837. 'CB_DEFAULT_INET_GATEWAY_NAME', 'cloudbridge-inetgateway')
  838. def __init__(self, provider):
  839. super(BaseInternetGateway, self).__init__(provider)
  840. def __repr__(self):
  841. return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
  842. self.name)
  843. def __eq__(self, other):
  844. return (isinstance(other, InternetGateway) and
  845. # pylint:disable=protected-access
  846. self._provider == other._provider and
  847. self.id == other.id)
  848. def wait_till_ready(self, timeout=None, interval=None):
  849. self.wait_for(
  850. [GatewayState.AVAILABLE],
  851. terminal_states=[GatewayState.ERROR, GatewayState.UNKNOWN],
  852. timeout=timeout,
  853. interval=interval)