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/saltstack/salt/lib/python3.10/site-packages/salt/utils/openstack
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils/openstack/nova.py
""" Nova class """ import inspect import logging import time import salt.utils.cloud import salt.utils.files from salt.exceptions import SaltCloudSystemExit from salt.utils.versions import Version HAS_NOVA = False # pylint: disable=import-error try: import novaclient import novaclient.auth_plugin import novaclient.base import novaclient.exceptions import novaclient.extension import novaclient.utils from novaclient import client from novaclient.shell import OpenStackComputeShell HAS_NOVA = True except ImportError: pass HAS_KEYSTONEAUTH = False try: import keystoneauth1.loading import keystoneauth1.session HAS_KEYSTONEAUTH = True except ImportError: pass # pylint: enable=import-error # Get logging started log = logging.getLogger(__name__) # Version added to novaclient.client.Client function NOVACLIENT_MINVER = "2.6.1" NOVACLIENT_MAXVER = "6.0.1" # dict for block_device_mapping_v2 CLIENT_BDM2_KEYS = { "id": "uuid", "source": "source_type", "dest": "destination_type", "bus": "disk_bus", "device": "device_name", "size": "volume_size", "format": "guest_format", "bootindex": "boot_index", "type": "device_type", "shutdown": "delete_on_termination", } def check_nova(): if HAS_NOVA: novaclient_ver = Version(novaclient.__version__) min_ver = Version(NOVACLIENT_MINVER) max_ver = Version(NOVACLIENT_MAXVER) if min_ver <= novaclient_ver <= max_ver: return HAS_NOVA elif novaclient_ver > max_ver: log.debug( "Older novaclient version required. Maximum: %s", NOVACLIENT_MAXVER ) return False log.debug("Newer novaclient version required. Minimum: %s", NOVACLIENT_MINVER) return False if check_nova(): try: import novaclient.auth_plugin except ImportError: log.debug( "Using novaclient version 7.0.0 or newer. Authentication " "plugin auth_plugin.py is not available anymore." ) # kwargs has to be an object instead of a dictionary for the __post_parse_arg__ class KwargsStruct: def __init__(self, **entries): self.__dict__.update(entries) def _parse_block_device_mapping_v2( block_device=None, boot_volume=None, snapshot=None, ephemeral=None, swap=None ): bdm = [] if block_device is None: block_device = [] if ephemeral is None: ephemeral = [] if boot_volume is not None: bdm_dict = { "uuid": boot_volume, "source_type": "volume", "destination_type": "volume", "boot_index": 0, "delete_on_termination": False, } bdm.append(bdm_dict) if snapshot is not None: bdm_dict = { "uuid": snapshot, "source_type": "snapshot", "destination_type": "volume", "boot_index": 0, "delete_on_termination": False, } bdm.append(bdm_dict) for device_spec in block_device: bdm_dict = {} for key, value in device_spec.items(): bdm_dict[CLIENT_BDM2_KEYS[key]] = value # Convert the delete_on_termination to a boolean or set it to true by # default for local block devices when not specified. if "delete_on_termination" in bdm_dict: action = bdm_dict["delete_on_termination"] bdm_dict["delete_on_termination"] = action == "remove" elif bdm_dict.get("destination_type") == "local": bdm_dict["delete_on_termination"] = True bdm.append(bdm_dict) for ephemeral_spec in ephemeral: bdm_dict = { "source_type": "blank", "destination_type": "local", "boot_index": -1, "delete_on_termination": True, } if "size" in ephemeral_spec: bdm_dict["volume_size"] = ephemeral_spec["size"] if "format" in ephemeral_spec: bdm_dict["guest_format"] = ephemeral_spec["format"] bdm.append(bdm_dict) if swap is not None: bdm_dict = { "source_type": "blank", "destination_type": "local", "boot_index": -1, "delete_on_termination": True, "guest_format": "swap", "volume_size": swap, } bdm.append(bdm_dict) return bdm class NovaServer: def __init__(self, name, server, password=None): """ Make output look like libcloud output for consistency """ self.name = name self.id = server["id"] self.image = server.get("image", {}).get("id", "Boot From Volume") self.size = server["flavor"]["id"] self.state = server["state"] self._uuid = None self.extra = {"metadata": server["metadata"], "access_ip": server["accessIPv4"]} self.addresses = server.get("addresses", {}) self.public_ips, self.private_ips = [], [] self.fixed_ips, self.floating_ips = [], [] for network in self.addresses.values(): for addr in network: if salt.utils.cloud.is_public_ip(addr["addr"]): self.public_ips.append(addr["addr"]) else: self.private_ips.append(addr["addr"]) if addr.get("OS-EXT-IPS:type") == "floating": self.floating_ips.append(addr["addr"]) else: self.fixed_ips.append(addr["addr"]) if password: self.extra["password"] = password def __str__(self): return str(self.__dict__) def get_entry(dict_, key, value, raise_error=True): for entry in dict_: if entry[key] == value: return entry if raise_error is True: raise SaltCloudSystemExit(f"Unable to find {key} in {dict_}.") return {} def get_entry_multi(dict_, pairs, raise_error=True): for entry in dict_: if all([entry[key] == value for key, value in pairs]): return entry if raise_error is True: raise SaltCloudSystemExit(f"Unable to find {pairs} in {dict_}.") return {} def get_endpoint_url_v3(catalog, service_type, region_name): for service_entry in catalog: if service_entry["type"] == service_type: for endpoint_entry in service_entry["endpoints"]: if ( endpoint_entry["region"] == region_name and endpoint_entry["interface"] == "public" ): return endpoint_entry["url"] return None def sanatize_novaclient(kwargs): variables = ( "username", "api_key", "project_id", "auth_url", "insecure", "timeout", "proxy_tenant_id", "proxy_token", "region_name", "endpoint_type", "extensions", "service_type", "service_name", "volume_service_name", "timings", "bypass_url", "os_cache", "no_cache", "http_log_debug", "auth_system", "auth_plugin", "auth_token", "cacert", "tenant_id", ) ret = {} for var in kwargs: if var in variables: ret[var] = kwargs[var] return ret # Function alias to not shadow built-ins class SaltNova: """ Class for all novaclient functions """ extensions = [] def __init__( self, username, project_id, auth_url, region_name=None, password=None, os_auth_plugin=None, use_keystoneauth=False, **kwargs, ): """ Set up nova credentials """ if all([use_keystoneauth, HAS_KEYSTONEAUTH]): self._new_init( username=username, project_id=project_id, auth_url=auth_url, region_name=region_name, password=password, os_auth_plugin=os_auth_plugin, **kwargs, ) else: self._old_init( username=username, project_id=project_id, auth_url=auth_url, region_name=region_name, password=password, os_auth_plugin=os_auth_plugin, **kwargs, ) def _new_init( self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, verify=True, **kwargs, ): if auth is None: auth = {} loader = keystoneauth1.loading.get_plugin_loader(os_auth_plugin or "password") self.client_kwargs = kwargs.copy() self.kwargs = auth.copy() if not self.extensions: if hasattr(OpenStackComputeShell, "_discover_extensions"): self.extensions = OpenStackComputeShell()._discover_extensions("2.0") else: self.extensions = client.discover_extensions("2.0") for extension in self.extensions: extension.run_hooks("__pre_parse_args__") self.client_kwargs["extensions"] = self.extensions self.kwargs["username"] = username self.kwargs["project_name"] = project_id self.kwargs["auth_url"] = auth_url self.kwargs["password"] = password if auth_url.endswith("3"): self.kwargs["user_domain_name"] = kwargs.get("user_domain_name", "default") self.kwargs["project_domain_name"] = kwargs.get( "project_domain_name", "default" ) self.client_kwargs["region_name"] = region_name self.client_kwargs["service_type"] = "compute" if hasattr(self, "extensions"): # needs an object, not a dictionary self.kwargstruct = KwargsStruct(**self.client_kwargs) for extension in self.extensions: extension.run_hooks("__post_parse_args__", self.kwargstruct) self.client_kwargs = self.kwargstruct.__dict__ # Requires novaclient version >= 2.6.1 self.version = str(kwargs.get("version", 2)) self.client_kwargs = sanatize_novaclient(self.client_kwargs) options = loader.load_from_options(**self.kwargs) self.session = keystoneauth1.session.Session(auth=options, verify=verify) conn = client.Client( version=self.version, session=self.session, **self.client_kwargs ) self.kwargs["auth_token"] = conn.client.session.get_token() identity_service_type = kwargs.get("identity_service_type", "identity") self.catalog = ( conn.client.session.get( "/auth/catalog", endpoint_filter={"service_type": identity_service_type} ) .json() .get("catalog", []) ) if conn.client.get_endpoint(service_type=identity_service_type).endswith("v3"): self._v3_setup(region_name) else: self._v2_setup(region_name) def _old_init( self, username, project_id, auth_url, region_name, password, os_auth_plugin, **kwargs, ): self.kwargs = kwargs.copy() if not self.extensions: if hasattr(OpenStackComputeShell, "_discover_extensions"): self.extensions = OpenStackComputeShell()._discover_extensions("2.0") else: self.extensions = client.discover_extensions("2.0") for extension in self.extensions: extension.run_hooks("__pre_parse_args__") self.kwargs["extensions"] = self.extensions self.kwargs["username"] = username self.kwargs["project_id"] = project_id self.kwargs["auth_url"] = auth_url self.kwargs["region_name"] = region_name self.kwargs["service_type"] = "compute" # used in novaclient extensions to see if they are rackspace or not, to know if it needs to load # the hooks for that extension or not. This is cleaned up by sanatize_novaclient self.kwargs["os_auth_url"] = auth_url if os_auth_plugin is not None: novaclient.auth_plugin.discover_auth_systems() auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_plugin) self.kwargs["auth_plugin"] = auth_plugin self.kwargs["auth_system"] = os_auth_plugin if not self.kwargs.get("api_key", None): self.kwargs["api_key"] = password # This has to be run before sanatize_novaclient before extra variables are cleaned out. if hasattr(self, "extensions"): # needs an object, not a dictionary self.kwargstruct = KwargsStruct(**self.kwargs) for extension in self.extensions: extension.run_hooks("__post_parse_args__", self.kwargstruct) self.kwargs = self.kwargstruct.__dict__ self.kwargs = sanatize_novaclient(self.kwargs) # Requires novaclient version >= 2.6.1 self.kwargs["version"] = str(kwargs.get("version", 2)) conn = client.Client(**self.kwargs) try: conn.client.authenticate() except novaclient.exceptions.AmbiguousEndpoints: raise SaltCloudSystemExit( "Nova provider requires a 'region_name' to be specified" ) self.kwargs["auth_token"] = conn.client.auth_token self.catalog = conn.client.service_catalog.catalog["access"]["serviceCatalog"] self._v2_setup(region_name) def _v3_setup(self, region_name): if region_name is not None: self.client_kwargs["bypass_url"] = get_endpoint_url_v3( self.catalog, "compute", region_name ) log.debug("Using Nova bypass_url: %s", self.client_kwargs["bypass_url"]) self.compute_conn = client.Client( version=self.version, session=self.session, **self.client_kwargs ) volume_endpoints = get_entry( self.catalog, "type", "volume", raise_error=False ).get("endpoints", {}) if volume_endpoints: if region_name is not None: self.client_kwargs["bypass_url"] = get_endpoint_url_v3( self.catalog, "volume", region_name ) log.debug( "Using Cinder bypass_url: %s", self.client_kwargs["bypass_url"] ) self.volume_conn = client.Client( version=self.version, session=self.session, **self.client_kwargs ) if hasattr(self, "extensions"): self.expand_extensions() else: self.volume_conn = None def _v2_setup(self, region_name): if region_name is not None: servers_endpoints = get_entry(self.catalog, "type", "compute")["endpoints"] self.kwargs["bypass_url"] = get_entry( servers_endpoints, "region", region_name )["publicURL"] self.compute_conn = client.Client(**self.kwargs) volume_endpoints = get_entry( self.catalog, "type", "volume", raise_error=False ).get("endpoints", {}) if volume_endpoints: if region_name is not None: self.kwargs["bypass_url"] = get_entry( volume_endpoints, "region", region_name )["publicURL"] self.volume_conn = client.Client(**self.kwargs) if hasattr(self, "extensions"): self.expand_extensions() else: self.volume_conn = None def expand_extensions(self): for connection in (self.compute_conn, self.volume_conn): if connection is None: continue for extension in self.extensions: for attr in extension.module.__dict__: if not inspect.isclass(getattr(extension.module, attr)): continue for key, value in connection.__dict__.items(): if not isinstance(value, novaclient.base.Manager): continue if value.__class__.__name__ == attr: setattr( connection, key, extension.manager_class(connection) ) def get_catalog(self): """ Return service catalog """ return self.catalog def server_show_libcloud(self, uuid): """ Make output look like libcloud output for consistency """ server_info = self.server_show(uuid) server = next(iter(server_info.values())) server_name = next(iter(server_info.keys())) if not hasattr(self, "password"): self.password = None ret = NovaServer(server_name, server, self.password) return ret def boot(self, name, flavor_id=0, image_id=0, timeout=300, **kwargs): """ Boot a cloud server. """ nt_ks = self.compute_conn kwargs["name"] = name kwargs["flavor"] = flavor_id kwargs["image"] = image_id or None ephemeral = kwargs.pop("ephemeral", []) block_device = kwargs.pop("block_device", []) boot_volume = kwargs.pop("boot_volume", None) snapshot = kwargs.pop("snapshot", None) swap = kwargs.pop("swap", None) kwargs["block_device_mapping_v2"] = _parse_block_device_mapping_v2( block_device=block_device, boot_volume=boot_volume, snapshot=snapshot, ephemeral=ephemeral, swap=swap, ) response = nt_ks.servers.create(**kwargs) self.uuid = response.id self.password = getattr(response, "adminPass", None) start = time.time() trycount = 0 while True: trycount += 1 try: return self.server_show_libcloud(self.uuid) except Exception as exc: # pylint: disable=broad-except log.debug("Server information not yet available: %s", exc) time.sleep(1) if time.time() - start > timeout: log.error( "Timed out after %s seconds while waiting for data", timeout ) return False log.debug("Retrying server_show() (try %s)", trycount) def show_instance(self, name): """ Find a server by its name (libcloud) """ return self.server_by_name(name) def root_password(self, server_id, password): """ Change server(uuid's) root password """ nt_ks = self.compute_conn nt_ks.servers.change_password(server_id, password) def server_by_name(self, name): """ Find a server by its name """ return self.server_show_libcloud(self.server_list().get(name, {}).get("id", "")) def _volume_get(self, volume_id): """ Organize information about a volume from the volume_id """ if self.volume_conn is None: raise SaltCloudSystemExit("No cinder endpoint available") nt_ks = self.volume_conn volume = nt_ks.volumes.get(volume_id) response = { "name": volume.display_name, "size": volume.size, "id": volume.id, "description": volume.display_description, "attachments": volume.attachments, "status": volume.status, } return response def volume_list(self, search_opts=None): """ List all block volumes """ if self.volume_conn is None: raise SaltCloudSystemExit("No cinder endpoint available") nt_ks = self.volume_conn volumes = nt_ks.volumes.list(search_opts=search_opts) response = {} for volume in volumes: response[volume.display_name] = { "name": volume.display_name, "size": volume.size, "id": volume.id, "description": volume.display_description, "attachments": volume.attachments, "status": volume.status, } return response def volume_show(self, name): """ Show one volume """ if self.volume_conn is None: raise SaltCloudSystemExit("No cinder endpoint available") volumes = self.volume_list( search_opts={"display_name": name}, ) volume = volumes[name] return volume def volume_create( self, name, size=100, snapshot=None, voltype=None, availability_zone=None ): """ Create a block device """ if self.volume_conn is None: raise SaltCloudSystemExit("No cinder endpoint available") nt_ks = self.volume_conn response = nt_ks.volumes.create( size=size, display_name=name, volume_type=voltype, snapshot_id=snapshot, availability_zone=availability_zone, ) return self._volume_get(response.id) def volume_delete(self, name): """ Delete a block device """ if self.volume_conn is None: raise SaltCloudSystemExit("No cinder endpoint available") nt_ks = self.volume_conn try: volume = self.volume_show(name) except KeyError as exc: raise SaltCloudSystemExit(f"Unable to find {name} volume: {exc}") if volume["status"] == "deleted": return volume response = nt_ks.volumes.delete(volume["id"]) return volume def volume_detach(self, name, timeout=300): """ Detach a block device """ try: volume = self.volume_show(name) except KeyError as exc: raise SaltCloudSystemExit(f"Unable to find {name} volume: {exc}") if not volume["attachments"]: return True response = self.compute_conn.volumes.delete_server_volume( volume["attachments"][0]["server_id"], volume["attachments"][0]["id"] ) trycount = 0 start = time.time() while True: trycount += 1 try: response = self._volume_get(volume["id"]) if response["status"] == "available": return response except Exception as exc: # pylint: disable=broad-except log.debug("Volume is detaching: %s", name) time.sleep(1) if time.time() - start > timeout: log.error( "Timed out after %d seconds while waiting for data", timeout ) return False log.debug("Retrying volume_show() (try %d)", trycount) def volume_attach(self, name, server_name, device="/dev/xvdb", timeout=300): """ Attach a block device """ try: volume = self.volume_show(name) except KeyError as exc: raise SaltCloudSystemExit(f"Unable to find {name} volume: {exc}") server = self.server_by_name(server_name) response = self.compute_conn.volumes.create_server_volume( server.id, volume["id"], device=device ) trycount = 0 start = time.time() while True: trycount += 1 try: response = self._volume_get(volume["id"]) if response["status"] == "in-use": return response except Exception as exc: # pylint: disable=broad-except log.debug("Volume is attaching: %s", name) time.sleep(1) if time.time() - start > timeout: log.error( "Timed out after %s seconds while waiting for data", timeout ) return False log.debug("Retrying volume_show() (try %s)", trycount) def suspend(self, instance_id): """ Suspend a server """ nt_ks = self.compute_conn response = nt_ks.servers.suspend(instance_id) return True def resume(self, instance_id): """ Resume a server """ nt_ks = self.compute_conn response = nt_ks.servers.resume(instance_id) return True def lock(self, instance_id): """ Lock an instance """ nt_ks = self.compute_conn response = nt_ks.servers.lock(instance_id) return True def delete(self, instance_id): """ Delete a server """ nt_ks = self.compute_conn response = nt_ks.servers.delete(instance_id) return True def flavor_list(self): """ Return a list of available flavors (nova flavor-list) """ nt_ks = self.compute_conn ret = {} for flavor in nt_ks.flavors.list(): links = {} for link in flavor.links: links[link["rel"]] = link["href"] ret[flavor.name] = { "disk": flavor.disk, "id": flavor.id, "name": flavor.name, "ram": flavor.ram, "swap": flavor.swap, "vcpus": flavor.vcpus, "links": links, } if hasattr(flavor, "rxtx_factor"): ret[flavor.name]["rxtx_factor"] = flavor.rxtx_factor return ret list_sizes = flavor_list def flavor_create( self, name, # pylint: disable=C0103 flavor_id=0, # pylint: disable=C0103 ram=0, disk=0, vcpus=1, ): """ Create a flavor """ nt_ks = self.compute_conn nt_ks.flavors.create( name=name, flavorid=flavor_id, ram=ram, disk=disk, vcpus=vcpus ) return {"name": name, "id": flavor_id, "ram": ram, "disk": disk, "vcpus": vcpus} def flavor_delete(self, flavor_id): # pylint: disable=C0103 """ Delete a flavor """ nt_ks = self.compute_conn nt_ks.flavors.delete(flavor_id) return f"Flavor deleted: {flavor_id}" def keypair_list(self): """ List keypairs """ nt_ks = self.compute_conn ret = {} for keypair in nt_ks.keypairs.list(): ret[keypair.name] = { "name": keypair.name, "fingerprint": keypair.fingerprint, "public_key": keypair.public_key, } return ret def keypair_add(self, name, pubfile=None, pubkey=None): """ Add a keypair """ nt_ks = self.compute_conn if pubfile: with salt.utils.files.fopen(pubfile, "r") as fp_: pubkey = salt.utils.stringutils.to_unicode(fp_.read()) if not pubkey: return False nt_ks.keypairs.create(name, public_key=pubkey) ret = {"name": name, "pubkey": pubkey} return ret def keypair_delete(self, name): """ Delete a keypair """ nt_ks = self.compute_conn nt_ks.keypairs.delete(name) return f"Keypair deleted: {name}" def image_show(self, image_id): """ Show image details and metadata """ nt_ks = self.compute_conn image = nt_ks.images.get(image_id) links = {} for link in image.links: links[link["rel"]] = link["href"] ret = { "name": image.name, "id": image.id, "status": image.status, "progress": image.progress, "created": image.created, "updated": image.updated, "metadata": image.metadata, "links": links, } if hasattr(image, "minDisk"): ret["minDisk"] = image.minDisk if hasattr(image, "minRam"): ret["minRam"] = image.minRam return ret def image_list(self, name=None): """ List server images """ nt_ks = self.compute_conn ret = {} for image in nt_ks.images.list(): links = {} for link in image.links: links[link["rel"]] = link["href"] ret[image.name] = { "name": image.name, "id": image.id, "status": image.status, "progress": image.progress, "created": image.created, "updated": image.updated, "metadata": image.metadata, "links": links, } if hasattr(image, "minDisk"): ret[image.name]["minDisk"] = image.minDisk if hasattr(image, "minRam"): ret[image.name]["minRam"] = image.minRam if name: return {name: ret[name]} return ret list_images = image_list def image_meta_set( self, image_id=None, name=None, **kwargs ): # pylint: disable=C0103 """ Set image metadata """ nt_ks = self.compute_conn if name: for image in nt_ks.images.list(): if image.name == name: image_id = image.id # pylint: disable=C0103 if not image_id: return {"Error": "A valid image name or id was not specified"} nt_ks.images.set_meta(image_id, kwargs) return {image_id: kwargs} def image_meta_delete( self, image_id=None, name=None, keys=None # pylint: disable=C0103 ): """ Delete image metadata """ nt_ks = self.compute_conn if name: for image in nt_ks.images.list(): if image.name == name: image_id = image.id # pylint: disable=C0103 pairs = keys.split(",") if not image_id: return {"Error": "A valid image name or id was not specified"} nt_ks.images.delete_meta(image_id, pairs) return {image_id: f"Deleted: {pairs}"} def server_list(self): """ List servers """ nt_ks = self.compute_conn ret = {} for item in nt_ks.servers.list(): try: ret[item.name] = { "id": item.id, "name": item.name, "state": item.status, "accessIPv4": item.accessIPv4, "accessIPv6": item.accessIPv6, "flavor": {"id": item.flavor["id"], "links": item.flavor["links"]}, "image": { "id": item.image["id"] if item.image else "Boot From Volume", "links": item.image["links"] if item.image else "", }, } except TypeError: pass return ret def server_list_min(self): """ List minimal information about servers """ nt_ks = self.compute_conn ret = {} for item in nt_ks.servers.list(detailed=False): try: ret[item.name] = {"id": item.id, "state": "Running"} except TypeError: pass return ret def server_list_detailed(self): """ Detailed list of servers """ nt_ks = self.compute_conn ret = {} for item in nt_ks.servers.list(): try: ret[item.name] = { "OS-EXT-SRV-ATTR": {}, "OS-EXT-STS": {}, "accessIPv4": item.accessIPv4, "accessIPv6": item.accessIPv6, "addresses": item.addresses, "created": item.created, "flavor": {"id": item.flavor["id"], "links": item.flavor["links"]}, "hostId": item.hostId, "id": item.id, "image": { "id": item.image["id"] if item.image else "Boot From Volume", "links": item.image["links"] if item.image else "", }, "key_name": item.key_name, "links": item.links, "metadata": item.metadata, "name": item.name, "state": item.status, "tenant_id": item.tenant_id, "updated": item.updated, "user_id": item.user_id, } except TypeError: continue ret[item.name]["progress"] = getattr(item, "progress", "0") if hasattr(item.__dict__, "OS-DCF:diskConfig"): ret[item.name]["OS-DCF"] = { "diskConfig": item.__dict__["OS-DCF:diskConfig"] } if hasattr(item.__dict__, "OS-EXT-SRV-ATTR:host"): ret[item.name]["OS-EXT-SRV-ATTR"]["host"] = item.__dict__[ "OS-EXT-SRV-ATTR:host" ] if hasattr(item.__dict__, "OS-EXT-SRV-ATTR:hypervisor_hostname"): ret[item.name]["OS-EXT-SRV-ATTR"]["hypervisor_hostname"] = ( item.__dict__["OS-EXT-SRV-ATTR:hypervisor_hostname"] ) if hasattr(item.__dict__, "OS-EXT-SRV-ATTR:instance_name"): ret[item.name]["OS-EXT-SRV-ATTR"]["instance_name"] = item.__dict__[ "OS-EXT-SRV-ATTR:instance_name" ] if hasattr(item.__dict__, "OS-EXT-STS:power_state"): ret[item.name]["OS-EXT-STS"]["power_state"] = item.__dict__[ "OS-EXT-STS:power_state" ] if hasattr(item.__dict__, "OS-EXT-STS:task_state"): ret[item.name]["OS-EXT-STS"]["task_state"] = item.__dict__[ "OS-EXT-STS:task_state" ] if hasattr(item.__dict__, "OS-EXT-STS:vm_state"): ret[item.name]["OS-EXT-STS"]["vm_state"] = item.__dict__[ "OS-EXT-STS:vm_state" ] if hasattr(item.__dict__, "security_groups"): ret[item.name]["security_groups"] = item.__dict__["security_groups"] return ret def server_show(self, server_id): """ Show details of one server """ ret = {} try: servers = self.server_list_detailed() except AttributeError: raise SaltCloudSystemExit( "Corrupt server in server_list_detailed. Remove corrupt servers." ) for server_name, server in servers.items(): if str(server["id"]) == server_id: ret[server_name] = server return ret def secgroup_create(self, name, description): """ Create a security group """ nt_ks = self.compute_conn nt_ks.security_groups.create(name, description) ret = {"name": name, "description": description} return ret def secgroup_delete(self, name): """ Delete a security group """ nt_ks = self.compute_conn for item in nt_ks.security_groups.list(): if item.name == name: nt_ks.security_groups.delete(item.id) return {name: f"Deleted security group: {name}"} return f"Security group not found: {name}" def secgroup_list(self): """ List security groups """ nt_ks = self.compute_conn ret = {} for item in nt_ks.security_groups.list(): ret[item.name] = { "name": item.name, "description": item.description, "id": item.id, "tenant_id": item.tenant_id, "rules": item.rules, } return ret def _item_list(self): """ List items """ nt_ks = self.compute_conn ret = [] for item in nt_ks.items.list(): ret.append(item.__dict__) return ret def _network_show(self, name, network_lst): """ Parse the returned network list """ for net in network_lst: if net.label == name: return net.__dict__ return {} def network_show(self, name): """ Show network information """ nt_ks = self.compute_conn net_list = nt_ks.networks.list() return self._network_show(name, net_list) def network_list(self): """ List extra private networks """ nt_ks = self.compute_conn return [network.__dict__ for network in nt_ks.networks.list()] def _sanatize_network_params(self, kwargs): """ Sanatize novaclient network parameters """ params = [ "label", "bridge", "bridge_interface", "cidr", "cidr_v6", "dns1", "dns2", "fixed_cidr", "gateway", "gateway_v6", "multi_host", "priority", "project_id", "vlan_start", "vpn_start", ] for variable in list(kwargs): # iterate over a copy, we might delete some if variable not in params: del kwargs[variable] return kwargs def network_create(self, name, **kwargs): """ Create extra private network """ nt_ks = self.compute_conn kwargs["label"] = name kwargs = self._sanatize_network_params(kwargs) net = nt_ks.networks.create(**kwargs) return net.__dict__ def _server_uuid_from_name(self, name): """ Get server uuid from name """ return self.server_list().get(name, {}).get("id", "") def virtual_interface_list(self, name): """ Get virtual interfaces on slice """ nt_ks = self.compute_conn nets = nt_ks.virtual_interfaces.list(self._server_uuid_from_name(name)) return [network.__dict__ for network in nets] def virtual_interface_create(self, name, net_name): """ Add an interfaces to a slice """ nt_ks = self.compute_conn serverid = self._server_uuid_from_name(name) networkid = self.network_show(net_name).get("id", None) if networkid is None: return {net_name: False} nets = nt_ks.virtual_interfaces.create(networkid, serverid) return nets def floating_ip_pool_list(self): """ List all floating IP pools .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn pools = nt_ks.floating_ip_pools.list() response = {} for pool in pools: response[pool.name] = { "name": pool.name, } return response def floating_ip_list(self): """ List floating IPs .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn floating_ips = nt_ks.floating_ips.list() response = {} for floating_ip in floating_ips: response[floating_ip.ip] = { "ip": floating_ip.ip, "fixed_ip": floating_ip.fixed_ip, "id": floating_ip.id, "instance_id": floating_ip.instance_id, "pool": floating_ip.pool, } return response def floating_ip_show(self, ip): """ Show info on specific floating IP .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn floating_ips = nt_ks.floating_ips.list() for floating_ip in floating_ips: if floating_ip.ip == ip: response = { "ip": floating_ip.ip, "fixed_ip": floating_ip.fixed_ip, "id": floating_ip.id, "instance_id": floating_ip.instance_id, "pool": floating_ip.pool, } return response return {} def floating_ip_create(self, pool=None): """ Allocate a floating IP .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn floating_ip = nt_ks.floating_ips.create(pool) response = { "ip": floating_ip.ip, "fixed_ip": floating_ip.fixed_ip, "id": floating_ip.id, "instance_id": floating_ip.instance_id, "pool": floating_ip.pool, } return response def floating_ip_delete(self, floating_ip): """ De-allocate a floating IP .. versionadded:: 2016.3.0 """ ip = self.floating_ip_show(floating_ip) nt_ks = self.compute_conn return nt_ks.floating_ips.delete(ip) def floating_ip_associate(self, server_name, floating_ip): """ Associate floating IP address to server .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn server_ = self.server_by_name(server_name) server = nt_ks.servers.get(server_.__dict__["id"]) server.add_floating_ip(floating_ip) return self.floating_ip_list()[floating_ip] def floating_ip_disassociate(self, server_name, floating_ip): """ Disassociate a floating IP from server .. versionadded:: 2016.3.0 """ nt_ks = self.compute_conn server_ = self.server_by_name(server_name) server = nt_ks.servers.get(server_.__dict__["id"]) server.remove_floating_ip(floating_ip) return self.floating_ip_list()[floating_ip] # The following is a list of functions that need to be incorporated in the # nova module. This list should be updated as functions are added. # # absolute-limits Print a list of absolute limits for a user # actions Retrieve server actions. # add-fixed-ip Add new IP address to network. # aggregate-add-host Add the host to the specified aggregate. # aggregate-create Create a new aggregate with the specified details. # aggregate-delete Delete the aggregate by its id. # aggregate-details Show details of the specified aggregate. # aggregate-list Print a list of all aggregates. # aggregate-remove-host # Remove the specified host from the specified aggregate. # aggregate-set-metadata # Update the metadata associated with the aggregate. # aggregate-update Update the aggregate's name and optionally # availability zone. # cloudpipe-create Create a cloudpipe instance for the given project # cloudpipe-list Print a list of all cloudpipe instances. # console-log Get console log output of a server. # credentials Show user credentials returned from auth # describe-resource Show details about a resource # diagnostics Retrieve server diagnostics. # dns-create Create a DNS entry for domain, name and ip. # dns-create-private-domain # Create the specified DNS domain. # dns-create-public-domain # Create the specified DNS domain. # dns-delete Delete the specified DNS entry. # dns-delete-domain Delete the specified DNS domain. # dns-domains Print a list of available dns domains. # dns-list List current DNS entries for domain and ip or domain # and name. # endpoints Discover endpoints that get returned from the # authenticate services # get-vnc-console Get a vnc console to a server. # host-action Perform a power action on a host. # host-update Update host settings. # image-create Create a new image by taking a snapshot of a running # server. # image-delete Delete an image. # live-migration Migrates a running instance to a new machine. # meta Set or Delete metadata on a server. # migrate Migrate a server. # pause Pause a server. # rate-limits Print a list of rate limits for a user # reboot Reboot a server. # rebuild Shutdown, re-image, and re-boot a server. # remove-fixed-ip Remove an IP address from a server. # rename Rename a server. # rescue Rescue a server. # resize Resize a server. # resize-confirm Confirm a previous resize. # resize-revert Revert a previous resize (and return to the previous # VM). # root-password Change the root password for a server. # secgroup-add-group-rule # Add a source group rule to a security group. # secgroup-add-rule Add a rule to a security group. # secgroup-delete-group-rule # Delete a source group rule from a security group. # secgroup-delete-rule # Delete a rule from a security group. # secgroup-list-rules # List rules for a security group. # ssh SSH into a server. # unlock Unlock a server. # unpause Unpause a server. # unrescue Unrescue a server. # usage-list List usage data for all tenants # volume-list List all the volumes. # volume-snapshot-create # Add a new snapshot. # volume-snapshot-delete # Remove a snapshot. # volume-snapshot-list # List all the snapshots. # volume-snapshot-show # Show details about a snapshot. # volume-type-create Create a new volume type. # volume-type-delete Delete a specific flavor # volume-type-list Print a list of available 'volume types'. # x509-create-cert Create x509 cert for a user in tenant # x509-get-root-cert Fetches the x509 root cert.