PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/usr/lib/python2.7/site-packages/lsm/
Upload File :
Current File : //usr/lib/python2.7/site-packages/lsm/_data.py
# Copyright (C) 2011-2016 Red Hat, Inc.
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; If not, see <http://www.gnu.org/licenses/>.
#
# Author: tasleson
#         Gris Ge <fge@redhat.com>
#         Joe Handzik <joseph.t.handzik@hpe.com>

from abc import ABCMeta as _ABCMeta
import re
import binascii
from six import with_metaclass

try:
    import simplejson as json
except ImportError:
    import json

from json.decoder import WHITESPACE

from lsm._common import get_class, default_property, ErrorNumber, LsmError

import six


class DataEncoder(json.JSONEncoder):
    """
    Custom json encoder for objects derived form ILsmData
    """

    def default(self, my_class):
        if not isinstance(my_class, IData):
            raise ValueError('incorrect class type:' + str(type(my_class)))
        else:
            return my_class._to_dict()


class DataDecoder(json.JSONDecoder):
    """
    Custom json decoder for objects derived from ILsmData
    """

    @staticmethod
    def __process_dict(d):
        """
        Processes a dictionary
        """
        rc = {}

        if 'class' in d:
            rc = IData._factory(d)
        else:
            for (k, v) in d.items():
                rc[k] = DataDecoder.__decode(v)

        return rc

    @staticmethod
    def __process_list(l):
        """
        Processes a list
        """
        rc = []
        for elem, value in enumerate(l):
            if type(value) is list:
                rc.append(DataDecoder.__process_list(value))
            elif type(value) is dict:
                rc.append(DataDecoder.__process_dict(value))
            else:
                rc.append(value)
        return rc

    @staticmethod
    def __decode(e):
        """
        Decodes the parsed json
        """
        if type(e) is dict:
            return DataDecoder.__process_dict(e)
        elif type(e) is list:
            return DataDecoder.__process_list(e)
        else:
            return e

    def decode(self, json_string, _w=WHITESPACE.match):
        return DataDecoder.__decode(json.loads(json_string))


class IData(with_metaclass(_ABCMeta, object)):
    """
    Base class functionality of serializable
    classes.
    """

    def _to_dict(self):
        """
        Represent the class as a dictionary
        """
        rc = {'class': self.__class__.__name__}

        # If one of the attributes is another IData we will
        # process that too, is there a better way to handle this?
        for (k, v) in list(self.__dict__.items()):
            if isinstance(v, IData):
                rc[k[1:]] = v._to_dict()
            else:
                rc[k[1:]] = v

        return rc

    @staticmethod
    def _factory(d):
        """
        Factory for creating the appropriate class given a dictionary.
        This only works for objects that inherit from IData
        """
        if 'class' in d:
            class_name = d['class']
            del d['class']
            c = get_class(__name__ + '.' + class_name)

            # If any of the parameters are themselves an IData process them
            for k, v in list(d.items()):
                if isinstance(v, dict) and 'class' in v:
                    d['_' + k] = IData._factory(d.pop(k))
                else:
                    d['_' + k] = d.pop(k)

            return c(**d)

    def __str__(self):
        """
        Used for human string representation.
        """
        return str(self._to_dict())


@default_property('id', doc="Unique identifier")
@default_property('name', doc="Disk name (aka. vendor)")
@default_property('disk_type', doc="Enumerated type of disk")
@default_property('block_size', doc="Size of each block")
@default_property('num_of_blocks', doc="Total number of blocks")
@default_property('status', doc="Enumerated status")
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Private plugin data")
class Disk(IData):
    """
    Represents a disk.
    """
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

    # We use '-1' to indicate we failed to get the requested number.
    # For example, when block found is undetectable, we use '-1' instead of
    # confusing 0.
    BLOCK_COUNT_NOT_FOUND = -1
    BLOCK_SIZE_NOT_FOUND = -1

    TYPE_UNKNOWN = 0
    TYPE_OTHER = 1
    TYPE_ATA = 3     # IDE disk which is seldomly used.
    TYPE_SATA = 4
    TYPE_SAS = 5
    TYPE_FC = 6
    TYPE_SOP = 7     # SCSI over PCIe(SSD)
    TYPE_SCSI = 8
    TYPE_LUN = 9   # Remote LUN was treated as a disk.

    # Due to complexity of disk types, we are defining these beside DMTF
    # standards:
    TYPE_NL_SAS = 51    # Near-Line SAS==SATA disk + SAS port.

    # in DMTF CIM 2.34.0+ CIM_DiskDrive['DiskType'], they also defined
    # SSD and HYBRID disk type. We use it as fallback.
    TYPE_HDD = 52    # Normal HDD
    TYPE_SSD = 53    # Solid State Drive
    TYPE_HYBRID = 54    # uses a combination of HDD and SSD

    STATUS_UNKNOWN = 1 << 0
    STATUS_OK = 1 << 1
    STATUS_OTHER = 1 << 2
    STATUS_PREDICTIVE_FAILURE = 1 << 3
    STATUS_ERROR = 1 << 4
    STATUS_REMOVED = 1 << 5
    STATUS_STARTING = 1 << 6
    STATUS_STOPPING = 1 << 7
    STATUS_STOPPED = 1 << 8
    STATUS_INITIALIZING = 1 << 9
    STATUS_MAINTENANCE_MODE = 1 << 10
    # In maintenance for bad sector scan, integrity check and etc
    # It might be combined with STATUS_OK or
    # STATUS_STOPPED for online maintenance or offline maintenance.
    STATUS_SPARE_DISK = 1 << 11
    # Indicate disk is a spare disk.
    STATUS_RECONSTRUCT = 1 << 12
    # Indicate disk is reconstructing data.
    STATUS_FREE = 1 << 13
    # New in version 1.2, indicate the whole disk is not holding any data or
    # acting as a dedicate spare disk.
    # This disk could be assigned as a dedicated spare disk or used for
    # creating pool.
    # If any spare disk(like those on NetApp ONTAP) does not require
    # any explicit action when assigning to pool, it should be treated as
    # free disk and marked as STATUS_FREE|STATUS_SPARE_DISK.

    RPM_NO_SUPPORT = -2
    RPM_UNKNOWN = -1
    RPM_NON_ROTATING_MEDIUM = 0
    RPM_ROTATING_UNKNOWN_SPEED = 1

    LINK_TYPE_NO_SUPPORT = -2
    LINK_TYPE_UNKNOWN = -1
    LINK_TYPE_FC = 0
    LINK_TYPE_SSA = 2
    LINK_TYPE_SBP = 3
    LINK_TYPE_SRP = 4
    LINK_TYPE_ISCSI = 5
    LINK_TYPE_SAS = 6
    LINK_TYPE_ADT = 7
    LINK_TYPE_ATA = 8
    LINK_TYPE_USB = 9
    LINK_TYPE_SOP = 10
    LINK_TYPE_PCIE = 11

    LED_STATUS_UNKNOWN = 1 << 0
    LED_STATUS_IDENT_ON = 1 << 1
    LED_STATUS_IDENT_OFF = 1 << 2
    LED_STATUS_IDENT_UNKNOWN = 1 << 3
    LED_STATUS_FAULT_ON = 1 << 4
    LED_STATUS_FAULT_OFF = 1 << 5
    LED_STATUS_FAULT_UNKNOWN = 1 << 6

    LINK_SPEED_UNKNOWN = 0

    HEALTH_STATUS_UNKNOWN = -1
    HEALTH_STATUS_FAIL = 0
    HEALTH_STATUS_WARN = 1
    HEALTH_STATUS_GOOD = 2

    def __init__(self, _id, _name, _disk_type, _block_size, _num_of_blocks,
                 _status, _system_id, _plugin_data=None, _vpd83='',
                 _location='', _rpm=RPM_NO_SUPPORT,
                 _link_type=LINK_TYPE_NO_SUPPORT):
        self._id = _id
        self._name = _name
        self._disk_type = _disk_type
        self._block_size = _block_size
        self._num_of_blocks = _num_of_blocks
        self._status = _status
        self._system_id = _system_id
        self._plugin_data = _plugin_data
        if _vpd83 and not Volume.vpd83_verify(_vpd83):
            raise LsmError(ErrorNumber.INVALID_ARGUMENT,
                           "Incorrect format of VPD 0x83 NAA(3) string: '%s', "
                           "expecting 32 or 16 lower case hex characters" %
                           _vpd83)
        self._vpd83 = _vpd83
        self._location = _location
        self._rpm = _rpm
        self._link_type = _link_type

    @property
    def size_bytes(self):
        """
        Disk size in bytes.
        """
        return self.block_size * self.num_of_blocks

    @property
    def vpd83(self):
        """
        String. SCSI VPD83 ID. New in version 1.3.
        Only available for DAS(direct attached storage) systems.
        The VPD83 ID could be used in 'lsm.SCSI.disk_paths_of_vpd83()'
        when physical disk is exposed to OS directly.
        """
        if self._vpd83 == '':
            raise LsmError(
                ErrorNumber.NO_SUPPORT,
                "Disk.vpd83 is not supported by current disk or plugin")

        return self._vpd83

    @property
    def location(self):
        """
        String. Disk location in storage topology. New in version 1.3.
        """
        if self._location == '':
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "Disk.location property is not supported by this "
                           "plugin yet")
        return self._location

    @property
    def rpm(self):
        """
        Integer. New in version 1.3.
        Disk rotation speed - revolutions per minute(RPM):
            -1 (LSM_DISK_RPM_UNKNOWN):
                Unknown RPM
             0 (LSM_DISK_RPM_NON_ROTATING_MEDIUM):
                Non-rotating medium (e.g., SSD)
             1 (LSM_DISK_RPM_ROTATING_UNKNOWN_SPEED):
                Rotational disk with unknown speed
            >1:
                Normal rotational disk (e.g., HDD)
        """
        if self._rpm == Disk.RPM_NO_SUPPORT:
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "Disk.rpm is not supported by this plugin yet")
        return self._rpm

    @property
    def link_type(self):
        """
        Integer. New in version 1.3.
        Link type, possible values are:
            lsm.Disk.LINK_TYPE_UNKNOWN
                Failed to detect link type
            lsm.Disk.LINK_TYPE_FC
                Fibre Channel
            lsm.Disk.LINK_TYPE_SSA
                Serial Storage Architecture, Old IBM tech.
            lsm.Disk.LINK_TYPE_SBP
                Serial Bus Protocol, used by IEEE 1394.
            lsm.Disk.LINK_TYPE_SRP
                SCSI RDMA Protocol
            lsm.Disk.LINK_TYPE_ISCSI
                Internet Small Computer System Interface
            lsm.Disk.LINK_TYPE_SAS
                Serial Attached SCSI
            lsm.Disk.LINK_TYPE_ADT
                Automation/Drive Interface Transport
                Protocol, often used by Tape.
            lsm.Disk.LINK_TYPE_ATA
                PATA/IDE or SATA.
            lsm.Disk.LINK_TYPE_USB
                USB disk.
            lsm.Disk.LINK_TYPE_SOP
                SCSI over PCI-E
            lsm.Disk.LINK_TYPE_PCIE
                PCI-E, e.g. NVMe
        """
        if self._link_type == Disk.LINK_TYPE_NO_SUPPORT:
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "Disk.link_type is not supported by this plugin "
                           "yet")
        return self._link_type

    def __str__(self):
        return self.name


# Lets do this once outside of the class to minimize the number of
# times it needs to be compiled.
_vol_regex_vpd83 = re.compile('(?:^6[0-9a-f]{31})|(?:^[235][0-9a-f]{15})$')


@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('vpd83', doc="Vital product page 0x83 identifier")
@default_property('block_size', doc="Volume block size")
@default_property('num_of_blocks', doc="Number of blocks")
@default_property('admin_state', doc="Enabled or disabled by administrator")
@default_property('system_id', doc="System identifier")
@default_property('pool_id', doc="Pool identifier")
@default_property("plugin_data", doc="Private plugin data")
class Volume(IData):
    """
    Represents a volume.
    """
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']

    # Replication types
    REPLICATE_UNKNOWN = -1
    REPLICATE_CLONE = 2
    REPLICATE_COPY = 3
    REPLICATE_MIRROR_SYNC = 4
    REPLICATE_MIRROR_ASYNC = 5

    # Provisioning types
    PROVISION_UNKNOWN = -1
    PROVISION_THIN = 1
    PROVISION_FULL = 2
    PROVISION_DEFAULT = 3

    ADMIN_STATE_DISABLED = 0
    ADMIN_STATE_ENABLED = 1

    RAID_TYPE_UNKNOWN = -1
    # The plugin failed to detect the volume's RAID type.
    RAID_TYPE_RAID0 = 0
    # Stripe
    RAID_TYPE_RAID1 = 1
    # Mirror for two disks. For 4 disks or more, they are RAID10.
    RAID_TYPE_RAID3 = 3
    # Byte-level striping with dedicated parity
    RAID_TYPE_RAID4 = 4
    # Block-level striping with dedicated parity
    RAID_TYPE_RAID5 = 5
    # Block-level striping with distributed parity
    RAID_TYPE_RAID6 = 6
    # Block-level striping with two distributed parities, aka, RAID-DP
    RAID_TYPE_RAID10 = 10
    # Stripe of mirrors
    RAID_TYPE_RAID15 = 15
    # Parity of mirrors
    RAID_TYPE_RAID16 = 16
    # Dual parity of mirrors
    RAID_TYPE_RAID50 = 50
    # Stripe of parities
    RAID_TYPE_RAID60 = 60
    # Stripe of dual parities
    RAID_TYPE_RAID51 = 51
    # Mirror of parities
    RAID_TYPE_RAID61 = 61
    # Mirror of dual parities
    RAID_TYPE_JBOD = 20
    # Just bunch of disks, no parity, no striping.
    RAID_TYPE_MIXED = 21
    # This volume contains multiple RAID settings.
    RAID_TYPE_OTHER = 22
    # Vendor specific RAID type

    STRIP_SIZE_UNKNOWN = 0
    DISK_COUNT_UNKNOWN = 0
    MIN_IO_SIZE_UNKNOWN = 0
    OPT_IO_SIZE_UNKNOWN = 0

    VCR_STRIP_SIZE_DEFAULT = 0

    WRITE_CACHE_POLICY_UNKNOWN = 1
    WRITE_CACHE_POLICY_WRITE_BACK = 2
    WRITE_CACHE_POLICY_AUTO = 3
    WRITE_CACHE_POLICY_WRITE_THROUGH = 4

    WRITE_CACHE_STATUS_UNKNOWN = 1
    WRITE_CACHE_STATUS_WRITE_BACK = 2
    WRITE_CACHE_STATUS_WRITE_THROUGH = 3

    READ_CACHE_POLICY_UNKNOWN = 1
    READ_CACHE_POLICY_ENABLED = 2
    READ_CACHE_POLICY_DISABLED = 3

    READ_CACHE_STATUS_UNKNOWN = 1
    READ_CACHE_STATUS_ENABLED = 2
    READ_CACHE_STATUS_DISABLED = 3

    PHYSICAL_DISK_CACHE_UNKNOWN = 1
    PHYSICAL_DISK_CACHE_ENABLED = 2
    PHYSICAL_DISK_CACHE_DISABLED = 3
    PHYSICAL_DISK_CACHE_USE_DISK_SETTING = 4

    def __init__(self, _id, _name, _vpd83, _block_size, _num_of_blocks,
                 _admin_state, _system_id, _pool_id, _plugin_data=None):
        self._id = _id                        # Identifier
        self._name = _name                    # Human recognisable name
        if _vpd83 and not Volume.vpd83_verify(_vpd83):
            raise LsmError(ErrorNumber.INVALID_ARGUMENT,
                           "Incorrect format of VPD 0x83 NAA(3) string: '%s', "
                           "expecting 32 or 16 lower case hex characters" %
                           _vpd83)
        self._vpd83 = _vpd83                  # SCSI page 83 unique ID
        self._block_size = _block_size        # Block size
        self._num_of_blocks = _num_of_blocks  # Number of blocks
        self._admin_state = _admin_state      # enable or disabled by admin
        self._system_id = _system_id          # System id this volume belongs
        self._pool_id = _pool_id              # Pool id this volume belongs
        self._plugin_data = _plugin_data

    @property
    def size_bytes(self):
        """
        Volume size in bytes.
        """
        return self.block_size * self.num_of_blocks

    def __str__(self):
        return self.name

    @staticmethod
    def vpd83_verify(vpd):
        """
        Returns True if string is valid vpd 0x83 representation
        """
        if vpd and _vol_regex_vpd83.match(vpd):
            return True
        return False


@default_property('id', doc="Unique identifier")
@default_property('name', doc="User defined system name")
@default_property('status', doc="Enumerated status of system")
@default_property('status_info', doc="Detail status information of system")
@default_property("plugin_data", doc="Private plugin data")
class System(IData):
    STATUS_UNKNOWN = 1 << 0
    STATUS_OK = 1 << 1
    STATUS_ERROR = 1 << 2
    STATUS_DEGRADED = 1 << 3
    STATUS_PREDICTIVE_FAILURE = 1 << 4
    STATUS_OTHER = 1 << 5

    MODE_NO_SUPPORT = -2
    MODE_UNKNOWN = -1
    MODE_HARDWARE_RAID = 0
    MODE_HBA = 1

    READ_CACHE_PCT_NO_SUPPORT = -2
    READ_CACHE_PCT_UNKNOWN = -1

    def __init__(self, _id, _name, _status, _status_info, _plugin_data=None,
                 _fw_version='', _mode=None, _read_cache_pct=None):
        self._id = _id
        self._name = _name
        self._status = _status
        self._status_info = _status_info
        self._plugin_data = _plugin_data
        self._fw_version = _fw_version
        if _read_cache_pct is None:
            self._read_cache_pct = System.READ_CACHE_PCT_NO_SUPPORT
        else:
            self._read_cache_pct = _read_cache_pct
        if _mode is None:
            self._mode = System.MODE_NO_SUPPORT
        else:
            self._mode = _mode

    @property
    def fw_version(self):
        """
        String. Firmware version string. New in version 1.3.
        On some system, it might contain multiple version strings, example:
            "Package: 23.32.0-0009, FW: 3.440.05-3712"
        """
        if self._fw_version == '':
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "System.fw_version() is not supported by this "
                           "plugin yet")
        return self._fw_version

    @property
    def mode(self):
        """
        Integer(enumerated value). System mode. New in version 1.3.
        Only available for HW RAID systems at this time.
        Possible values:
            * lsm.System.MODE_HARDWARE_RAID
                The logical volume(aka, RAIDed virtual disk) can be exposed
                to OS  while hardware RAID card is handling the RAID
                algorithm. Physical disk can not be exposed to OS directly.

            * lsm.System.MODE_HBA
                The physical disks can be exposed to OS directly.
                SCSI enclosure service might be exposed to OS also.
        """
        if self._mode == System.MODE_NO_SUPPORT:
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "System.mode is not supported by this plugin yet")
        return self._mode

    @property
    def read_cache_pct(self):
        """
        Integer. Read cache percentage. New in version 1.3.
        Possible values:
            * 0-100
                The read cache percentage. The write cache percentage will
                then be 100 - read_cache_pct
        """
        if self._read_cache_pct == System.READ_CACHE_PCT_NO_SUPPORT:
            raise LsmError(ErrorNumber.NO_SUPPORT,
                           "System.read_cache_pct is not supported by this "
                           "plugin yet")
        return self._read_cache_pct


@default_property('id', doc="Unique identifier")
@default_property('name', doc="User supplied name")
@default_property('total_space', doc="Total space in bytes")
@default_property('free_space', doc="Free space in bytes")
@default_property('status', doc="Enumerated status")
@default_property('status_info', doc="Text explaining status")
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Plug-in private data")
@default_property("element_type", doc="What pool can be used for")
@default_property("unsupported_actions",
                  doc="What cannot be done with this pool")
class Pool(IData):
    """
    Pool specific information
    """
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

    TOTAL_SPACE_NOT_FOUND = -1
    FREE_SPACE_NOT_FOUND = -1

    # Element Type indicate what kind of element could this pool create:
    #   * Another Pool
    #   * Volume (aka, LUN)
    #   * System Reserved Pool.
    ELEMENT_TYPE_POOL = 1 << 1
    ELEMENT_TYPE_VOLUME = 1 << 2
    ELEMENT_TYPE_FS = 1 << 3
    ELEMENT_TYPE_DELTA = 1 << 4
    ELEMENT_TYPE_VOLUME_FULL = 1 << 5
    ELEMENT_TYPE_VOLUME_THIN = 1 << 6
    ELEMENT_TYPE_SYS_RESERVED = 1 << 10     # Reserved for system use

    # Unsupported actions, what pool cannot be used for
    UNSUPPORTED_VOLUME_GROW = 1 << 0
    UNSUPPORTED_VOLUME_SHRINK = 1 << 1

    # Pool status could be any combination of these status.
    STATUS_UNKNOWN = 1 << 0
    STATUS_OK = 1 << 1
    STATUS_OTHER = 1 << 2
    STATUS_DEGRADED = 1 << 4
    STATUS_ERROR = 1 << 5
    STATUS_STOPPED = 1 << 9
    STATUS_RECONSTRUCTING = 1 << 12
    STATUS_VERIFYING = 1 << 13
    STATUS_INITIALIZING = 1 << 14
    STATUS_GROWING = 1 << 15

    MEMBER_TYPE_UNKNOWN = 0
    MEMBER_TYPE_OTHER = 1
    MEMBER_TYPE_DISK = 2
    MEMBER_TYPE_POOL = 3

    def __init__(self, _id, _name, _element_type, _unsupported_actions,
                 _total_space, _free_space,
                 _status, _status_info, _system_id, _plugin_data=None):
        self._id = _id                      # Identifier
        self._name = _name                  # Human recognisable name
        self._element_type = _element_type  # What pool can be used to create

        # What pool cannot be used for
        self._unsupported_actions = _unsupported_actions

        self._total_space = _total_space    # Total size
        self._free_space = _free_space      # Free space available
        self._status = _status              # Status of pool.
        self._status_info = _status_info    # Additional status text of pool
        self._system_id = _system_id        # System id this pool belongs
        self._plugin_data = _plugin_data    # Plugin private data


@default_property('id', doc="Unique identifier")
@default_property('name', doc="File system name")
@default_property('total_space', doc="Total space in bytes")
@default_property('free_space', doc="Free space available")
@default_property('pool_id', doc="What pool the file system resides on")
@default_property('system_id', doc="System ID")
@default_property("plugin_data", doc="Private plugin data")
class FileSystem(IData):
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']

    def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
                 _system_id, _plugin_data=None):
        self._id = _id
        self._name = _name
        self._total_space = _total_space
        self._free_space = _free_space
        self._pool_id = _pool_id
        self._system_id = _system_id
        self._plugin_data = _plugin_data


@default_property('id', doc="Unique identifier")
@default_property('name', doc="Snapshot name")
@default_property('ts', doc="Time stamp the snapshot was created")
@default_property("plugin_data", doc="Private plugin data")
class FsSnapshot(IData):

    def __init__(self, _id, _name, _ts, _plugin_data=None):
        self._id = _id
        self._name = _name
        self._ts = int(_ts)
        self._plugin_data = _plugin_data


@default_property('id', doc="Unique identifier")
@default_property('fs_id', doc="Filesystem that is exported")
@default_property('export_path', doc="Export path")
@default_property('auth', doc="Authentication type")
@default_property('root', doc="List of hosts with no_root_squash")
@default_property('rw', doc="List of hosts with Read & Write privileges")
@default_property('ro', doc="List of hosts with Read only privileges")
@default_property('anonuid', doc="UID for anonymous user id")
@default_property('anongid', doc="GID for anonymous group id")
@default_property('options', doc="String containing advanced options")
@default_property('plugin_data', doc="Plugin private data")
class NfsExport(IData):
    SUPPORTED_SEARCH_KEYS = ['id', 'fs_id']
    ANON_UID_GID_NA = -1
    ANON_UID_GID_ERROR = -2

    def __init__(self, _id, _fs_id, _export_path, _auth, _root, _rw, _ro,
                 _anonuid, _anongid, _options, _plugin_data=None):
        assert (_fs_id is not None)
        assert (_export_path is not None)

        self._id = _id
        self._fs_id = _fs_id          # File system exported
        self._export_path = _export_path     # Export path
        self._auth = _auth            # Authentication type
        self._root = _root            # List of hosts with no_root_squash
        self._rw = _rw                # List of hosts with read/write
        self._ro = _ro                # List of hosts with read/only
        self._anonuid = _anonuid      # uid for anonymous user id
        self._anongid = _anongid      # gid for anonymous group id
        self._options = _options      # NFS options
        self._plugin_data = _plugin_data


@default_property('src_block', doc="Source logical block address")
@default_property('dest_block', doc="Destination logical block address")
@default_property('block_count', doc="Number of blocks")
class BlockRange(IData):
    def __init__(self, _src_block, _dest_block, _block_count):
        self._src_block = _src_block
        self._dest_block = _dest_block
        self._block_count = _block_count


@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
@default_property('init_ids', doc="List of initiator IDs")
@default_property('init_type', doc="Initiator type")
@default_property('system_id', doc="System identifier")
@default_property('plugin_data', doc="Plugin private data")
class AccessGroup(IData):
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

    INIT_TYPE_UNKNOWN = 0
    INIT_TYPE_OTHER = 1
    INIT_TYPE_WWPN = 2
    INIT_TYPE_ISCSI_IQN = 5
    INIT_TYPE_ISCSI_WWPN_MIXED = 7

    def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
                 _plugin_data=None):
        self._id = _id
        self._name = _name                # AccessGroup name
        # A list of Initiator ID strings.
        self._init_ids = AccessGroup._standardize_init_list(_init_ids)

        self._init_type = _init_type
        self._system_id = _system_id      # System id this group belongs
        self._plugin_data = _plugin_data

    @staticmethod
    def _standardize_init_list(init_ids):
        rc = []
        for i in init_ids:
            valid, init_type, init_id = AccessGroup.initiator_id_verify(i)

            if valid:
                rc.append(init_id)
            else:
                raise LsmError(ErrorNumber.INVALID_ARGUMENT,
                               "Invalid initiator ID %s" % i)
        return rc

    _regex_wwpn = re.compile(r"""
        ^(0x|0X)?([0-9A-Fa-f]{2})
        (([\.:\-])?[0-9A-Fa-f]{2}){7}$
        """, re.X)

    @staticmethod
    def initiator_id_verify(init_id, init_type=None, raise_exception=False):
        """
        Public method which can be used to verify an initiator id
        :param init_id:
        :param init_type:
        :param raise_exception: Will throw a LsmError INVALID_ARGUMENT if
                                not a valid initiator address
        :return:(Bool, init_type, init_id)  Note: init_id will be returned in
                normalized format if it's a WWPN
        """
        if init_id.startswith('iqn') or init_id.startswith('eui') or\
                init_id.startswith('naa'):

            if init_type is None or \
                    init_type == AccessGroup.INIT_TYPE_ISCSI_IQN:
                return True, AccessGroup.INIT_TYPE_ISCSI_IQN, init_id
        if AccessGroup._regex_wwpn.match(str(init_id)):
            if init_type is None or \
                    init_type == AccessGroup.INIT_TYPE_WWPN:
                return (True, AccessGroup.INIT_TYPE_WWPN,
                        AccessGroup._wwpn_to_lsm_type(init_id))

        if raise_exception:
            raise LsmError(ErrorNumber.INVALID_ARGUMENT,
                           "Initiator id '%s' is invalid" % init_id)

        return False, None, None

    @staticmethod
    def _wwpn_to_lsm_type(wwpn, raise_error=True):
        """
        Convert provided WWPN string into LSM standard one:

        LSM WWPN format:
            ^(?:[0-9a-f]{2}:){7}[0-9a-f]{2}$
        LSM WWPN Example:
           10:00:00:00:c9:95:2f:de

        Acceptable WWPN format is:
            ^[0x|0X]{0,1}(:?[0-9A-Fa-f]{2}[\.\-:]{0,1}){7}[0-9A-Fa-f]{2}$
        Acceptable WWPN example:
           10:00:00:00:c9:95:2f:de
           10:00:00:00:C9:95:2F:DE
           10-00-00-00-C9-95-2F-DE
           10-00-00-00-c9-95-2f-de
           10.00.00.00.C9.95.2F.DE
           10.00.00.00.c9.95.2f.de
           0x10000000c9952fde
           0X10000000C9952FDE
           10000000c9952fde
           10000000C9952FDE
        Return the LSM WWPN
        Return None if raise_error is False and not a valid WWPN.
        """
        if AccessGroup._regex_wwpn.match(str(wwpn)):
            s = str(wwpn)
            s = s.lower()
            s = re.sub(r'0x', '', s)
            s = re.sub(r'[^0-9a-f]', '', s)
            s = ":".join(re.findall(r'..', s))
            return s
        if raise_error:
            raise LsmError(ErrorNumber.INVALID_ARGUMENT,
                           "Invalid WWPN Initiator: %s" % wwpn)
        return None


@default_property('id', doc="Unique instance identifier")
@default_property('port_type', doc="Target port type")
@default_property('service_address', doc="Target port service address")
@default_property('network_address', doc="Target port network address")
@default_property('physical_address', doc="Target port physical address")
@default_property('physical_name', doc="Target port physical port name")
@default_property('system_id', doc="System identifier")
@default_property('plugin_data', doc="Plugin private data")
class TargetPort(IData):
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

    TYPE_OTHER = 1
    TYPE_FC = 2
    TYPE_FCOE = 3
    TYPE_ISCSI = 4

    def __init__(self, _id, _port_type, _service_address,
                 _network_address, _physical_address, _physical_name,
                 _system_id, _plugin_data=None):
        self._id = _id
        self._port_type = _port_type
        self._service_address = _service_address
        # service_address:
        #   The address used by upper layer like FC and iSCSI:
        #       FC and FCoE:    WWPN
        #       iSCSI:          IQN
        #   String. Lower case, split with : every two digits if WWPN.
        self._network_address = _network_address
        # network_address:
        #   The address used by network layer like FC and TCP/IP:
        #       FC/FCoE:        WWPN
        #       iSCSI:          IPv4:Port
        #                       [IPv6]:Port
        #   String. Lower case, split with : every two digits if WWPN.
        self._physical_address = _physical_address
        # physical_address:
        #   The address used by physical layer like FC-0 and MAC:
        #       FC:             WWPN
        #       FCoE:           WWPN
        #       iSCSI:          MAC
        #   String. Lower case, split with : every two digits.
        self._physical_name = _physical_name
        # physical_name
        #   The name of physical port. Administrator could use this name to
        #   locate the port on storage system.
        #   String.
        self._system_id = _system_id
        self._plugin_data = _plugin_data


class Capabilities(IData):
    UNSUPPORTED = 0
    SUPPORTED = 1

    _NUM = 512              # Indicate the maximum capability integer

    _CAP_NUM_BEGIN = 20     # Indicate the first capability integer

    # Block operations
    VOLUMES = 20
    VOLUME_CREATE = 21
    VOLUME_RESIZE = 22

    VOLUME_REPLICATE = 23
    VOLUME_REPLICATE_CLONE = 24
    VOLUME_REPLICATE_COPY = 25
    VOLUME_REPLICATE_MIRROR_ASYNC = 26
    VOLUME_REPLICATE_MIRROR_SYNC = 27

    VOLUME_COPY_RANGE_BLOCK_SIZE = 28
    VOLUME_COPY_RANGE = 29
    VOLUME_COPY_RANGE_CLONE = 30
    VOLUME_COPY_RANGE_COPY = 31

    VOLUME_DELETE = 33

    VOLUME_ENABLE = 34
    VOLUME_DISABLE = 35

    VOLUME_MASK = 36
    VOLUME_UNMASK = 37
    ACCESS_GROUPS = 38
    ACCESS_GROUP_CREATE_WWPN = 39
    ACCESS_GROUP_DELETE = 40
    ACCESS_GROUP_INITIATOR_ADD_WWPN = 41
    # For empty access group, this indicate it can add WWPN into it.
    ACCESS_GROUP_INITIATOR_DELETE = 42

    VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
    ACCESS_GROUPS_GRANTED_TO_VOLUME = 44

    VOLUME_CHILD_DEPENDENCY = 45
    VOLUME_CHILD_DEPENDENCY_RM = 46

    ACCESS_GROUP_CREATE_ISCSI_IQN = 47
    ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN = 48
    # For empty access group, this indicate it can add iSCSI IQN into it.

    VOLUME_ISCSI_CHAP_AUTHENTICATION = 53

    VOLUME_RAID_INFO = 54

    VOLUME_THIN = 55

    BATTERIES = 56

    VOLUME_CACHE_INFO = 57
    VOLUME_PHYSICAL_DISK_CACHE_UPDATE = 58
    VOLUME_PHYSICAL_DISK_CACHE_UPDATE_SYSTEM_LEVEL = 59
    VOLUME_WRITE_CACHE_POLICY_UPDATE_WRITE_BACK = 60
    VOLUME_WRITE_CACHE_POLICY_UPDATE_AUTO = 61
    VOLUME_WRITE_CACHE_POLICY_UPDATE_WRITE_THROUGH = 62
    VOLUME_WRITE_CACHE_POLICY_UPDATE_IMPACT_READ = 63
    VOLUME_WRITE_CACHE_POLICY_UPDATE_WB_IMPACT_OTHER = 64
    VOLUME_READ_CACHE_POLICY_UPDATE = 65
    VOLUME_READ_CACHE_POLICY_UPDATE_IMPACT_WRITE = 66

    # File system
    FS = 100
    FS_DELETE = 101
    FS_RESIZE = 102
    FS_CREATE = 103
    FS_CLONE = 104
    FILE_CLONE = 105
    FS_SNAPSHOTS = 106
    FS_SNAPSHOT_CREATE = 107
    FS_SNAPSHOT_DELETE = 109
    FS_SNAPSHOT_RESTORE = 110
    FS_SNAPSHOT_RESTORE_SPECIFIC_FILES = 111
    FS_CHILD_DEPENDENCY = 112
    FS_CHILD_DEPENDENCY_RM = 113
    FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES = 114

    # NFS
    EXPORT_AUTH = 120
    EXPORTS = 121
    EXPORT_FS = 122
    EXPORT_REMOVE = 123
    EXPORT_CUSTOM_PATH = 124

    SYS_READ_CACHE_PCT_UPDATE = 158
    SYS_READ_CACHE_PCT_GET = 159
    SYS_FW_VERSION_GET = 160
    SYS_MODE_GET = 161
    DISK_LOCATION = 163
    DISK_RPM = 164
    DISK_LINK_TYPE = 165
    VOLUME_LED = 171

    POOLS_QUICK_SEARCH = 210
    VOLUMES_QUICK_SEARCH = 211
    DISKS_QUICK_SEARCH = 212
    ACCESS_GROUPS_QUICK_SEARCH = 213
    FS_QUICK_SEARCH = 214
    NFS_EXPORTS_QUICK_SEARCH = 215
    TARGET_PORTS = 216
    TARGET_PORTS_QUICK_SEARCH = 217

    DISKS = 220
    POOL_MEMBER_INFO = 221
    VOLUME_RAID_CREATE = 222
    DISK_VPD83_GET = 223

    def _to_dict(self):
        return {'class': self.__class__.__name__,
                'cap': ''.join(['%02x' % b for b in self._cap])}

    def __init__(self, _cap=None):
        if _cap is not None:
            self._cap = bytearray(binascii.unhexlify(_cap))
        else:
            self._cap = bytearray(Capabilities._NUM)

    def supported(self, capability):
        return self.get(capability) == Capabilities.SUPPORTED

    def get(self, capability):
        if capability >= len(self._cap):
            return Capabilities.UNSUPPORTED
        return self._cap[capability]

    @staticmethod
    def _lsm_cap_to_str_dict():
        """
        Return a dict containing all valid capability:
            integer => string name
        """
        lsm_cap_to_str_conv = dict()
        for c_str, c_int in list(Capabilities.__dict__.items()):
            if isinstance(c_str, six.string_types) and type(c_int) == int and \
                    c_str[0] != '_' and \
                    Capabilities._CAP_NUM_BEGIN <= c_int <= Capabilities._NUM:
                lsm_cap_to_str_conv[c_int] = c_str
        return lsm_cap_to_str_conv

    def get_supported(self, all_cap=False):
        """
        Returns a hash of the supported capabilities in the form
        constant, name
        """
        all_caps = Capabilities._lsm_cap_to_str_dict()

        if all_cap:
            return all_caps

        rc = {}
        for i in list(all_caps.keys()):
            if self._cap[i] == Capabilities.SUPPORTED:
                if i in all_caps:
                    rc[i] = all_caps[i]
        return rc

    def set(self, capability, value=SUPPORTED):
        self._cap[capability] = value

    def enable_all(self):
        for i in range(len(self._cap)):
            self._cap[i] = Capabilities.SUPPORTED


@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('type', doc="Cache hardware type")
@default_property('status', doc='Battery status')
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Private plugin data")
class Battery(IData):
    SUPPORTED_SEARCH_KEYS = ['id', 'system_id']

    TYPE_UNKNOWN = 1
    TYPE_OTHER = 2
    TYPE_CHEMICAL = 3
    TYPE_CAPACITOR = 4

    STATUS_UNKNOWN = 1 << 0
    STATUS_OTHER = 1 << 1
    STATUS_OK = 1 << 2
    STATUS_DISCHARGING = 1 << 3
    STATUS_CHARGING = 1 << 4
    STATUS_LEARNING = 1 << 5
    STATUS_DEGRADED = 1 << 6
    STATUS_ERROR = 1 << 7

    def __init__(self, _id, _name, _type, _status, _system_id,
                 _plugin_data=None):
        self._id = _id
        self._name = _name
        self._type = _type
        self._status = _status
        self._system_id = _system_id
        self._plugin_data = _plugin_data


if __name__ == '__main__':
    # TODO Need some unit tests that encode/decode all the types with nested
    pass