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/compute/drivers
Viewing File: /opt/imh-python/lib/python3.9/site-packages/libcloud/compute/drivers/kamatera.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. """ Kamatera node driver """ import json import datetime import time from libcloud.utils.py3 import basestring from libcloud.compute.base import NodeDriver, NodeLocation, NodeSize from libcloud.compute.base import NodeImage, Node, NodeState from libcloud.compute.types import Provider from libcloud.common.base import ConnectionUserAndKey, JsonResponse class KamateraResponse(JsonResponse): """ Response class for KamateraDriver """ def parse_error(self): data = self.parse_body() if 'message' in data: return data['message'] else: return json.dumps(data) class KamateraConnection(ConnectionUserAndKey): """ Connection class for KamateraDriver """ host = 'cloudcli.cloudwm.com' responseCls = KamateraResponse def add_default_headers(self, headers): """Adds headers that are needed for all requests""" headers['AuthClientId'] = self.user_id headers['AuthSecret'] = self.key headers['Accept'] = 'application/json' headers['Content-Type'] = 'application/json' return headers class KamateraNodeDriver(NodeDriver): """ Kamatera node driver :keyword key: API Client ID, required for authentication :type key: ``str`` :keyword secret: API Secret, required for authentcaiont :type secret: ``str`` """ type = Provider.KAMATERA name = 'Kamatera' website = 'https://www.kamatera.com/' connectionCls = KamateraConnection features = {'create_node': ['password', 'generates_password', 'ssh_key']} EX_BILLINGCYCLE_HOURLY = 'hourly' EX_BILLINGCYCLE_MONTHLY = 'monthly' def list_locations(self): """ List available locations for deployment :rtype: ``list`` of :class:`NodeLocation` """ response = self.connection.request('service/server?datacenter=1') return [self.ex_get_location( datacenter['id'], datacenter['subCategory'], datacenter['name'] ) for datacenter in response.object] def list_sizes(self, location): """ List predefined sizes for the given location. :param location: Location of the deployment. :type location: :class:`.NodeLocation` @inherits: :class:`NodeDriver.list_sizes` """ response = self.connection.request( 'service/server?sizes=1&datacenter=%s' % location.id) return [self.ex_get_size( size['ramMB'], size['diskSizeGB'], size['cpuType'], size['cpuCores'], extraDiskSizesGB=[], monthlyTrafficPackage=size['monthlyTrafficPackage'], id=size['id'] ) for size in response.object] def list_images(self, location): """ List available disk images. :param location: Location of the deployement. Available disk images depend on location. :type location: :class:`.NodeLocation` :rtype: ``list`` of :class:`NodeImage` """ response = self.connection.request( 'service/server?images=1&datacenter=%s' % location.id) images = [] for image in response.object: extra = self._copy_dict(('datacenter', 'os', 'code', 'osDiskSizeGB', 'ramMBMin'), image) images.append(self.ex_get_image(image['name'], image['id'], extra)) return images def create_node(self, name, size, image, location, auth=None, ex_networks=None, ex_dailybackup=False, ex_managed=False, ex_billingcycle=EX_BILLINGCYCLE_HOURLY, ex_poweronaftercreate=True, ex_wait=True): """ Creates a Kamatera node. If auth is not given then password will be generated. :param name: String with a name for this new node (required) :type name: ``str`` :param size: The size of resources allocated to this node (required) :type size: :class:`.NodeSize` :param image: OS Image to boot on node. (required) :type image: :class:`.NodeImage` :param location: Which data center to create a node in. (required) :type location: :class:`.NodeLocation` :param auth: Authentication information for the node (optional) :type auth: :class:`.NodeAuthSSHKey` or :class:`.NodeAuthPassword` :param ex_networks: Network configurations (optional) :type ex_networks: ``list`` of ``dict`` :param ex_dailybackup: Whether to create daily backups (optional) :type ex_dailybackup: ``bool`` :param ex_managed: Whether to provide managed support (optional) :type ex_managed: ``bool`` :param ex_billingcycle: billing cycle (hourly / monthly) (optional) :type ex_billingcycle: ``str`` :param ex_poweronaftercreate: power on after creation (optional) :type ex_poweronaftercreate: ``bool`` :param ex_wait: wait for server to be running (optional) :type ex_wait: ``bool`` :return: The newly created node. :rtype: :class:`.Node` """ password = None pubkey = None generate_password = False if isinstance(auth, basestring): password = auth else: auth_obj = self._get_and_check_auth(auth) if getattr(auth_obj, 'generated', False): password = '__generate__' generate_password = True elif hasattr(auth_obj, 'password'): password = auth_obj.password generate_password = False elif hasattr(auth_obj, 'pubkey'): pubkey = auth_obj.pubkey if not ex_networks: ex_networks = [{'name': 'wan', 'ip': 'auto'}] request_data = { "name": name, "password": password or '', "passwordValidate": password or '', 'ssh-key': pubkey or '', "datacenter": location.id, "image": image.id, "cpu": '%s%s' % (size.extra['cpuCores'], size.extra['cpuType']), "ram": size.ram, "disk": ' '.join([ 'size=%d' % disksize for disksize in [size.disk] + size.extra['extraDiskSizesGB']]), "dailybackup": 'yes' if ex_dailybackup else 'no', "managed": 'yes' if ex_managed else 'no', "network": ' '.join([','.join([ '%s=%s' % (k, v) for k, v in network.items()]) for network in ex_networks]), "quantity": 1, "billingcycle": ex_billingcycle, "monthlypackage": size.extra['monthlyTrafficPackage'] or '', "poweronaftercreate": 'yes' if ex_poweronaftercreate else 'no' } response = self.connection.request('service/server', method='POST', data=json.dumps(request_data)) if generate_password: command_ids = response.object['commandIds'] generated_password = response.object['password'] else: command_ids = response.object generated_password = None if len(command_ids) != 1: raise RuntimeError('invalid response') node = self.ex_get_node( name=name, size=size, image=image, location=location, dailybackup=ex_dailybackup, managed=ex_managed, billingcycle=ex_billingcycle, generated_password=generated_password, create_command_id=command_ids[0], poweronaftercreate=ex_poweronaftercreate) if ex_wait: if ( 'create_command_id' not in node.extra or node.state != NodeState.UNKNOWN ): raise ValueError('invalid node for updating create status') command = self.ex_wait_command(node.extra['create_command_id']) node.extra['create_log'] = command.get('log') if node.extra.get('poweronaftercreate'): node.state = NodeState.RUNNING else: node.state = NodeState.STOPPED if command.get('completed'): node.created_at = datetime.datetime.strptime( command['completed'], '%Y-%m-%d %H:%M:%S') name_lines = [line for line in node.extra['create_log'].split("\n") if line.startswith('Name: ')] if len(name_lines) != 1: raise RuntimeError('Invalid node create log response') node.name = name_lines[0].replace('Name: ', '') response = self.connection.request( '/service/server/info', method='POST', data=json.dumps({'name': node.name})) self._update_node_from_server_info(node, response.object[0]) return node def list_nodes(self, ex_name_regex=None, ex_full_details=False, ex_id=None): """ List nodes :param ex_name_regex: Regular expression to match node names if set returns full node details (optional) :type ex_name_regex: ``str`` :param ex_full_details: Whether to return full node details takes longer to complete (optional) :type ex_full_details: ``bool`` :return: List of node objects :rtype: ``list`` of :class:`Node` """ if ex_name_regex or ex_full_details or ex_id: request_data = {} if ex_id: request_data['id'] = ex_id else: if not ex_name_regex: ex_name_regex = '.*' request_data['name'] = ex_name_regex response = self.connection.request( '/service/server/info', method='POST', data=json.dumps(request_data)) return [self._update_node_from_server_info( self.ex_get_node(), server) for server in response.object] else: response = self.connection.request('/service/servers') return [ self.ex_get_node( id=server['id'], name=server['name'], state=( NodeState.RUNNING if server['power'] == 'on' else NodeState.STOPPED), location=self.ex_get_location(server['datacenter']) ) for server in response.object] def reboot_node(self, node, ex_wait=True): """ Reboot the given node :param node: the node to reboot :type node: :class:`Node` :param ex_wait: wait for reboot to complete (optional) :type ex_wait: ``bool`` :rtype: ``bool`` """ return self.ex_node_operation(node, 'reboot', ex_wait) def destroy_node(self, node, ex_wait=True): """ Destroy the given node :param node: the node to destroy :type node: :class:`Node` :param ex_wait: wait for destroy to complete (optional) :type ex_wait: ``bool`` :rtype: ``bool`` """ return self.ex_node_operation(node, 'terminate', ex_wait) def stop_node(self, node, ex_wait=True): """ Stop the given node :param node: the node to stop :type node: :class:`Node` :param ex_wait: wait for stop to complete (optional) :type ex_wait: ``bool`` :rtype: ``bool`` """ return self.ex_node_operation(node, 'poweroff', ex_wait) def start_node(self, node, ex_wait=True): """ Start the given node :param node: the node to start :type node: :class:`Node` :param ex_wait: wait for start to complete (optional) :type ex_wait: ``bool`` :rtype: ``bool`` """ return self.ex_node_operation(node, 'poweron', ex_wait) def ex_node_operation(self, node, operation, wait=True): """ Run custom operations on the node :param node: the node to run operation on :type node: :class:`Node` :param operation: the operation to run :type operation: ``str`` :param ex_wait: wait for destroy to complete (optional) :type ex_wait: ``bool`` :rtype: ``bool`` """ if node.id: request_data = {'id': node.id} elif node.name: request_data = {'name': node.name} else: raise ValueError('Invalid node for %s node operation: ' 'missing id / name' % operation) if operation == 'terminate': request_data['force'] = True command_id = self.connection.request( '/service/server/%s' % operation, method='POST', data=json.dumps(request_data)).object[0] if wait: self.ex_wait_command(command_id) else: node.extra['%s_command_id' % operation] = command_id return True def ex_get_location(self, id, name=None, country=None): """ Get a NodeLocation object to use for other methods :param id: Location ID - uppercase letters code (required) :type id: ``str`` :param name: Location Name (optional) :type name: ``str`` :param name: Location country (optional) :type name: ``str`` :rtype: :class:`.NodeLocation` """ return NodeLocation(id=id, name=name, country=country, driver=self) def ex_get_size(self, ramMB, diskSizeGB, cpuType, cpuCores, extraDiskSizesGB=None, monthlyTrafficPackage=None, id=None, name=None): """ Get a NodeSize object to use for other methods :param ramMB: Amount of RAM to allocate in MB (required) :type ramMB: ``int`` :param diskSizeGB: disk size GB for primary hard disk (required) :type diskSizeGB: ``int`` :param cpuType: CPU type ID (single uppercase letter), see ex_list_capabilities (required) :type cpuType: ``str`` :param cpuCores: Number of CPU cores to allocate (required) :type cpuCores: ``int`` :param extraDiskSizesGB: additional disk sizes in GB (optional) :type extraDiskSizesGB: ``list`` of :int: :param monthlyTrafficPackage: ID of monthly traffic package see ex_list_capabilities (optional) :type monthlyTrafficPackage: ``str`` :param id: Size ID (optional) :type id: ``str`` :param name: Size Name (optional) :type name: ``str`` :rtype: :class:`.NodeLocation` """ if not id: id = str(cpuCores) + cpuType id += '-' + str(ramMB) + 'MB-' + str(diskSizeGB) + 'GB' if monthlyTrafficPackage: id += '-' + monthlyTrafficPackage if not name: name = id return NodeSize( id=id, name=name, ram=ramMB, disk=diskSizeGB, bandwidth=0, price=0, driver=self.connection.driver, extra={ 'cpuType': cpuType, 'cpuCores': cpuCores, 'monthlyTrafficPackage': monthlyTrafficPackage, 'extraDiskSizesGB': extraDiskSizesGB or [] } ) def ex_get_image(self, name=None, id=None, extra=None): if not id and not name: raise ValueError('either id or name are required for NodeImage') return NodeImage(id=id or name, name=name or '', driver=self, extra=extra or {}) def ex_list_capabilities(self, location): """ List capabilities for given location. :param location: Location of the deployment. :type location: :class:`.NodeLocation` :return: ``dict`` """ return self.connection.request( 'service/server?capabilities=1&' 'datacenter=%s' % location.id).object def ex_wait_command(self, command_id, timeout_seconds=600, poll_interval_seconds=2): """ Wait for command to complete and return the command status details :param command_id: Command ID to wait for. (required) :type command_id: ``int`` :param timeout_seconds: Max seconds to wait for command. (optional) :type timeout_seconds: ``int`` :param poll_interval_seconds: Poll interval in seconds (optional) :type poll_interval_seconds: ``int`` :return: ``dict`` """ start_time = datetime.datetime.now() time.sleep(poll_interval_seconds) while True: max_time = start_time + datetime.timedelta( seconds=timeout_seconds) if max_time < datetime.datetime.now(): raise TimeoutError( 'Timeout waiting for command ' '(timeout_seconds=%s, command_id=%s)' % ( str(timeout_seconds), str(command_id))) time.sleep(poll_interval_seconds) command = self.ex_get_command_status(command_id) status = command.get('status') if status == 'complete': return command elif status == 'error': raise RuntimeError('Command failed: ' + command.get('log')) def ex_get_command_status(self, command_id): """ Get Kamatera command status details :param command_id: Command ID to get details for. (required) :type command_id: ``int`` :return: ``dict`` """ response = self.connection.request( '/service/queue?id=' + str(command_id)) if len(response.object) != 1: raise RuntimeError('invalid response') return response.object[0] def ex_get_node(self, id=None, name=None, state=NodeState.UNKNOWN, public_ips=None, private_ips=None, size=None, image=None, created_at=None, location=None, dailybackup=None, managed=None, billingcycle=None, generated_password=None, create_command_id=None, poweronaftercreate=None): """ Get a Kamatera node object. :param id: Node ID (optional) :type id: ``str`` :param name: Node name (optional) :type name: ``str`` :param state: Node state (optional) :type state: :class:`libcloud.compute.types.NodeState` :param public_ips: Node public IPS. (optional) :type public_ips: ``list`` of :str: :param private_ips: Node private IPS. (optional) :type private_ips: ``list`` of :str: :param size: node size. (optional) :type size: :class:`.NodeSize` :param image: Node OS Image. (optional) :type image: :class:`.NodeImage` :param created_at: Node creation time. (optional) :type created_at: ``datetime.datetime`` :param location: Node datacenter. (optional) :type location: :class:`.NodeLocation` :param dailybackup: create daily backups for the node (optional) :type dailybackup: ``bool`` :param managed: provide managed support for the node (optional) :type managed: ``bool`` :param billingcycle: billing cycle (hourly / monthly) (optional) :type billingcycle: ``str`` :param generated_password: server generated password (optional) :type generated_password: ``str`` :param create_command_id: creation task command ID (optional) :type create_command_id: ``int`` :param poweronaftercreate: power on the node after create (optional) :type poweronaftercreate: ``bool`` :return: The node. :rtype: :class:`.Node` """ extra = {} if location: extra['location'] = location if dailybackup is not None: extra['dailybackup'] = dailybackup if managed is not None: extra['managed'] = managed if billingcycle is not None: extra['billingcycle'] = billingcycle if generated_password is not None: extra['generated_password'] = generated_password if create_command_id is not None: extra['create_command_id'] = create_command_id if poweronaftercreate is not None: extra['poweronaftercreate'] = poweronaftercreate return Node(id=id, name=name, state=state, public_ips=public_ips, private_ips=private_ips, driver=self, size=size, image=image, created_at=created_at, extra=extra) def _copy_dict(self, keys, d): extra = {} for key in keys: extra[key] = d[key] return extra def _update_node_from_server_info(self, node, server): node.id = server['id'] node.name = server['name'] if server['power'] == 'on': node.state = NodeState.RUNNING else: node.state = NodeState.STOPPED for network in server.get('networks', []): if network.get('network').startswith('wan-'): node.public_ips += network.get('ips', []) else: node.private_ips += network.get('ips', []) billing = server.get('billing', node.extra.get('billingcycle')).lower() if billing == self.EX_BILLINGCYCLE_HOURLY: node.extra['billingcycle'] = self.EX_BILLINGCYCLE_HOURLY node.extra['priceOn'] = server.get('priceHourlyOn') node.extra['priceOff'] = server.get('priceHourlyOff') else: node.extra['billingcycle'] = self.EX_BILLINGCYCLE_MONTHLY node.extra['priceOn'] = server.get('priceMonthlyOn') node.extra['priceOff'] = server.get('priceMonthlyOn') node.extra['location'] = self.ex_get_location(server['datacenter']) node.extra['dailybackup'] = server.get('backup') == "1" node.extra['managed'] = server.get('managed') == "1" return node