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/nephoscale.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. """ NephoScale Cloud driver (http://www.nephoscale.com) API documentation: http://docs.nephoscale.com Created by Markos Gogoulos (https://mist.io) """ import base64 import time import os import binascii from libcloud.utils.py3 import httplib from libcloud.utils.py3 import b from libcloud.utils.py3 import urlencode from libcloud.compute.providers import Provider from libcloud.common.base import JsonResponse, ConnectionUserAndKey from libcloud.compute.types import (NodeState, InvalidCredsError, LibcloudError) from libcloud.compute.base import (Node, NodeDriver, NodeImage, NodeSize, NodeLocation) from libcloud.utils.networking import is_private_subnet API_HOST = 'api.nephoscale.com' NODE_STATE_MAP = { 'on': NodeState.RUNNING, 'off': NodeState.UNKNOWN, 'unknown': NodeState.UNKNOWN, } VALID_RESPONSE_CODES = [httplib.OK, httplib.ACCEPTED, httplib.CREATED, httplib.NO_CONTENT] # used in create_node and specifies how many times to get the list of nodes and # check if the newly created node is there. This is because when a request is # sent to create a node, NephoScale replies with the job id, and not the node # itself thus we don't have the ip addresses, that are required in deploy_node CONNECT_ATTEMPTS = 10 class NodeKey(object): def __init__(self, id, name, public_key=None, key_group=None, password=None): self.id = id self.name = name self.key_group = key_group self.password = password self.public_key = public_key def __repr__(self): return (('<NodeKey: id=%s, name=%s>') % (self.id, self.name)) class NephoscaleResponse(JsonResponse): """ Nephoscale API Response """ def parse_error(self): if self.status == httplib.UNAUTHORIZED: raise InvalidCredsError('Authorization Failed') if self.status == httplib.NOT_FOUND: raise Exception("The resource you are looking for is not found.") return self.body def success(self): return self.status in VALID_RESPONSE_CODES class NephoscaleConnection(ConnectionUserAndKey): """ Nephoscale connection class. Authenticates to the API through Basic Authentication with username/password """ host = API_HOST responseCls = NephoscaleResponse allow_insecure = False def add_default_headers(self, headers): """ Add parameters that are necessary for every request """ user_b64 = base64.b64encode(b('%s:%s' % (self.user_id, self.key))) headers['Authorization'] = 'Basic %s' % (user_b64.decode('utf-8')) return headers class NephoscaleNodeDriver(NodeDriver): """ Nephoscale node driver class. >>> from libcloud.compute.providers import get_driver >>> driver = get_driver('nephoscale') >>> conn = driver('nepho_user','nepho_password') >>> conn.list_nodes() """ type = Provider.NEPHOSCALE api_name = 'nephoscale' name = 'NephoScale' website = 'http://www.nephoscale.com' connectionCls = NephoscaleConnection features = {'create_node': ['ssh_key']} def list_locations(self): """ List available zones for deployment :rtype: ``list`` of :class:`NodeLocation` """ result = self.connection.request('/datacenter/zone/').object locations = [] for value in result.get('data', []): location = NodeLocation(id=value.get('id'), name=value.get('name'), country='US', driver=self) locations.append(location) return locations def list_images(self): """ List available images for deployment :rtype: ``list`` of :class:`NodeImage` """ result = self.connection.request('/image/server/').object images = [] for value in result.get('data', []): extra = {'architecture': value.get('architecture'), 'disks': value.get('disks'), 'billable_type': value.get('billable_type'), 'pcpus': value.get('pcpus'), 'cores': value.get('cores'), 'uri': value.get('uri'), 'storage': value.get('storage'), } image = NodeImage(id=value.get('id'), name=value.get('friendly_name'), driver=self, extra=extra) images.append(image) return images def list_sizes(self): """ List available sizes containing prices :rtype: ``list`` of :class:`NodeSize` """ result = self.connection.request('/server/type/cloud/').object sizes = [] for value in result.get('data', []): value_id = value.get('id') size = NodeSize(id=value_id, name=value.get('friendly_name'), ram=value.get('ram'), disk=value.get('storage'), bandwidth=None, price=self._get_size_price(size_id=str(value_id)), driver=self) sizes.append(size) return sorted(sizes, key=lambda k: k.price) def list_nodes(self): """ List available nodes :rtype: ``list`` of :class:`Node` """ result = self.connection.request('/server/cloud/').object nodes = [self._to_node(value) for value in result.get('data', [])] return nodes def rename_node(self, node, name, hostname=None): """rename a cloud server, optionally specify hostname too""" data = {'name': name} if hostname: data['hostname'] = hostname params = urlencode(data) result = self.connection.request('/server/cloud/%s/' % node.id, data=params, method='PUT').object return result.get('response') in VALID_RESPONSE_CODES def reboot_node(self, node): """reboot a running node""" result = self.connection.request('/server/cloud/%s/initiator/restart/' % node.id, method='POST').object return result.get('response') in VALID_RESPONSE_CODES def start_node(self, node): """start a stopped node""" result = self.connection.request('/server/cloud/%s/initiator/start/' % node.id, method='POST').object return result.get('response') in VALID_RESPONSE_CODES def stop_node(self, node): """stop a running node""" result = self.connection.request('/server/cloud/%s/initiator/stop/' % node.id, method='POST').object return result.get('response') in VALID_RESPONSE_CODES def destroy_node(self, node): """destroy a node""" result = self.connection.request('/server/cloud/%s/' % node.id, method='DELETE').object return result.get('response') in VALID_RESPONSE_CODES def ex_start_node(self, node): # NOTE: This method is here for backward compatibility reasons after # this method was promoted to be part of the standard compute API in # Libcloud v2.7.0 return self.start_node(node=node) def ex_stop_node(self, node): # NOTE: This method is here for backward compatibility reasons after # this method was promoted to be part of the standard compute API in # Libcloud v2.7.0 return self.stop_node(node=node) def ex_list_keypairs(self, ssh=False, password=False, key_group=None): """ List available console and server keys There are two types of keys for NephoScale, ssh and password keys. If run without arguments, lists all keys. Otherwise list only ssh keys, or only password keys. Password keys with key_group 4 are console keys. When a server is created, it has two keys, one password or ssh key, and one password console key. :keyword ssh: if specified, show ssh keys only (optional) :type ssh: ``bool`` :keyword password: if specified, show password keys only (optional) :type password: ``bool`` :keyword key_group: if specified, show keys with this key_group only eg key_group=4 for console password keys (optional) :type key_group: ``int`` :rtype: ``list`` of :class:`NodeKey` """ if (ssh and password): raise LibcloudError('You can only supply ssh or password. To \ get all keys call with no arguments') if ssh: result = self.connection.request('/key/sshrsa/').object elif password: result = self.connection.request('/key/password/').object else: result = self.connection.request('/key/').object keys = [self._to_key(value) for value in result.get('data', [])] if key_group: keys = [key for key in keys if key.key_group == key_group] return keys def ex_create_keypair(self, name, public_key=None, password=None, key_group=None): """Creates a key, ssh or password, for server or console The group for the key (key_group) is 1 for Server and 4 for Console Returns the id of the created key """ if public_key: if not key_group: key_group = 1 data = { 'name': name, 'public_key': public_key, 'key_group': key_group } params = urlencode(data) result = self.connection.request('/key/sshrsa/', data=params, method='POST').object else: if not key_group: key_group = 4 if not password: password = self.random_password() data = { 'name': name, 'password': password, 'key_group': key_group } params = urlencode(data) result = self.connection.request('/key/password/', data=params, method='POST').object return result.get('data', {}).get('id', '') def ex_delete_keypair(self, key_id, ssh=False): """Delete an ssh key or password given it's id """ if ssh: result = self.connection.request('/key/sshrsa/%s/' % key_id, method='DELETE').object else: result = self.connection.request('/key/password/%s/' % key_id, method='DELETE').object return result.get('response') in VALID_RESPONSE_CODES def create_node(self, name, size, image, server_key=None, console_key=None, zone=None, **kwargs): """Creates the node, and sets the ssh key, console key NephoScale will respond with a 200-200 response after sending a valid request. If nowait=True is specified in the args, we then ask a few times until the server is created and assigned a public IP address, so that deploy_node can be run >>> from libcloud.compute.providers import get_driver >>> driver = get_driver('nephoscale') >>> conn = driver('nepho_user','nepho_password') >>> conn.list_nodes() >>> name = 'staging-server' >>> size = conn.list_sizes()[0] <NodeSize: id=27, ...name=CS025 - 0.25GB, 10GB, ...> >>> image = conn.list_images()[9] <NodeImage: id=49, name=Linux Ubuntu Server 10.04 LTS 64-bit, ...> >>> server_keys = conn.ex_list_keypairs(key_group=1)[0] <NodeKey: id=71211, name=markos> >>> server_key = conn.ex_list_keypairs(key_group=1)[0].id 70867 >>> console_keys = conn.ex_list_keypairs(key_group=4)[0] <NodeKey: id=71213, name=mistio28434> >>> console_key = conn.ex_list_keypairs(key_group=4)[0].id 70907 >>> node = conn.create_node(name=name, size=size, image=image, \ console_key=console_key, server_key=server_key) We can also create an ssh key, plus a console key and deploy node with them >>> server_key = conn.ex_create_keypair(name, public_key='123') 71211 >>> console_key = conn.ex_create_keypair(name, key_group=4) 71213 We can increase the number of connect attempts to wait until the node is created, so that deploy_node has ip address to deploy the script We can also specify the location >>> location = conn.list_locations()[0] >>> node = conn.create_node(name=name, >>> ... size=size, >>> ... image=image, >>> ... console_key=console_key, >>> ... server_key=server_key, >>> ... connect_attempts=10, >>> ... nowait=True, >>> ... zone=location.id) """ hostname = kwargs.get('hostname', name) service_type = size.id image = image.id connect_attempts = int(kwargs.get('connect_attempts', CONNECT_ATTEMPTS)) data = {'name': name, 'hostname': hostname, 'service_type': service_type, 'image': image, 'server_key': server_key, 'console_key': console_key, 'zone': zone } params = urlencode(data) try: node = self.connection.request('/server/cloud/', data=params, method='POST') except Exception as e: raise Exception("Failed to create node %s" % e) node = Node(id='', name=name, state=NodeState.UNKNOWN, public_ips=[], private_ips=[], driver=self) nowait = kwargs.get('ex_wait', False) if not nowait: return node else: # try to get the created node public ips, for use in deploy_node # At this point we don't have the id of the newly created Node, # so search name in nodes created_node = False while connect_attempts > 0: nodes = self.list_nodes() created_node = [c_node for c_node in nodes if c_node.name == name] if created_node: return created_node[0] else: time.sleep(60) connect_attempts = connect_attempts - 1 return node def _to_node(self, data): """Convert node in Node instances """ state = NODE_STATE_MAP.get(data.get('power_status'), '4') public_ips = [] private_ips = [] ip_addresses = data.get('ipaddresses', '') # E.g. "ipaddresses": "198.120.14.6, 10.132.60.1" if ip_addresses: for ip in ip_addresses.split(','): ip = ip.replace(' ', '') if is_private_subnet(ip): private_ips.append(ip) else: public_ips.append(ip) extra = { 'zone_data': data.get('zone'), 'zone': data.get('zone', {}).get('name'), 'image': data.get('image', {}).get('friendly_name'), 'create_time': data.get('create_time'), 'network_ports': data.get('network_ports'), 'is_console_enabled': data.get('is_console_enabled'), 'service_type': data.get('service_type', {}).get('friendly_name'), 'hostname': data.get('hostname') } node = Node(id=data.get('id'), name=data.get('name'), state=state, public_ips=public_ips, private_ips=private_ips, driver=self, extra=extra) return node def _to_key(self, data): return NodeKey(id=data.get('id'), name=data.get('name'), password=data.get('password'), key_group=data.get('key_group'), public_key=data.get('public_key')) def random_password(self, size=8): value = os.urandom(size) password = binascii.hexlify(value).decode('ascii') return password[:size]