PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/imh-python/lib/python3.9/site-packages/libcloud/loadbalancer/drivers
Viewing File: /opt/imh-python/lib/python3.9/site-packages/libcloud/loadbalancer/drivers/rackspace.py
# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime try: import simplejson as json except ImportError: import json from libcloud.utils.py3 import httplib from libcloud.utils.misc import reverse_dict from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm from libcloud.loadbalancer.base import DEFAULT_ALGORITHM from libcloud.compute.drivers.rackspace import RackspaceConnection from libcloud.common.types import LibcloudError from libcloud.common.base import JsonResponse, PollingConnection from libcloud.loadbalancer.types import State, MemberCondition from libcloud.common.openstack import OpenStackDriverMixin from libcloud.common.rackspace import AUTH_URL ENDPOINT_ARGS_MAP = { 'dfw': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'DFW'}, 'ord': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'ORD'}, 'iad': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'IAD'}, 'lon': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'LON'}, 'syd': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'SYD'}, 'hkg': {'service_type': 'rax:load-balancer', 'name': 'cloudLoadBalancers', 'region': 'HKG'}, } class RackspaceResponse(JsonResponse): def parse_body(self): if not self.body: return None return super(RackspaceResponse, self).parse_body() def success(self): return 200 <= int(self.status) <= 299 class RackspaceHealthMonitor(object): """ :param type: type of load balancer. currently CONNECT (connection monitoring), HTTP, HTTPS (connection and HTTP monitoring) are supported. :type type: ``str`` :param delay: minimum seconds to wait before executing the health monitor. (Must be between 1 and 3600) :type delay: ``int`` :param timeout: maximum seconds to wait when establishing a connection before timing out. (Must be between 1 and 3600) :type timeout: ``int`` :param attempts_before_deactivation: Number of monitor failures before removing a node from rotation. (Must be between 1 and 10) :type attempts_before_deactivation: ``int`` """ def __init__(self, type, delay, timeout, attempts_before_deactivation): self.type = type self.delay = delay self.timeout = timeout self.attempts_before_deactivation = attempts_before_deactivation def __repr__(self): return ('<RackspaceHealthMonitor: type=%s, delay=%d, timeout=%d, ' 'attempts_before_deactivation=%d>' % (self.type, self.delay, self.timeout, self.attempts_before_deactivation)) def _to_dict(self): return { 'type': self.type, 'delay': self.delay, 'timeout': self.timeout, 'attemptsBeforeDeactivation': self.attempts_before_deactivation } class RackspaceHTTPHealthMonitor(RackspaceHealthMonitor): """ A HTTP health monitor adds extra features to a Rackspace health monitor. :param path: the HTTP path to monitor. :type path: ``str`` :param body_regex: Regular expression used to evaluate the body of the HTTP response. :type body_regex: ``str`` :param status_regex: Regular expression used to evaluate the HTTP status code of the response. :type status_regex: ``str`` """ def __init__(self, type, delay, timeout, attempts_before_deactivation, path, body_regex, status_regex): super(RackspaceHTTPHealthMonitor, self).__init__( type, delay, timeout, attempts_before_deactivation) self.path = path self.body_regex = body_regex self.status_regex = status_regex def __repr__(self): return ('<RackspaceHTTPHealthMonitor: type=%s, delay=%d, timeout=%d, ' 'attempts_before_deactivation=%d, path=%s, body_regex=%s, ' 'status_regex=%s>' % (self.type, self.delay, self.timeout, self.attempts_before_deactivation, self.path, self.body_regex, self.status_regex)) def _to_dict(self): super_dict = super(RackspaceHTTPHealthMonitor, self)._to_dict() super_dict['path'] = self.path super_dict['statusRegex'] = self.status_regex if self.body_regex: super_dict['bodyRegex'] = self.body_regex return super_dict class RackspaceConnectionThrottle(object): """ :param min_connections: Minimum number of connections per IP address before applying throttling. :type min_connections: ``int`` :param max_connections: Maximum number of connections per IP address. (Must be between 0 and 100000, 0 allows an unlimited number of connections.) :type max_connections: ``int`` :param max_connection_rate: Maximum number of connections allowed from a single IP address within the given rate_interval_seconds. (Must be between 0 and 100000, 0 allows an unlimited number of connections.) :type max_connection_rate: ``int`` :param rate_interval_seconds: Interval at which the max_connection_rate is enforced. (Must be between 1 and 3600.) :type rate_interval_seconds: ``int`` """ def __init__(self, min_connections, max_connections, max_connection_rate, rate_interval_seconds): self.min_connections = min_connections self.max_connections = max_connections self.max_connection_rate = max_connection_rate self.rate_interval_seconds = rate_interval_seconds def __repr__(self): return ('<RackspaceConnectionThrottle: min_connections=%d, ' 'max_connections=%d, max_connection_rate=%d, ' 'rate_interval_seconds=%d>' % (self.min_connections, self.max_connections, self.max_connection_rate, self.rate_interval_seconds)) def _to_dict(self): return { 'maxConnections': self.max_connections, 'minConnections': self.min_connections, 'maxConnectionRate': self.max_connection_rate, 'rateInterval': self.rate_interval_seconds } class RackspaceAccessRuleType(object): ALLOW = 0 DENY = 1 _RULE_TYPE_STRING_MAP = { ALLOW: 'ALLOW', DENY: 'DENY' } class RackspaceAccessRule(object): """ An access rule allows or denies traffic to a Load Balancer based on the incoming IPs. :param id: Unique identifier to refer to this rule by. :type id: ``str`` :param rule_type: RackspaceAccessRuleType.ALLOW or RackspaceAccessRuleType.DENY. :type id: ``int`` :param address: IP address or cidr (can be IPv4 or IPv6). :type address: ``str`` """ def __init__(self, id=None, rule_type=None, address=None): self.id = id self.rule_type = rule_type self.address = address def _to_dict(self): type_string =\ RackspaceAccessRuleType._RULE_TYPE_STRING_MAP[self.rule_type] as_dict = { 'type': type_string, 'address': self.address } if self.id is not None: as_dict['id'] = self.id return as_dict class RackspaceConnection(RackspaceConnection, PollingConnection): responseCls = RackspaceResponse auth_url = AUTH_URL poll_interval = 2 timeout = 80 cache_busting = True def request(self, action, params=None, data='', headers=None, method='GET'): if not headers: headers = {} if not params: params = {} if method in ('POST', 'PUT'): headers['Content-Type'] = 'application/json' return super(RackspaceConnection, self).request( action=action, params=params, data=data, method=method, headers=headers) def get_poll_request_kwargs(self, response, context, request_kwargs): return {'action': request_kwargs['action'], 'method': 'GET'} def has_completed(self, response): state = response.object['loadBalancer']['status'] if state == 'ERROR': raise LibcloudError("Load balancer entered an ERROR state.", driver=self.driver) return state == 'ACTIVE' def encode_data(self, data): return data class RackspaceLBDriver(Driver, OpenStackDriverMixin): connectionCls = RackspaceConnection api_name = 'rackspace_lb' name = 'Rackspace LB' website = 'http://www.rackspace.com/' LB_STATE_MAP = { 'ACTIVE': State.RUNNING, 'BUILD': State.PENDING, 'ERROR': State.ERROR, 'DELETED': State.DELETED, 'PENDING_UPDATE': State.PENDING, 'PENDING_DELETE': State.PENDING } LB_MEMBER_CONDITION_MAP = { 'ENABLED': MemberCondition.ENABLED, 'DISABLED': MemberCondition.DISABLED, 'DRAINING': MemberCondition.DRAINING } CONDITION_LB_MEMBER_MAP = reverse_dict(LB_MEMBER_CONDITION_MAP) _VALUE_TO_ALGORITHM_MAP = { 'RANDOM': Algorithm.RANDOM, 'ROUND_ROBIN': Algorithm.ROUND_ROBIN, 'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS, 'WEIGHTED_ROUND_ROBIN': Algorithm.WEIGHTED_ROUND_ROBIN, 'WEIGHTED_LEAST_CONNECTIONS': Algorithm.WEIGHTED_LEAST_CONNECTIONS } _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP) def __init__(self, key, secret=None, secure=True, host=None, port=None, region='ord', **kwargs): ex_force_region = kwargs.pop('ex_force_region', None) if ex_force_region: # For backward compatibility region = ex_force_region OpenStackDriverMixin.__init__(self, **kwargs) super(RackspaceLBDriver, self).__init__(key=key, secret=secret, secure=secure, host=host, port=port, region=region) @classmethod def list_regions(cls): return ENDPOINT_ARGS_MAP.keys() def _ex_connection_class_kwargs(self): endpoint_args = ENDPOINT_ARGS_MAP[self.region] kwargs = self.openstack_connection_kwargs() kwargs['get_endpoint_args'] = endpoint_args return kwargs def list_protocols(self): return self._to_protocols( self.connection.request('/loadbalancers/protocols').object) def ex_list_protocols_with_default_ports(self): """ List protocols with default ports. :rtype: ``list`` of ``tuple`` :return: A list of protocols with default ports included. """ return self._to_protocols_with_default_ports( self.connection.request('/loadbalancers/protocols').object) def list_balancers(self, ex_member_address=None, ex_status=None, ex_changes_since=None, ex_params={}): """ @inherits: :class:`Driver.list_balancers` :param ex_member_address: Optional IP address of the attachment member. If provided, only the load balancers which have this member attached will be returned. :type ex_member_address: ``str`` :param ex_status: Optional. Filter balancers by status :type ex_status: ``str`` :param ex_changes_since: Optional. List all load balancers that have changed since the specified date/time :type ex_changes_since: ``str`` :param ex_params: Optional. Set parameters to be submitted to the API in the query string :type ex_params: ``dict`` """ params = {} if ex_member_address: params['nodeaddress'] = ex_member_address if ex_status: params['status'] = ex_status if ex_changes_since: params['changes-since'] = ex_changes_since for key, value in ex_params.items(): params[key] = value return self._to_balancers( self.connection.request('/loadbalancers', params=params).object) def create_balancer(self, name, members, protocol='http', port=80, algorithm=DEFAULT_ALGORITHM): return self.ex_create_balancer(name, members, protocol, port, algorithm) def ex_create_balancer(self, name, members, protocol='http', port=80, algorithm=DEFAULT_ALGORITHM, vip='PUBLIC'): """ Creates a new load balancer instance :param name: Name of the new load balancer (required) :type name: ``str`` :param members: ``list`` of:class:`Member`s to attach to balancer :type members: ``list`` of :class:`Member` :param protocol: Loadbalancer protocol, defaults to http. :type protocol: ``str`` :param port: Port the load balancer should listen on, defaults to 80 :type port: ``str`` :param algorithm: Load balancing algorithm, defaults to LBAlgorithm.ROUND_ROBIN :type algorithm: :class:`Algorithm` :param vip: Virtual ip type of PUBLIC, SERVICENET, or ID of a virtual ip :type vip: ``str`` :rtype: :class:`LoadBalancer` """ balancer_attrs = self._kwargs_to_mutable_attrs( name=name, protocol=protocol, port=port, algorithm=algorithm, vip=vip) balancer_attrs.update({ 'nodes': [self._member_attributes(member) for member in members], }) # balancer_attrs['nodes'] = ['fu'] balancer_object = {"loadBalancer": balancer_attrs} resp = self.connection.request('/loadbalancers', method='POST', data=json.dumps(balancer_object)) return self._to_balancer(resp.object['loadBalancer']) def _member_attributes(self, member): member_attributes = {'address': member.ip, 'port': member.port} member_attributes.update(self._kwargs_to_mutable_member_attrs( **member.extra)) # If the condition is not specified on the member, then it should be # set to ENABLED by default if 'condition' not in member_attributes: member_attributes['condition'] =\ self.CONDITION_LB_MEMBER_MAP[MemberCondition.ENABLED] return member_attributes def destroy_balancer(self, balancer): uri = '/loadbalancers/%s' % (balancer.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_destroy_balancers(self, balancers): """ Destroys a list of Balancers (the API supports up to 10). :param balancers: A list of Balancers to destroy. :type balancers: ``list`` of :class:`LoadBalancer` :return: Returns whether the destroy request was accepted. :rtype: ``bool`` """ ids = [('id', balancer.id) for balancer in balancers] resp = self.connection.request('/loadbalancers', method='DELETE', params=ids) return resp.status == httplib.ACCEPTED def get_balancer(self, balancer_id): uri = '/loadbalancers/%s' % (balancer_id) resp = self.connection.request(uri) return self._to_balancer(resp.object["loadBalancer"]) def balancer_attach_member(self, balancer, member): member_object = {"nodes": [self._member_attributes(member)]} uri = '/loadbalancers/%s/nodes' % (balancer.id) resp = self.connection.request(uri, method='POST', data=json.dumps(member_object)) return self._to_members(resp.object, balancer)[0] def ex_balancer_attach_members(self, balancer, members): """ Attaches a list of members to a load balancer. :param balancer: The Balancer to which members will be attached. :type balancer: :class:`LoadBalancer` :param members: A list of Members to attach. :type members: ``list`` of :class:`Member` :rtype: ``list`` of :class:`Member` """ member_objects = {"nodes": [self._member_attributes(member) for member in members]} uri = '/loadbalancers/%s/nodes' % (balancer.id) resp = self.connection.request(uri, method='POST', data=json.dumps(member_objects)) return self._to_members(resp.object, balancer) def balancer_detach_member(self, balancer, member): # Loadbalancer always needs to have at least 1 member. # Last member cannot be detached. You can only disable it or destroy # the balancer. uri = '/loadbalancers/%s/nodes/%s' % (balancer.id, member.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_balancer_detach_members(self, balancer, members): """ Detaches a list of members from a balancer (the API supports up to 10). This method blocks until the detach request has been processed and the balancer is in a RUNNING state again. :param balancer: The Balancer to detach members from. :type balancer: :class:`LoadBalancer` :param members: A list of Members to detach. :type members: ``list`` of :class:`Member` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_balancer_detach_members_no_poll(balancer, members) if not accepted: msg = 'Detach members request was not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_balancer_detach_members_no_poll(self, balancer, members): """ Detaches a list of members from a balancer (the API supports up to 10). This method returns immediately. :param balancer: The Balancer to detach members from. :type balancer: :class:`LoadBalancer` :param members: A list of Members to detach. :type members: ``list`` of :class:`Member` :return: Returns whether the detach request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/nodes' % (balancer.id) ids = [('id', member.id) for member in members] resp = self.connection.request(uri, method='DELETE', params=ids) return resp.status == httplib.ACCEPTED def balancer_list_members(self, balancer): uri = '/loadbalancers/%s/nodes' % (balancer.id) data = self.connection.request(uri).object return self._to_members(data, balancer) def update_balancer(self, balancer, **kwargs): attrs = self._kwargs_to_mutable_attrs(**kwargs) resp = self.connection.async_request( action='/loadbalancers/%s' % balancer.id, method='PUT', data=json.dumps(attrs)) return self._to_balancer(resp.object["loadBalancer"]) def ex_update_balancer_no_poll(self, balancer, **kwargs): """ Update balancer no poll. @inherits: :class:`Driver.update_balancer` """ attrs = self._kwargs_to_mutable_attrs(**kwargs) resp = self.connection.request( action='/loadbalancers/%s' % balancer.id, method='PUT', data=json.dumps(attrs)) return resp.status == httplib.ACCEPTED def ex_balancer_update_member(self, balancer, member, **kwargs): """ Updates a Member's extra attributes for a Balancer. The attributes can include 'weight' or 'condition'. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to update the member on. :type balancer: :class:`LoadBalancer` :param member: Member which should be used :type member: :class:`Member` :keyword **kwargs: New attributes. Should contain either 'weight' or 'condition'. 'condition' can be set to 'ENABLED', 'DISABLED'. or 'DRAINING'. 'weight' can be set to a positive integer between 1 and 100, with a higher weight indicating that the node will receive more traffic (assuming the Balancer is using a weighted algorithm). :type **kwargs: ``dict`` :return: Updated Member. :rtype: :class:`Member` """ accepted = self.ex_balancer_update_member_no_poll( balancer, member, **kwargs) if not accepted: msg = 'Update member attributes was not accepted' raise LibcloudError(msg, driver=self) balancer = self._get_updated_balancer(balancer) members = balancer.extra['members'] updated_members = [m for m in members if m.id == member.id] if not updated_members: raise LibcloudError('Could not find updated member') return updated_members[0] def ex_balancer_update_member_no_poll(self, balancer, member, **kwargs): """ Updates a Member's extra attributes for a Balancer. The attribute can include 'weight' or 'condition'. This method returns immediately. :param balancer: Balancer to update the member on. :type balancer: :class:`LoadBalancer` :param member: Member which should be used :type member: :class:`Member` :keyword **kwargs: New attributes. Should contain either 'weight' or 'condition'. 'condition' can be set to 'ENABLED', 'DISABLED'. or 'DRAINING'. 'weight' can be set to a positive integer between 1 and 100, with a higher weight indicating that the node will receive more traffic (assuming the Balancer is using a weighted algorithm). :type **kwargs: ``dict`` :return: Returns whether the update request was accepted. :rtype: ``bool`` """ resp = self.connection.request( action='/loadbalancers/%s/nodes/%s' % (balancer.id, member.id), method='PUT', data=json.dumps(self._kwargs_to_mutable_member_attrs(**kwargs)) ) return resp.status == httplib.ACCEPTED def ex_list_algorithm_names(self): """ Lists algorithms supported by the API. Returned as strings because this list may change in the future. :rtype: ``list`` of ``str`` """ response = self.connection.request('/loadbalancers/algorithms') return [a["name"].upper() for a in response.object["algorithms"]] def ex_get_balancer_error_page(self, balancer): """ List error page configured for the specified load balancer. :param balancer: Balancer which should be used :type balancer: :class:`LoadBalancer` :rtype: ``str`` """ uri = '/loadbalancers/%s/errorpage' % (balancer.id) resp = self.connection.request(uri) return resp.object["errorpage"]["content"] def ex_balancer_access_list(self, balancer): """ List the access list. :param balancer: Balancer which should be used :type balancer: :class:`LoadBalancer` :rtype: ``list`` of :class:`RackspaceAccessRule` """ uri = '/loadbalancers/%s/accesslist' % (balancer.id) resp = self.connection.request(uri) return [self._to_access_rule(el) for el in resp.object["accessList"]] def _get_updated_balancer(self, balancer): """ Updating a balancer's attributes puts a balancer into 'PENDING_UPDATE' status. Wait until the balancer is back in 'ACTIVE' status and then return the individual balancer details call. """ resp = self.connection.async_request( action='/loadbalancers/%s' % balancer.id, method='GET') return self._to_balancer(resp.object['loadBalancer']) def ex_update_balancer_health_monitor(self, balancer, health_monitor): """ Sets a Balancer's health monitor. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to update. :type balancer: :class:`LoadBalancer` :param health_monitor: Health Monitor for the balancer. :type health_monitor: :class:`RackspaceHealthMonitor` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_update_balancer_health_monitor_no_poll( balancer, health_monitor) if not accepted: msg = 'Update health monitor request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_update_balancer_health_monitor_no_poll(self, balancer, health_monitor): """ Sets a Balancer's health monitor. This method returns immediately. :param balancer: Balancer to update health monitor on. :type balancer: :class:`LoadBalancer` :param health_monitor: Health Monitor for the balancer. :type health_monitor: :class:`RackspaceHealthMonitor` :return: Returns whether the update request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/healthmonitor' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps(health_monitor._to_dict())) return resp.status == httplib.ACCEPTED def ex_disable_balancer_health_monitor(self, balancer): """ Disables a Balancer's health monitor. This method blocks until the disable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to disable health monitor on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_disable_balancer_health_monitor_no_poll(balancer): msg = 'Disable health monitor request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_disable_balancer_health_monitor_no_poll(self, balancer): """ Disables a Balancer's health monitor. This method returns immediately. :param balancer: Balancer to disable health monitor on. :type balancer: :class:`LoadBalancer` :return: Returns whether the disable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/healthmonitor' % (balancer.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_update_balancer_connection_throttle(self, balancer, connection_throttle): """ Updates a Balancer's connection throttle. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to update connection throttle on. :type balancer: :class:`LoadBalancer` :param connection_throttle: Connection Throttle for the balancer. :type connection_throttle: :class:`RackspaceConnectionThrottle` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_update_balancer_connection_throttle_no_poll( balancer, connection_throttle) if not accepted: msg = 'Update connection throttle request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_update_balancer_connection_throttle_no_poll(self, balancer, connection_throttle): """ Sets a Balancer's connection throttle. This method returns immediately. :param balancer: Balancer to update connection throttle on. :type balancer: :class:`LoadBalancer` :param connection_throttle: Connection Throttle for the balancer. :type connection_throttle: :class:`RackspaceConnectionThrottle` :return: Returns whether the update request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/connectionthrottle' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps(connection_throttle._to_dict())) return resp.status == httplib.ACCEPTED def ex_disable_balancer_connection_throttle(self, balancer): """ Disables a Balancer's connection throttle. This method blocks until the disable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to disable connection throttle on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_disable_balancer_connection_throttle_no_poll(balancer): msg = 'Disable connection throttle request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_disable_balancer_connection_throttle_no_poll(self, balancer): """ Disables a Balancer's connection throttle. This method returns immediately. :param balancer: Balancer to disable connection throttle on. :type balancer: :class:`LoadBalancer` :return: Returns whether the disable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/connectionthrottle' % (balancer.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_enable_balancer_connection_logging(self, balancer): """ Enables connection logging for a Balancer. This method blocks until the enable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to enable connection logging on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_enable_balancer_connection_logging_no_poll(balancer): msg = 'Enable connection logging request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_enable_balancer_connection_logging_no_poll(self, balancer): """ Enables connection logging for a Balancer. This method returns immediately. :param balancer: Balancer to enable connection logging on. :type balancer: :class:`LoadBalancer` :return: Returns whether the enable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/connectionlogging' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps({'connectionLogging': {'enabled': True}}) ) return resp.status == httplib.ACCEPTED def ex_disable_balancer_connection_logging(self, balancer): """ Disables connection logging for a Balancer. This method blocks until the enable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to disable connection logging on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_disable_balancer_connection_logging_no_poll(balancer): msg = 'Disable connection logging request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_disable_balancer_connection_logging_no_poll(self, balancer): """ Disables connection logging for a Balancer. This method returns immediately. :param balancer: Balancer to disable connection logging on. :type balancer: :class:`LoadBalancer` :return: Returns whether the disable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/connectionlogging' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps({'connectionLogging': {'enabled': False}}) ) return resp.status == httplib.ACCEPTED def ex_enable_balancer_session_persistence(self, balancer): """ Enables session persistence for a Balancer by setting the persistence type to 'HTTP_COOKIE'. This method blocks until the enable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to enable session persistence on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_enable_balancer_session_persistence_no_poll(balancer): msg = 'Enable session persistence request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_enable_balancer_session_persistence_no_poll(self, balancer): """ Enables session persistence for a Balancer by setting the persistence type to 'HTTP_COOKIE'. This method returns immediately. :param balancer: Balancer to enable session persistence on. :type balancer: :class:`LoadBalancer` :return: Returns whether the enable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/sessionpersistence' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps( {'sessionPersistence': {'persistenceType': 'HTTP_COOKIE'}}) ) return resp.status == httplib.ACCEPTED def ex_disable_balancer_session_persistence(self, balancer): """ Disables session persistence for a Balancer. This method blocks until the disable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to disable session persistence on. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_disable_balancer_session_persistence_no_poll(balancer): msg = 'Disable session persistence request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_disable_balancer_session_persistence_no_poll(self, balancer): """ Disables session persistence for a Balancer. This method returns immediately. :param balancer: Balancer to disable session persistence for. :type balancer: :class:`LoadBalancer` :return: Returns whether the disable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/sessionpersistence' % (balancer.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_update_balancer_error_page(self, balancer, page_content): """ Updates a Balancer's custom error page. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to update the custom error page for. :type balancer: :class:`LoadBalancer` :param page_content: HTML content for the custom error page. :type page_content: ``str`` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_update_balancer_error_page_no_poll(balancer, page_content) if not accepted: msg = 'Update error page request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_update_balancer_error_page_no_poll(self, balancer, page_content): """ Updates a Balancer's custom error page. This method returns immediately. :param balancer: Balancer to update the custom error page for. :type balancer: :class:`LoadBalancer` :param page_content: HTML content for the custom error page. :type page_content: ``str`` :return: Returns whether the update request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/errorpage' % (balancer.id) resp = self.connection.request( uri, method='PUT', data=json.dumps({'errorpage': {'content': page_content}}) ) return resp.status == httplib.ACCEPTED def ex_disable_balancer_custom_error_page(self, balancer): """ Disables a Balancer's custom error page, returning its error page to the Rackspace-provided default. This method blocks until the disable request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to disable the custom error page for. :type balancer: :class:`LoadBalancer` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ if not self.ex_disable_balancer_custom_error_page_no_poll(balancer): msg = 'Disable custom error page request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_disable_balancer_custom_error_page_no_poll(self, balancer): """ Disables a Balancer's custom error page, returning its error page to the Rackspace-provided default. This method returns immediately. :param balancer: Balancer to disable the custom error page for. :type balancer: :class:`LoadBalancer` :return: Returns whether the disable request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/errorpage' % (balancer.id) resp = self.connection.request(uri, method='DELETE') # Load Balancer API currently returns 200 OK on custom error page # delete. return resp.status == httplib.OK or resp.status == httplib.ACCEPTED def ex_create_balancer_access_rule(self, balancer, rule): """ Adds an access rule to a Balancer's access list. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to create the access rule for. :type balancer: :class:`LoadBalancer` :param rule: Access Rule to add to the balancer. :type rule: :class:`RackspaceAccessRule` :return: The created access rule. :rtype: :class:`RackspaceAccessRule` """ accepted = self.ex_create_balancer_access_rule_no_poll(balancer, rule) if not accepted: msg = 'Create access rule not accepted' raise LibcloudError(msg, driver=self) balancer = self._get_updated_balancer(balancer) access_list = balancer.extra['accessList'] created_rule = self._find_matching_rule(rule, access_list) if not created_rule: raise LibcloudError('Could not find created rule') return created_rule def ex_create_balancer_access_rule_no_poll(self, balancer, rule): """ Adds an access rule to a Balancer's access list. This method returns immediately. :param balancer: Balancer to create the access rule for. :type balancer: :class:`LoadBalancer` :param rule: Access Rule to add to the balancer. :type rule: :class:`RackspaceAccessRule` :return: Returns whether the create request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/accesslist' % (balancer.id) resp = self.connection.request( uri, method='POST', data=json.dumps({'networkItem': rule._to_dict()}) ) return resp.status == httplib.ACCEPTED def ex_create_balancer_access_rules(self, balancer, rules): """ Adds a list of access rules to a Balancer's access list. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to create the access rule for. :type balancer: :class:`LoadBalancer` :param rules: List of :class:`RackspaceAccessRule` to add to the balancer. :type rules: ``list`` of :class:`RackspaceAccessRule` :return: The created access rules. :rtype: :class:`RackspaceAccessRule` """ accepted = self.ex_create_balancer_access_rules_no_poll(balancer, rules) if not accepted: msg = 'Create access rules not accepted' raise LibcloudError(msg, driver=self) balancer = self._get_updated_balancer(balancer) access_list = balancer.extra['accessList'] created_rules = [] for r in rules: matched_rule = self._find_matching_rule(r, access_list) if matched_rule: created_rules.append(matched_rule) if len(created_rules) != len(rules): raise LibcloudError('Could not find all created rules') return created_rules def _find_matching_rule(self, rule_to_find, access_list): """ LB API does not return the ID for the newly created rules, so we have to search the list to find the rule with a matching rule type and address to return an object with the right identifier.it. The API enforces rule type and address uniqueness. """ for r in access_list: if rule_to_find.rule_type == r.rule_type and\ rule_to_find.address == r.address: return r return None def ex_create_balancer_access_rules_no_poll(self, balancer, rules): """ Adds a list of access rules to a Balancer's access list. This method returns immediately. :param balancer: Balancer to create the access rule for. :type balancer: :class:`LoadBalancer` :param rules: List of :class:`RackspaceAccessRule` to add to the balancer. :type rules: ``list`` of :class:`RackspaceAccessRule` :return: Returns whether the create request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/accesslist' % (balancer.id) resp = self.connection.request( uri, method='POST', data=json.dumps({'accessList': [rule._to_dict() for rule in rules]}) ) return resp.status == httplib.ACCEPTED def ex_destroy_balancer_access_rule(self, balancer, rule): """ Removes an access rule from a Balancer's access list. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to remove the access rule from. :type balancer: :class:`LoadBalancer` :param rule: Access Rule to remove from the balancer. :type rule: :class:`RackspaceAccessRule` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_destroy_balancer_access_rule_no_poll(balancer, rule) if not accepted: msg = 'Delete access rule not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_destroy_balancer_access_rule_no_poll(self, balancer, rule): """ Removes an access rule from a Balancer's access list. This method returns immediately. :param balancer: Balancer to remove the access rule from. :type balancer: :class:`LoadBalancer` :param rule: Access Rule to remove from the balancer. :type rule: :class:`RackspaceAccessRule` :return: Returns whether the destroy request was accepted. :rtype: ``bool`` """ uri = '/loadbalancers/%s/accesslist/%s' % (balancer.id, rule.id) resp = self.connection.request(uri, method='DELETE') return resp.status == httplib.ACCEPTED def ex_destroy_balancer_access_rules(self, balancer, rules): """ Removes a list of access rules from a Balancer's access list. This method blocks until the update request has been processed and the balancer is in a RUNNING state again. :param balancer: Balancer to remove the access rules from. :type balancer: :class:`LoadBalancer` :param rules: List of :class:`RackspaceAccessRule` objects to remove from the balancer. :type rules: ``list`` of :class:`RackspaceAccessRule` :return: Updated Balancer. :rtype: :class:`LoadBalancer` """ accepted = self.ex_destroy_balancer_access_rules_no_poll( balancer, rules) if not accepted: msg = 'Destroy access rules request not accepted' raise LibcloudError(msg, driver=self) return self._get_updated_balancer(balancer) def ex_destroy_balancer_access_rules_no_poll(self, balancer, rules): """ Removes a list of access rules from a Balancer's access list. This method returns immediately. :param balancer: Balancer to remove the access rules from. :type balancer: :class:`LoadBalancer` :param rules: List of :class:`RackspaceAccessRule` objects to remove from the balancer. :type rules: ``list`` of :class:`RackspaceAccessRule` :return: Returns whether the destroy request was accepted. :rtype: ``bool`` """ ids = [('id', rule.id) for rule in rules] uri = '/loadbalancers/%s/accesslist' % balancer.id resp = self.connection.request(uri, method='DELETE', params=ids) return resp.status == httplib.ACCEPTED def ex_list_current_usage(self, balancer): """ Return current load balancer usage report. :param balancer: Balancer to remove the access rules from. :type balancer: :class:`LoadBalancer` :return: Raw load balancer usage object. :rtype: ``dict`` """ uri = '/loadbalancers/%s/usage/current' % (balancer.id) resp = self.connection.request(uri, method='GET') return resp.object def _to_protocols(self, object): protocols = [] for item in object["protocols"]: protocols.append(item['name'].lower()) return protocols def _to_protocols_with_default_ports(self, object): protocols = [] for item in object["protocols"]: name = item['name'].lower() port = int(item['port']) protocols.append((name, port)) return protocols def _to_balancers(self, object): return [self._to_balancer(el) for el in object["loadBalancers"]] def _to_balancer(self, el): ip = None port = None sourceAddresses = {} if 'port' in el: port = el["port"] if 'sourceAddresses' in el: sourceAddresses = el['sourceAddresses'] extra = { "ipv6PublicSource": sourceAddresses.get("ipv6Public"), "ipv4PublicSource": sourceAddresses.get("ipv4Public"), "ipv4PrivateSource": sourceAddresses.get("ipv4Servicenet"), "service_name": self.connection.get_service_name(), "uri": "https://%s%s/loadbalancers/%s" % ( self.connection.host, self.connection.request_path, el["id"]), } if 'virtualIps' in el: ip = el['virtualIps'][0]['address'] extra['virtualIps'] = el['virtualIps'] if 'protocol' in el: extra['protocol'] = el['protocol'] if 'algorithm' in el and \ el["algorithm"] in self._VALUE_TO_ALGORITHM_MAP: extra["algorithm"] = self._value_to_algorithm(el["algorithm"]) if 'healthMonitor' in el: health_monitor = self._to_health_monitor(el) if health_monitor: extra["healthMonitor"] = health_monitor if 'connectionThrottle' in el: extra["connectionThrottle"] = self._to_connection_throttle(el) if 'sessionPersistence' in el: persistence = el["sessionPersistence"] extra["sessionPersistenceType"] =\ persistence.get("persistenceType") if 'connectionLogging' in el: logging = el["connectionLogging"] extra["connectionLoggingEnabled"] = logging.get("enabled") if 'nodes' in el: extra['members'] = self._to_members(el) if 'created' in el: extra['created'] = self._iso_to_datetime(el['created']['time']) if 'updated' in el: extra['updated'] = self._iso_to_datetime(el['updated']['time']) if 'accessList' in el: extra['accessList'] = [self._to_access_rule(rule) for rule in el['accessList']] return LoadBalancer(id=el["id"], name=el["name"], state=self.LB_STATE_MAP.get( el["status"], State.UNKNOWN), ip=ip, port=port, driver=self.connection.driver, extra=extra) def _to_members(self, object, balancer=None): return [self._to_member(el, balancer) for el in object["nodes"]] def _to_member(self, el, balancer=None): extra = {} if 'weight' in el: extra['weight'] = el["weight"] if 'condition' in el and\ el['condition'] in self.LB_MEMBER_CONDITION_MAP: extra['condition'] =\ self.LB_MEMBER_CONDITION_MAP.get(el["condition"]) if 'status' in el: extra['status'] = el["status"] lbmember = Member(id=el["id"], ip=el["address"], port=el["port"], balancer=balancer, extra=extra) return lbmember def _protocol_to_value(self, protocol): non_standard_protocols = {'imapv2': 'IMAPv2', 'imapv3': 'IMAPv3', 'imapv4': 'IMAPv4'} protocol_name = protocol.lower() if protocol_name in non_standard_protocols: protocol_value = non_standard_protocols[protocol_name] else: protocol_value = protocol.upper() return protocol_value def _kwargs_to_mutable_attrs(self, **attrs): update_attrs = {} if "name" in attrs: update_attrs['name'] = attrs['name'] if "algorithm" in attrs: algorithm_value = self._algorithm_to_value(attrs['algorithm']) update_attrs['algorithm'] = algorithm_value if "protocol" in attrs: update_attrs['protocol'] =\ self._protocol_to_value(attrs['protocol']) if "port" in attrs: update_attrs['port'] = int(attrs['port']) if "vip" in attrs: if attrs['vip'] == 'PUBLIC' or attrs['vip'] == 'SERVICENET': update_attrs['virtualIps'] = [{'type': attrs['vip']}] else: update_attrs['virtualIps'] = [{'id': attrs['vip']}] return update_attrs def _kwargs_to_mutable_member_attrs(self, **attrs): update_attrs = {} if 'condition' in attrs: update_attrs['condition'] =\ self.CONDITION_LB_MEMBER_MAP.get(attrs['condition']) if 'weight' in attrs: update_attrs['weight'] = attrs['weight'] return update_attrs def _to_health_monitor(self, el): health_monitor_data = el["healthMonitor"] type = health_monitor_data.get("type") delay = health_monitor_data.get("delay") timeout = health_monitor_data.get("timeout") attempts_before_deactivation =\ health_monitor_data.get("attemptsBeforeDeactivation") if type == "CONNECT": return RackspaceHealthMonitor( type=type, delay=delay, timeout=timeout, attempts_before_deactivation=attempts_before_deactivation) if type == "HTTP" or type == "HTTPS": return RackspaceHTTPHealthMonitor( type=type, delay=delay, timeout=timeout, attempts_before_deactivation=attempts_before_deactivation, path=health_monitor_data.get("path"), status_regex=health_monitor_data.get("statusRegex"), body_regex=health_monitor_data.get("bodyRegex", '')) return None def _to_connection_throttle(self, el): connection_throttle_data = el["connectionThrottle"] min_connections = connection_throttle_data.get("minConnections") max_connections = connection_throttle_data.get("maxConnections") max_connection_rate = connection_throttle_data.get("maxConnectionRate") rate_interval = connection_throttle_data.get("rateInterval") return RackspaceConnectionThrottle( min_connections=min_connections, max_connections=max_connections, max_connection_rate=max_connection_rate, rate_interval_seconds=rate_interval) def _to_access_rule(self, el): return RackspaceAccessRule( id=el.get("id"), rule_type=self._to_access_rule_type(el.get("type")), address=el.get("address")) def _to_access_rule_type(self, type): if type == "ALLOW": return RackspaceAccessRuleType.ALLOW elif type == "DENY": return RackspaceAccessRuleType.DENY def _iso_to_datetime(self, isodate): date_formats = ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S%z') date = None for date_format in date_formats: try: date = datetime.strptime(isodate, date_format) except ValueError: pass if date: break return date