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/backup/drivers
Viewing File: /opt/imh-python/lib/python3.9/site-packages/libcloud/backup/drivers/dimensiondata.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 libcloud.utils.py3 import ET from libcloud.backup.base import BackupDriver, BackupTarget, BackupTargetJob from libcloud.backup.types import BackupTargetType from libcloud.backup.types import Provider from libcloud.common.dimensiondata import dd_object_to_id from libcloud.common.dimensiondata import DimensionDataConnection from libcloud.common.dimensiondata import DimensionDataBackupClient from libcloud.common.dimensiondata import DimensionDataBackupClientAlert from libcloud.common.dimensiondata import DimensionDataBackupClientType from libcloud.common.dimensiondata import DimensionDataBackupDetails from libcloud.common.dimensiondata import DimensionDataBackupSchedulePolicy from libcloud.common.dimensiondata import DimensionDataBackupStoragePolicy from libcloud.common.dimensiondata import API_ENDPOINTS, DEFAULT_REGION from libcloud.common.dimensiondata import TYPES_URN from libcloud.common.dimensiondata import GENERAL_NS, BACKUP_NS from libcloud.utils.xml import fixxpath, findtext, findall # pylint: disable=no-member DEFAULT_BACKUP_PLAN = 'Advanced' class DimensionDataBackupDriver(BackupDriver): """ DimensionData backup driver. """ selected_region = None connectionCls = DimensionDataConnection name = 'Dimension Data Backup' website = 'https://cloud.dimensiondata.com/' type = Provider.DIMENSIONDATA api_version = 1.0 network_domain_id = None def __init__(self, key, secret=None, secure=True, host=None, port=None, api_version=None, region=DEFAULT_REGION, **kwargs): if region not in API_ENDPOINTS and host is None: raise ValueError( 'Invalid region: %s, no host specified' % (region)) if region is not None: self.selected_region = API_ENDPOINTS[region] super(DimensionDataBackupDriver, self).__init__( key=key, secret=secret, secure=secure, host=host, port=port, api_version=api_version, region=region, **kwargs) def _ex_connection_class_kwargs(self): """ Add the region to the kwargs before the connection is instantiated """ kwargs = super(DimensionDataBackupDriver, self)._ex_connection_class_kwargs() kwargs['region'] = self.selected_region return kwargs def get_supported_target_types(self): """ Get a list of backup target types this driver supports :return: ``list`` of :class:``BackupTargetType`` """ return [BackupTargetType.VIRTUAL] def list_targets(self): """ List all backuptargets :rtype: ``list`` of :class:`BackupTarget` """ targets = self._to_targets( self.connection.request_with_orgId_api_2('server/server').object) return targets def create_target(self, name, address, type=BackupTargetType.VIRTUAL, extra=None): """ Creates a new backup target :param name: Name of the target (not used) :type name: ``str`` :param address: The ID of the node in Dimension Data Cloud :type address: ``str`` :param type: Backup target type, only Virtual supported :type type: :class:`BackupTargetType` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :rtype: Instance of :class:`BackupTarget` """ if extra is not None: service_plan = extra.get('servicePlan', DEFAULT_BACKUP_PLAN) else: service_plan = DEFAULT_BACKUP_PLAN extra = {'servicePlan': service_plan} create_node = ET.Element('NewBackup', {'xmlns': BACKUP_NS}) create_node.set('servicePlan', service_plan) response = self.connection.request_with_orgId_api_1( 'server/%s/backup' % (address), method='POST', data=ET.tostring(create_node)).object asset_id = None for info in findall(response, 'additionalInformation', GENERAL_NS): if info.get('name') == 'assetId': asset_id = findtext(info, 'value', GENERAL_NS) return BackupTarget( id=asset_id, name=name, address=address, type=type, extra=extra, driver=self ) def create_target_from_node(self, node, type=BackupTargetType.VIRTUAL, extra=None): """ Creates a new backup target from an existing node :param node: The Node to backup :type node: ``Node`` :param type: Backup target type (Physical, Virtual, ...). :type type: :class:`BackupTargetType` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :rtype: Instance of :class:`BackupTarget` """ return self.create_target(name=node.name, address=node.id, type=BackupTargetType.VIRTUAL, extra=extra) def create_target_from_container(self, container, type=BackupTargetType.OBJECT, extra=None): """ Creates a new backup target from an existing storage container :param node: The Container to backup :type node: ``Container`` :param type: Backup target type (Physical, Virtual, ...). :type type: :class:`BackupTargetType` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :rtype: Instance of :class:`BackupTarget` """ return NotImplementedError( 'create_target_from_container not supported for this driver') def update_target(self, target, name=None, address=None, extra=None): """ Update the properties of a backup target, only changing the serviceplan is supported. :param target: Backup target to update :type target: Instance of :class:`BackupTarget` or ``str`` :param name: Name of the target :type name: ``str`` :param address: Hostname, FQDN, IP, file path etc. :type address: ``str`` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :rtype: Instance of :class:`BackupTarget` """ if extra is not None: service_plan = extra.get('servicePlan', DEFAULT_BACKUP_PLAN) else: service_plan = DEFAULT_BACKUP_PLAN request = ET.Element('ModifyBackup', {'xmlns': BACKUP_NS}) request.set('servicePlan', service_plan) server_id = self._target_to_target_address(target) self.connection.request_with_orgId_api_1( 'server/%s/backup/modify' % (server_id), method='POST', data=ET.tostring(request)).object if isinstance(target, BackupTarget): target.extra = extra else: target = self.ex_get_target_by_id(server_id) return target def delete_target(self, target): """ Delete a backup target :param target: Backup target to delete :type target: Instance of :class:`BackupTarget` or ``str`` :rtype: ``bool`` """ server_id = self._target_to_target_address(target) response = self.connection.request_with_orgId_api_1( 'server/%s/backup?disable' % (server_id), method='GET').object response_code = findtext(response, 'result', GENERAL_NS) return response_code in ['IN_PROGRESS', 'SUCCESS'] def list_recovery_points(self, target, start_date=None, end_date=None): """ List the recovery points available for a target :param target: Backup target to delete :type target: Instance of :class:`BackupTarget` :param start_date: The start date to show jobs between (optional) :type start_date: :class:`datetime.datetime` :param end_date: The end date to show jobs between (optional) :type end_date: :class:`datetime.datetime`` :rtype: ``list`` of :class:`BackupTargetRecoveryPoint` """ raise NotImplementedError( 'list_recovery_points not implemented for this driver') def recover_target(self, target, recovery_point, path=None): """ Recover a backup target to a recovery point :param target: Backup target to delete :type target: Instance of :class:`BackupTarget` :param recovery_point: Backup target with the backup data :type recovery_point: Instance of :class:`BackupTarget` :param path: The part of the recovery point to recover (optional) :type path: ``str`` :rtype: Instance of :class:`BackupTargetJob` """ raise NotImplementedError( 'recover_target not implemented for this driver') def recover_target_out_of_place(self, target, recovery_point, recovery_target, path=None): """ Recover a backup target to a recovery point out-of-place :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :param recovery_point: Backup target with the backup data :type recovery_point: Instance of :class:`BackupTarget` :param recovery_target: Backup target with to recover the data to :type recovery_target: Instance of :class:`BackupTarget` :param path: The part of the recovery point to recover (optional) :type path: ``str`` :rtype: Instance of :class:`BackupTargetJob` """ raise NotImplementedError( 'recover_target_out_of_place not implemented for this driver') def get_target_job(self, target, id): """ Get a specific backup job by ID :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :param id: Backup target with the backup data :type id: Instance of :class:`BackupTarget` :rtype: :class:`BackupTargetJob` """ jobs = self.list_target_jobs(target) return list(filter(lambda x: x.id == id, jobs))[0] def list_target_jobs(self, target): """ List the backup jobs on a target :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :rtype: ``list`` of :class:`BackupTargetJob` """ raise NotImplementedError( 'list_target_jobs not implemented for this driver') def create_target_job(self, target, extra=None): """ Create a new backup job on a target :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :param extra: (optional) Extra attributes (driver specific). :type extra: ``dict`` :rtype: Instance of :class:`BackupTargetJob` """ raise NotImplementedError( 'create_target_job not implemented for this driver') def resume_target_job(self, target, job): """ Resume a suspended backup job on a target :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :param job: Backup target job to resume :type job: Instance of :class:`BackupTargetJob` :rtype: ``bool`` """ raise NotImplementedError( 'resume_target_job not implemented for this driver') def suspend_target_job(self, target, job): """ Suspend a running backup job on a target :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` :param job: Backup target job to suspend :type job: Instance of :class:`BackupTargetJob` :rtype: ``bool`` """ raise NotImplementedError( 'suspend_target_job not implemented for this driver') def cancel_target_job(self, job, ex_client=None, ex_target=None): """ Cancel a backup job on a target :param job: Backup target job to cancel. If it is ``None`` ex_client and ex_target must be set :type job: Instance of :class:`BackupTargetJob` or ``None`` :param ex_client: Client of the job to cancel. Not necessary if job is specified. DimensionData only has 1 job per client :type ex_client: Instance of :class:`DimensionDataBackupClient` or ``str`` :param ex_target: Target to cancel a job from. Not necessary if job is specified. :type ex_target: Instance of :class:`BackupTarget` or ``str`` :rtype: ``bool`` """ if job is None: if ex_client is None or ex_target is None: raise ValueError("Either job or ex_client and " "ex_target have to be set") server_id = self._target_to_target_address(ex_target) client_id = self._client_to_client_id(ex_client) else: server_id = job.target.address client_id = job.extra['clientId'] response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client/%s?cancelJob' % (server_id, client_id), method='GET').object response_code = findtext(response, 'result', GENERAL_NS) return response_code in ['IN_PROGRESS', 'SUCCESS'] def ex_get_target_by_id(self, id): """ Get a target by server id :param id: The id of the target you want to get :type id: ``str`` :rtype: :class:`BackupTarget` """ node = self.connection.request_with_orgId_api_2( 'server/server/%s' % id).object return self._to_target(node) def ex_add_client_to_target(self, target, client_type, storage_policy, schedule_policy, trigger, email): """ Add a client to a target :param target: Backup target with the backup data :type target: Instance of :class:`BackupTarget` or ``str`` :param client: Client to add to the target :type client: Instance of :class:`DimensionDataBackupClientType` or ``str`` :param storage_policy: The storage policy for the client :type storage_policy: Instance of :class:`DimensionDataBackupStoragePolicy` or ``str`` :param schedule_policy: The schedule policy for the client :type schedule_policy: Instance of :class:`DimensionDataBackupSchedulePolicy` or ``str`` :param trigger: The notify trigger for the client :type trigger: ``str`` :param email: The notify email for the client :type email: ``str`` :rtype: ``bool`` """ server_id = self._target_to_target_address(target) backup_elm = ET.Element('NewBackupClient', {'xmlns': BACKUP_NS}) if isinstance(client_type, DimensionDataBackupClientType): ET.SubElement(backup_elm, "type").text = client_type.type else: ET.SubElement(backup_elm, "type").text = client_type if isinstance(storage_policy, DimensionDataBackupStoragePolicy): ET.SubElement(backup_elm, "storagePolicyName").text = storage_policy.name else: ET.SubElement(backup_elm, "storagePolicyName").text = storage_policy if isinstance(schedule_policy, DimensionDataBackupSchedulePolicy): ET.SubElement(backup_elm, "schedulePolicyName").text = schedule_policy.name else: ET.SubElement(backup_elm, "schedulePolicyName").text = schedule_policy alerting_elm = ET.SubElement(backup_elm, "alerting") alerting_elm.set('trigger', trigger) ET.SubElement(alerting_elm, "emailAddress").text = email response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client' % (server_id), method='POST', data=ET.tostring(backup_elm)).object response_code = findtext(response, 'result', GENERAL_NS) return response_code in ['IN_PROGRESS', 'SUCCESS'] def ex_remove_client_from_target(self, target, backup_client): """ Removes a client from a backup target :param target: The backup target to remove the client from :type target: :class:`BackupTarget` or ``str`` :param backup_client: The backup client to remove :type backup_client: :class:`DimensionDataBackupClient` or ``str`` :rtype: ``bool`` """ server_id = self._target_to_target_address(target) client_id = self._client_to_client_id(backup_client) response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client/%s?disable' % (server_id, client_id), method='GET').object response_code = findtext(response, 'result', GENERAL_NS) return response_code in ['IN_PROGRESS', 'SUCCESS'] def ex_get_backup_details_for_target(self, target): """ Returns a backup details object for a target :param target: The backup target to get details for :type target: :class:`BackupTarget` or ``str`` :rtype: :class:`DimensionDataBackupDetails` """ if not isinstance(target, BackupTarget): target = self.ex_get_target_by_id(target) if target is None: return response = self.connection.request_with_orgId_api_1( 'server/%s/backup' % (target.address), method='GET').object return self._to_backup_details(response, target) def ex_list_available_client_types(self, target): """ Returns a list of available backup client types :param target: The backup target to list available types for :type target: :class:`BackupTarget` or ``str`` :rtype: ``list`` of :class:`DimensionDataBackupClientType` """ server_id = self._target_to_target_address(target) response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client/type' % (server_id), method='GET').object return self._to_client_types(response) def ex_list_available_storage_policies(self, target): """ Returns a list of available backup storage policies :param target: The backup target to list available policies for :type target: :class:`BackupTarget` or ``str`` :rtype: ``list`` of :class:`DimensionDataBackupStoragePolicy` """ server_id = self._target_to_target_address(target) response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client/storagePolicy' % (server_id), method='GET').object return self._to_storage_policies(response) def ex_list_available_schedule_policies(self, target): """ Returns a list of available backup schedule policies :param target: The backup target to list available policies for :type target: :class:`BackupTarget` or ``str`` :rtype: ``list`` of :class:`DimensionDataBackupSchedulePolicy` """ server_id = self._target_to_target_address(target) response = self.connection.request_with_orgId_api_1( 'server/%s/backup/client/schedulePolicy' % (server_id), method='GET').object return self._to_schedule_policies(response) def _to_storage_policies(self, object): elements = object.findall(fixxpath('storagePolicy', BACKUP_NS)) return [self._to_storage_policy(el) for el in elements] def _to_storage_policy(self, element): return DimensionDataBackupStoragePolicy( retention_period=int(element.get('retentionPeriodInDays')), name=element.get('name'), secondary_location=element.get('secondaryLocation') ) def _to_schedule_policies(self, object): elements = object.findall(fixxpath('schedulePolicy', BACKUP_NS)) return [self._to_schedule_policy(el) for el in elements] def _to_schedule_policy(self, element): return DimensionDataBackupSchedulePolicy( name=element.get('name'), description=element.get('description') ) def _to_client_types(self, object): elements = object.findall(fixxpath('backupClientType', BACKUP_NS)) return [self._to_client_type(el) for el in elements] def _to_client_type(self, element): description = element.get('description') if description is None: description = findtext(element, 'description', BACKUP_NS) return DimensionDataBackupClientType( type=element.get('type'), description=description, is_file_system=bool(element.get('isFileSystem') == 'true') ) def _to_backup_details(self, object, target): return DimensionDataBackupDetails( asset_id=object.get('assetId'), service_plan=object.get('servicePlan'), status=object.get('state'), clients=self._to_clients(object, target) ) def _to_clients(self, object, target): elements = object.findall(fixxpath('backupClient', BACKUP_NS)) return [self._to_client(el, target) for el in elements] def _to_client(self, element, target): client_id = element.get('id') return DimensionDataBackupClient( id=client_id, type=self._to_client_type(element), status=element.get('status'), schedule_policy=findtext(element, 'schedulePolicyName', BACKUP_NS), storage_policy=findtext(element, 'storagePolicyName', BACKUP_NS), download_url=findtext(element, 'downloadUrl', BACKUP_NS), running_job=self._to_backup_job(element, target, client_id), alert=self._to_alert(element) ) def _to_alert(self, element): alert = element.find(fixxpath('alerting', BACKUP_NS)) if alert is not None: notify_list = [ email_addr.text for email_addr in alert.findall(fixxpath('emailAddress', BACKUP_NS)) ] return DimensionDataBackupClientAlert( trigger=element.get('trigger'), notify_list=notify_list ) return None def _to_backup_job(self, element, target, client_id): running_job = element.find(fixxpath('runningJob', BACKUP_NS)) if running_job is not None: return BackupTargetJob( id=running_job.get('id'), status=running_job.get('status'), progress=int(running_job.get('percentageComplete')), driver=self.connection.driver, target=target, extra={'clientId': client_id} ) return None def _to_targets(self, object): node_elements = object.findall(fixxpath('server', TYPES_URN)) return [self._to_target(el) for el in node_elements] def _to_target(self, element): backup = findall(element, 'backup', TYPES_URN) if len(backup) == 0: return extra = { 'description': findtext(element, 'description', TYPES_URN), 'sourceImageId': findtext(element, 'sourceImageId', TYPES_URN), 'datacenterId': element.get('datacenterId'), 'deployedTime': findtext(element, 'createTime', TYPES_URN), 'servicePlan': backup[0].get('servicePlan') } n = BackupTarget(id=backup[0].get('assetId'), name=findtext(element, 'name', TYPES_URN), address=element.get('id'), driver=self.connection.driver, type=BackupTargetType.VIRTUAL, extra=extra) return n @staticmethod def _client_to_client_id(backup_client): return dd_object_to_id(backup_client, DimensionDataBackupClient) @staticmethod def _target_to_target_address(target): return dd_object_to_id(target, BackupTarget, id_value='address')