diff -Naur a/config/test_config.yaml b/config/test_config.yaml --- a/config/test_config.yaml 2022-09-21 14:21:00.528533970 +0800 +++ b/config/test_config.yaml 2022-09-21 14:14:27.680533970 +0800 @@ -2,8 +2,8 @@ # needs to be enclosed in single quotation marks #disk: fc\raid\disk testcase need configuration, The disk to be tested, such as sda; Or all, test all qualified disks. #if_rdma: ethernet testcase need configuration, N means to test according to ordinary network card, y means to test according to RDMA card -#server_ip: ethernet\infiniband testcase need configuration, If the server port is modified, you need to add the port number after the IP address - +#server_ip: ethernet\infiniband testcase need configuration. If the server port is modified, need to add the port number after the IP address. eg: 2.2.2.4:8090. +#clent_ip: ethernet\infiniband testcase need configuration. IP to be configured for the client port fc: fc1: device: '0000:03:00.0' @@ -20,20 +20,39 @@ disk: sdb disk: all ethernet: + # IP has been manually configured, get server IP. eth1: device: enp125s0f0 if_rdma: N - server_ip: 127.0.0.1 + client_ip: + server_ip: 2.2.2.4 + # Configure the IP obtained here. eth2: device: enp125s0f1 if_rdma: N - server_ip: 127.0.0.1:8090 + client_ip: 2.2.2.3 + server_ip: 2.2.2.4 + # The program automatically generates an IP address for IP configuration + eth3: + device: enp125s0f2 + if_rdma: y + client_ip: + server_ip: infiniband: + # IP has been manually configured, get server IP. ib1: device: ibp1s0 - server_ip: 127.0.0.1 + client_ip: + server_ip: 2.2.2.4 + # Configure the IP obtained here. ib2: device: ibp1s0 - server_ip: 127.0.0.1:8090 + client_ip: 2.2.2.3 + server_ip: 2.2.2.4:8090 + # The program automatically generates an IP address for IP configuration + ib3: + device: ibp1s0 + client_ip: + server_ip: diff -Naur a/docs/design_docs/dev_design.md b/docs/design_docs/dev_design.md --- a/docs/design_docs/dev_design.md 2022-09-21 14:21:00.532533970 +0800 +++ b/docs/design_docs/dev_design.md 2022-09-21 14:14:27.684533970 +0800 @@ -29,9 +29,8 @@ | tar | 打包、解压测试日志 | 可使用 `dnf/yum` 进行安装 | | qperf | 用于服务端网络自测 | 可使用 `dnf/yum` 进行安装 | | psmisc | 提供进程管理服务 | 可使用 `dnf/yum` 进行安装 | -| Flask | 提供web访问的http协议的框架,v2.1.2 及以上版本 | 可使用 `pip3` 进行安装 | -| Flask-bootstrap | 提供Web页面美化,v3.3.4.1 及以上版本 | 可使用 `pip3` 进行安装 | -| uwsgi | 提供wsgi协议的服务器,v2.0.20 及以上版本 | 可使用 `pip3` 进行安装 | +| python3-flask | 提供web访问的http协议的框架 | 可使用 `dnf/yum` 进行安装 | +| python3-uWSGI | 提供wsgi协议的服务器 | 可使用 `dnf/yum` 进行安装 | #### 1.2.3 客户端测试项依赖组件 @@ -39,8 +38,8 @@ | --------- | ------ | --------- | | acpi | acpica-tools | acpi 测试 | | cdrom | dvd+rw-tools | cd/dvd 测试 | +| | cdrkit | | | | genisoimage | | -| | wodim | | | | util-linux | | | disk/fc/raid | fio | 硬盘读写测试 | | gpu | git | gpu 测试,获取开源项目 gpu_burn/cuda_samples | @@ -65,11 +64,12 @@ | nvme | nvme-cli | nvme 测试 | | perf | perf | perf测试 | | usb | usbutils | usb 测试 | -| vgpu | qemu | vgpu测试 | +| vgpu | qemu | vgpu 测试 | | | libvirt | | | | xz | | | | util-linux | | | | expect | | +| system | policycoreutils | system 测试 | ### 1.3 License @@ -115,7 +115,7 @@ │ ├── oech-server-pre.sh 服务预执行脚本 │ ├── results/ 测试结果存放目录 │ ├── server.py 服务端主程序 -│ ├── static/ 图片存放目录 +│ ├── static/ 网页图片、样式设计文件存放目录 │ ├── templates/ 网页模板存放目录 │ ├── uwsgi.conf nginx 服务配置 │ └── uwsgi.ini uwsgi 服务配置 @@ -189,6 +189,8 @@ 停止 oech 服务:`systemctl stop oech.service` + 重启 oech 服务:`systemctl restart oech.service` + * 服务端 启动 oech-server 服务:`systemctl start oech-server.service` @@ -197,6 +199,8 @@ 停止 oech-server 服务:`systemctl stop oech-server.service` + 重启 oech-server 服务:`systemctl restart oech-server.service` + #### 3.4.3 配置文件 ##### 3.4.3.1 版本配置文件 diff -Naur a/docs/design_docs/oech_rpm_version_design.md b/docs/design_docs/oech_rpm_version_design.md --- a/docs/design_docs/oech_rpm_version_design.md 2022-09-21 14:21:00.532533970 +0800 +++ b/docs/design_docs/oech_rpm_version_design.md 2022-09-21 14:14:27.684533970 +0800 @@ -18,7 +18,7 @@ 2.1 openEuler/oec-hardware - 源码仓库里的版本号通过 Version 进行管理。在新增功能特性后,工具的版本号+1,并在SPEC中增加 changelog 说明,Version 演讲说明如下: + 源码仓库里的版本号通过 Version 进行管理。在新增功能特性后,工具的版本号+1,并在SPEC中增加 changelog 说明,Version 演进说明如下: Version: X.Y.Z diff -Naur a/docs/pictures/result-qemu.png b/docs/pictures/result-qemu.png --- a/docs/pictures/result-qemu.png 2022-09-21 14:21:00.532533970 +0800 +++ b/docs/pictures/result-qemu.png 2022-09-21 14:14:27.684533970 +0800 @@ -1,24 +1,43 @@ PNG  - IHDRr IDATxݻ$aa_x{ߋhLȄoE 3 ߂#G e'jXͪש^H;]}ٚogw 8q q q q q q q q q~nFZY~r[~d} [|c,8t7L_؟kqqhY!p /Hqjp7Mp9Tơ{b=,~?c9BZ}0~rkjq[LaZI[>ZSduxs+z8k7rl=9b:5?#?|(=Σ?=>V4C'5_}_[AUEeϡ ]zks鸥/Sx>-F~vPuzn}|q:ߖc]xk^[z#_Ƿx|>#F6W888:RiZl1~kuk+COxVƞX#^QJ1~^<6ơV}㯽n;2ki}>[lzQP{>~Gzj~zn[`mzSqKLϧEz~y}d7`[ơ^qu?r:j=+C{:=_*P!8ike-)ݮ65+y]ns_rqty\|{t쵿ktٚsFCiuۨA}4u۽?ǡoZv)=ە<֖<~ktۚˏ|>Zhcm͐U{ǷW}\=yxM/Hj_r|:/iװ~{^ۭ| ޏ4~.=<ι데ʈq~o;}^^uBs_r۞?wޏۗ]sg=sٚckys(=y[>Z8k`sD?0q8}n(!P>GoC08yUC˧7?rß?|qt~~/?[_vQ5}// }o~e .ơ/1;__/~?>ʜO[_va2C! q83`!8dC@ǡ)5<kqGC-ۚxU{kgׯdmFX8tO>ǡQ=-N:^lesqgpǡL]h꺏^w:gJe9?GmPƎj>>l㇚Sכ:emkxuC`ׯtyhvquy[}yq]keC{j.+ơYnxmkc=ʨqct^cOơ-PZ2[h,޶8tF5#[=ǡ%!]6jCیCQy~mCn0q ڷ~Qz_cl9:֎)P5X5]AKיqn?xFn)m(ޖGq/mhrױeZ}6wc-{׸} 7qH!8dC@0qf2C! q8z(sWi .ơӿܿÇ3w?߿ᗭ/ŷ?޿ __}o]TC_ӿܿ𧸾Oo~0qcfffffffV5}I$I$Ij8k23333333333333333333333333333333333333333333333333336nFg<c\rJ6|xT_/~>돸_#Cơc83qөۯ=I:guy[ R^5#OwKO[s{Iqoz3IrNyZN5QPwlxz׭V^w8TCtq$I=WmxRGC[^孯s9=]Xd֜>s{Iqhfk6I$Ku׌9Kw\suǡ菱_z;%Iqh!It澃c8{kǔ=suqǔ$I/~u{cn8k^ϑ~[Z/͞$ۖVq瘒$I#{!{Jǫ JߙQ:׼ƭxZ?ry_"IoqH$I$I8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$ICooo$I$IPC!^q q q q q q q q q q q q ذqvMQ-gǷ=nZ~*C-oFvD~*v>w֔2];\89?}qQeji}1.7@ ?R\!9Tݚ˖.7@#g?H^qxeP:0ʌC Q9ZC+8Tk._{S "\@>}}y|8^|YMqacfffffffffffV5I$I$B %I$ItC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -8$IAmmS-]gM_Z$))$IZ͍7koj|Z:Dy$u8W.IO~gHx~Q{ݹ_gjo-}=?I}ZW#Nij=$IҨzơ]oqhFXV'IRRCZul: I^afjz9T5r'I|C' ''5)TZ'I4ڱfn384r$)CơZƚ#7v$I=1쵏oqy$INN$Iךb9G98r’$)ǡQ'D$I#3zORǡ$IRbC(bωT$I4⼥\=WC$I$ItC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -n8&I$I 5t5 n۪˷O=lqTsY.;\AyP3u-װ+;!y|p}{ U:y>oߩj8iqtS;Cs1ԵcNkn -Xc_XއqӮҷW/\oCZȹ 0܏GXY|9ǩQǿ9Oqnd0b;n?`i8tןL#u;=6}[ -h;~Xcqk033333333333$I$ItC$I$IP!I$IC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$8t&ɍ:n8so$I9[=eDzs$J2}M{LL]I$IYsZƛczes$IJ܉Q黍/:c:vw8͝-Ϗ:ّ$՞onf0i9zKzU{k,>$I)v9XFۗXzKM$_Kǡ=^=u\Zuk$IgP֞\3*M] $I׫ܢvZ?w5#Wk1⹶Es1Iҫu84unCsϻ$J{4jRY{/msSs9\qhicΑFg͍7o}{;jC-8nz)ݾnV3޶.7SCgT3\, [ܾ1y<5joxc|Cơ_xiwz1՞Cn18ơ EYm59xZshzskSC-k1^Zg1<^F!gdjqhtQƖ./ݾc\QWqy-1tߧ}`ץtM7KJ)=Pj_{Xsr W1|Hc8/>Jƀs2@Jƀ}c+2333333333$I$ItC$I$IP!I$IC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$8t>SH$t^$P:$IϹ$I*uqt$I{>Y;w9s/IT8$I4Z/ome$3I$ jyqH$qH$iP{XY?*K$2I$ jw |Pcr%I2I$ 8$I)!''$mR?s/I;|jyI37l޹$IznqH$I$I8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$ICooo$I$IPC!^q q q q q q q q q q q q ذqv|6>~y}_I$I$B xM!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`!`Cơ(#eZuVy~pmT-9{E|5pMlC''>y|rueD˱S:nk֯掹 c0nP|۶^rygTq@->_>Z]ơ51F'p=_l~Fz1mn9\>7kuY|+=kiZSF~\\!xMWs{NX4u2k:Z3Uؚ _ 땎2:U_s|^ -^á u]tQY{ZZ3zssk>(S5'_K׼}겞Of>ki^Z.kMmF#*CKafffffffffffV5I$I$B$I$I:K$I$B$I$I3I$I$g$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -8$I$IKC I$I{wl-鹎kR$I$%V;}zn)gptng-]%I$))㐤5>͍As?_VT$Inq:TO$I$I |OZ/֐Z6hqH$Iڪkơcw8a!I$Im-COȉIК8$I$If*?H-v8T2 $I$-Wѯv%I$܈q~G$Cơތ?$I$Ic3I$I$g$I$I -R$I$If$I$I -8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$g$I$I -n8&I$I 5t56nc6tn_gffvtcnN_Md(:p-!`!`!`!`!`!`!`!`!`!`!`!`Uۛ$I$I.q]$I$I8$I$IqH$I$)8$I$IRp!I$IC$I$I$I$I3I$I$W̮Ō)IENDB` \ No newline at end of file + IHDR+sRGBgAMA a pHYs%%IR$YIDATx^o_W:c $H?|$)Ձl4,-7er,), Y64OrBAnX6 66M7 +z5s]kf|ٱ+!=ό\_(hJ +F0hJzCCC$I$It3$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"JI$I$IȰR$I$IR#2$I$IԈ +%I$I$5"ʕ6V|\j`7ۿ ^$I$ +v+om]t|[u_(^ҟ.c+ܹsf/T^OWKA^wvA5I$I$]axJO.6xugҝ骩<ȫcrsm4=JI$IԤaeҏBr[a-ě]A骩ְ2C+ΰOƛ/={$I$IJuM +&nɘu$Ȱ2 {^?_oW<] +mև~)&ذ/-^$I$vW2- SގY9Ik*æܰRVyWuykwmyذrJI$I]uʷg +39|.pb9O U^GWyCC׳)_'PuϺg^ae$V[?g cI$ITXd\L' +or<қq)}{LPk$}x:|pR->,?W*_ ϝ|5rka νR|5⏱t,^}3 SYip\u%I$Itÿ+8tsn[Nz*&Y~ŰYûKSA҆o:>ޅՕiAy|5NzNssT86bX9нT$I$I?gb(Nt_t`rxV/~~86l\>fn@kxs|ƛ]uJutss'pZzq8+v@NǏ_jO/1bODB -|~jL:q׆$I$^ưrxtc}? +|&ッ7Ǔc_Nْn-]:YK5|U47L|'][&x$EQ~:ǁ\aeUǬr^um)I$IͮaPda0b.6_/?w7KV--JC%] +N/=垓|BC^jų֧͝W+sq}0ع'$I$IC̰rw _n_ư/]~׃9aePW:~'Dzfz]+^y x~oL+⾮Bs8?*I$ITVM:⮸o;XdX +u +媿g険J'GJ^]'ɩsz,xkRU'I$I54}xbO3hXY>owr|!?^V^ cSw3w+怦mw˗8Nw^wVg'-9]8$I$IRu԰r'U V~4|(xVaYNIs?gUܥu۽Oƫ3Ǹ,?ƷwuV:<¥ +k~|.vӛO$I$I=g{3)l6pXuYi8xnM~ϾjKgNr5w˗~5Zu}u^rי𓟇pisq.O{O0ֽua$I$IZklXT<=L4,1}q8rn.mA|?Qxx[LW ?89/%{f??>}??~"~qY~vӖ8J'}pxݷk?{ݦk{28݃/ŗ +I$ITյ7_.O v +o_h.N.w +%UWSϖ߲5CgfKBsf.qX9tg5TZ!_U8$I$I58 +Û'+n~fy㧾\V^V&}gҽwN_/n{>]M_oxaPuxdWo}aϫg'O^9tw0$I$I +s[{:ޠT[>=}=}7yE^Smrrx򟞉{0[?Wy\b7?O>ϭ}xzn?3ݽc/A_$I$f$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$VJ$I$IjD$I$Ia$I$IFdX)I$I$ +J +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ +F0hJ*I\Quvdp:OOB?s}_Ϯ@z3}H Ց ,3ȮwCkZ]C5zmye.EĹw;w.,89~v}`88YsTlŶ3q엿>J81]?z?kA<5}{3^vĩka%UOѣ1RXl;?>`bܕޅN'+ci?#ߌ3Mܣqj' ??$W^z ^Gc^.(sUuw"{z}%^00Gv/<'C?1^B8w}^/+fb6%=6bx:wALa.^ha? []!Ll*Fv?'{wX= Pg3poV<V^gY/7IXZw_ -+/jVwb?-եˏTI[O,& W^PU sG_ؽ)+Sog^~?J,yVv\cŦqOT;w2=Ko<GBk[@gg|zÕߗNQ?Xm5O~Sn= ."|e/ +cϳޙ[ +86ır=0]}To8Qy'Z]]jL'=I.JubHS;+~g}aLx͛Ӯݷǡm\{ObnGf#1=w >y?x`n\Ocg{0ɞG}}-u7z:vhծcO{caq-aXX<޾voo:\_ؚsa˫jqzWƑ__ l叾?=/Q]r6\{O~enU`oOS~c.M+WeXYY]{2[~MQ8Tl*~{+t ~9#;#U6؉,Gv&cvIͣ?^W}}-u,$vIE=~O;-^tɪ?z^Wđ +Nj#q l[vAmձ0~({tO_wV8X7WDoߋmfÐ谲8g^;+QgHt~'rl}tҮss둞f}Ͽ˥ޟvTet|,.\͵ڥ\x8صM{2ܿ}ǒn.[ae0pu\䱥J>b^%Xmusqx6(mR>% +]_~=39 {/:URJuvz҇]7(ӹ.~|hE~&J'c:&jyz*NaUկ灿[VbgfpYC[l, 뼾R^'Nf-;>ZgǬ}M_͵%V[Ұrɿ.F+Gy5>H]<w//2 +X^H*//rѶ mw8n`:,!erqw!f)_c8Rulخ.)97sYv wMdgX> D۠w x6 n3f>]+|-V@E}.i.-_\o`WwXY縃޹bGęxse*KHy}^4LfX"3s +|6\]k% +[h+; +NnnucB0Ŗӱ~RxemU3"ӳ?y Jk;m#kuV<M&#vl#f;+ҟgRgk]i;ݯw w]lw^[a6V ?{WnX7qpɰre|}/(ou(ŮW1_{kt۟|Ǟ, {*}M_aebY;VJ+k7yʷ"+ߏ4\>?J[QnH[⩷X|؂Ew7ާZ[z?'?'_y%Z)~N{cn6=uk.ԒwX-sg8˖@4rXoOcz8Q\<7 UzJ܅' 1%?ũֿOk>O̙wNlsswqc|6kYo.:g9ly~JIcsw'k}{R[k5t%~wfٟ|V_4׿ٵk'mU%1">]LKdzfOopi +}ܲ;K[٪KfXIM,d IQ%+ޏ'[T~_9JVa%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#V`X 4a%p;>rZ羿\= +:q3z +JV҇o>G_r[s6([\n˰aejmuO?tٕ8)8x;Mk ئ},߼#ͮ0={Ls~/[Sk|u<_d`V.J +lWTv;/5m8;Tx%߮J}jWm)+˟ğî4'~b'=RtGikYS[w8EOmˮR|^: υa%aX šw=;?Y]x9vl=:O_?#^guؾ 8xy[>T!>=,(> 8L~8ζ>T, }' w>G^N\UcX +1T;}m*`b5xaw?ͮ_QV .\xX)p‰vҁWfV&~|/ D }j릮2ӱv5^t9GL(e"ʘ/mM% +[>}o֔=]WzX_wğ[=`7k+ssCv V^]8Sm5c]'fR{އ$I +:S_n?0_qyw珍pruo5.[~y 6__$IWycXvxL^=.n󇷶mݻb331_ql;}N& +%IziۡEb]ykq]jVJtF=rZ;,Nr¸ ÙӅN+8gVJ [o*.ؚ͡n]U˰R6߃Gw9 _//_ +%IRwk;[yyZzxawkq]jVJtF=2Ѫ]^nJ3}o/n7tʼn8VMmO|̾ٵ5woL-\w0tYv?챽1_f4Ljyl<7[oi_7|uErWc/{xv+.kX'hLܞpl|?\x,C#1ZI9y"ynoJ^w_+$IZ*t=}޽u[]=>fGc~dLJKCo uDyɺ䶭1qx_(\/_MW7+ Cpa5rk,ae֐Ton:QٙkXA$Iǰ2Yԝuv9y8Y޼'T=o/wպp:^wE ugb+[be +b,y#Ov>оk@h$>psn 9ѾË_$\qh[w:;bC{tK^]H;}ds!fz{'ɎY<>:Hݛ#Zk͝7GlM7vXF;Y'e_sg}x`kdۡ8});H1=~šwϛ8wXƒ$I+V@ﺗw9>wŰT ['b淅KJpxwahr4&^/.Cc4-,0̪ ԾKG:,-O>;[Ou>yc4[ְ2y|{oݗdk 1_kx뷾_KV~&-#g? :c1|>򳙗hIM~ <8ZZt5t8u9Kijxs۵i>LװS zkz;wT$-+1l-Rvr^l U +gƆ[GUC|a/%3lVfXycM#ݻ8gv`ʳp[J觓z법Vn==t뉪Px. $IRevʿW"|̅7~ۻ8^kuc&~z4FNk|MWfǫm +_ccX֫ȭbWyh+I.91uewB;31ʁ؛~bXs<{PJ+l^^uݛ]K}}s{wL]ʮU$钫ڂ2r]Wd]VZ2;=gOoa +s{Xܪ0|d;5paezݟG]~>]|T5zCgu6ݝ>qx$i|PY{aA]=<;'6wž0bX0,jq8֌ϗY1LJ++u|IZ~|7݋Wf]mIyCH߸$?;Vf{l `T1/=k  kk|=X\^|T5f$JE=Efnr~GxpEd:[c8ϏMThtKt=ى881TmY9`XY{Uuޒ$5D;eO벴t0Ul4;Å]c5owo:++wa_5% +mY)Iҕz + |WJZ.ۃ>$Jw拋e ++nN+ۨC瘕¾tt[7gNJ30dw5̏Ty{f4ƞwx$I=8lVb]*[l~n.l==quҝ#[{P=ְ2]ۤCA=d.yXpcuk3NX(IJG=EfѶwCq+Tv&҆ng'ۏrC=pVkk}G5l|?wK옿={[%ΰ2c>3™*Gۻv>nX)IRU_?ҭ,4/i%eNǾۓ> 98d밡.za>nG+-7{ޔδf:~yV`X9cyg ƒ$I+Vv-2][ֈnwމց'cßm/KVvКtѿ2:hn~}pxLc9fg:#o`nd!gv7YCơ5c'[lV~tdMىvғ9v$5։vnn;N벼߭^V |qCKʠ5]3Dl=Y%mW0&]ìİ2tc^KO:{ir+kIǰLd۶C)V/N-%2=ɘ ϊ1/uX4֡hk:qϽbf>->l-O8.x=|+cyfoJ$IVu[9>V~ +%Ij\.ŭՇ[uY|(Y>Mgw3Twm˷d}~|]k ۛKа2̾|ʾVJI.=n$IR#J7$IxcX)I$]Nu~2 ItF=$Iҕ*=^'ي$I5VJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$IVJ$I$I\w}W$I$IjlsM +a%@#V`X 4a%@#V`X 4a%@#V`X 4a%@#Vp+ߘnm_Vsqps8&c6Ȫx._fcr1X_-6Lv11ZCxi亷=trk'ֵ{m3%x;gz.<% ϝ~=|cuppWt}}B +;I_r6vk<.cX9|SZ}gH&g">|$/< X1sGַHÿ.riUV&t1Kg-|}ʇGb[FPuwkW^'>NrZ]gѺ'DV%vkl]ae~|&ޢwӳWӊ ++|};~2mA<_<;\aU0̖2~|bfX ם|A|m9J}ؑ -]Vn:+PGO}`ɮaek ʥf,aFPwI?H{3=w,&F?k~ݾ<=;#Kq>;DMSmv;On?;ջwYXmcqml縉kjԿ&3`X9çb +YwW&gcj~{GlZӾx^ّ';nPmyX9ݝhS1KRdxک0\Jd3}4Lz-,6yyc/XK;NđGƛ[f '\q͐&ȱG64b so'k/-wK Vcg8}rP|`[`im{V_sjIpOkccy=6CC6/sϝc_\x<*N=XgNes=kM;]oc;b;R?t?mSlJ#_[9^~6voh/PGw-AK3ɂuHLnk2vd/aXY-f-c`Ώyn6֦ؑ^їq+qp1fm*>yҕc|/v[үcM&xXy#[5T\wwvZe:G{zձʿCl][[ea. 1>Ov1{euG)BצM֚f=*$_Oֱ ;#qղ|>̟ݟ=rvdkl]lxKV.8V'kcV+;ߓ|Mz_8ϫz6oliv3Gb[kxnİ#&&k{BʾG[%%ߟmݓuq^>?op2V~|1;q3 -],wOJqqcKrMdYTy7ꅁޥ +ݰ?2nvgp92w88vVft[Zz&mא?Cc%Wh|ceMt~?`ݎ8R%ݮ+o%nݳ.Koz]ZqΠ.zPt|Z<6\sW98Lgx3$\x*ֶV{C{2hq%\T>[tXwQ<ǧ։zɘznp51\gј/z=_狛|1RuL|AawHL~&KV&.V}ͽ@li$Yf({KD~wf[LOn}\gW3`7kww~nݹ+#ٖ s]`LoLHe x*=ty8RVב߬ȶ|ֳ"UkΊ1a5mIpl:c}Sqt>>Kucf9 ~q\ۨz _|]ݣz l8vU>J + U寋k/oOsb٘~`<5#6ǘZWVվ_Oo6[*}bZV.{߅¢׽[K}?V.iѲDyw1t]bz~g҃sb9{iX/¥ݖ3=|/ ΪHfɚ&ݢ_vGd<F.-<;8Mc Z:|]X}}!}e')ڛj4h g]w lE^Um^s|Nk>e׺rXإq;\ +BaEw+_vٷ̼?N[5;&'̇\A@׷EYs{P`5e[懤rw7ۻ^+Dz=>~_~x25;KxkAk>c5X{z԰rEfԯ5hE]eoYu%q;\ +BaǬ:MD1G=ުշ_\$e/K]FFdV&N|3-Yzܢ"EYsۭ\зLv}֗]qU|s7qI[m;:[Ofo'봃uSi}nq&kڻW좝jm!:x瘄wokz=UF84zTosWR<]ȥb5nǠa]($ढ़ Ln ˻.u{`e7tSC|W*{`o="=zs1#h'ׅS: bn=MZ1{SKU&7=stXipp6vqd/7{KoϽtŰrEV5zR׺y^ijWc =JV.{aefCX8#Izv-6] ǦdaڵD@k4kc#vfiK'۔plv->>؎,>7G[1ܽxȰ2/KwOe}{V&pgxFb}׸pCkő_0LJ;z/T+x;%aHIϖe={tve.ғ7Ŧ ztCq'_% c6|fj4oZ'ԯ%4FMO!}԰xa-G 'ktZ[9ZdO~OTx..q2*eX_b;4]ndy϶{NđGa8OF~DvY Ӂ'[,>l縉.?c19[bg1L仃w_/Y0Ǔ,7k tXae˯uuRGĉ_+pI+m9w(Z듞- Gdp8]z.]+oI;},N%w~|!a۹6no:l]m Brc#_}vʟ碴Z.RON_=rU3ˠ`ߘ'YkUtZ=J]JQ +F0hJ +F0hJ +F0hJ +F0hJa+I$I$561$I$IV9&}$I$IبǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZǰR$I$IZ:VPla;^yj7PX7ǁ_V_.IԘ^hn3+.:6?wrIk1>҅C1_uyqtX.UugX)Iae8_u$JIt=F=ss~Sqyot}8]u$I* +FcVJ1nYyEBzw$Iˆ։m +%I\'Ytӱt8Z<҅11vSC7{Ty7_ͭE8X{Cr5]ǡ>ڷqhOǶƆGG̾ٵcpvt}I喭;}wwAʳϏH^IukL>;, haMվ]9{k˒4ۿ5Fu%IV8~ǡmBu,}!]~}jaۙ5[߾=&v]nn/xo^/ +7>exXrvZÉU.Sւ|8~8n/Gc8?x"FFbMGZyb%IjVĮֺ6IVoo>vP1V{=tLZ A䱵LCk÷T$i%xXp Oc3,=NX^Ggx/aekNIgO1j=% +y5©>[b~,쏆?CX$IVV&}~fWUtq}q880Sg[i>~y~ҡ$IV8깾I[P.lq9V&C۷w.wo{H.?V>4py<2| q8[@.+Ͽ:|AqwW1JIU-*vV}N0`.fk|=^/_wX^O%k{+pI[)|J$\s+?:/ƒp>Kwz8J++|XٹR=_\8x|X{ޔUMuUAIJIY[w]DzyaeyM;\rvߖ$IE=vnt,]'IUǷlaeޅqzhn]-}4$Iހv?b[V.eX8$I31L?5Y8^R>|,DzPztEwax 1HarYiX)IVAkPrYYoXouNcñxw$IJG=ix@:,,p|閏ñB7=dk]8 -+;؛3)3Xϰ_ +%IҪ=[ +?͋FJTMemn|d$I+VfNspuf*[[ c"=(cm>onn1 |8Y/N2CuMXO;r48 +%I"z7bCkFco3=q3\2ix6({'Z'{mv[ې$IZǰ2ovO딻p6=upN_Cow_ocVdzxklȏS΁׻ʴݟ,woLgX)I.Wuw$[ceV7}ҡÇx%IV>1$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV9&$I$IبVW?J +F0hJ +F0hJ +F0hJ +F02uT{vgl۰6b(im|{\v>.Y4ߘˮ@='-կƖ6>pz:oS5ֺ}>_Z#<2zWt}]ʼnwƦ]FNKs8ޱ#at=|Y9xf:oה42>cٕKr+^٢; _{9T-mkHؘVX> @cCtZ >iа>[no2YNƎzut2fK3szƵe<]-v5׶sq/12#&=ٛ9¶ڻ>'zV@4*cLK7NȮ/ɮ ,u=,loW|D=G%Wѹd.Jc3'\ylm t7OdI\}+8xOKr>#-N3v3N8ҳ5\ܜ\~*SLqjD {>^%gG縉7m<]P-߰2= m ٩)8g?nvVu3q8퍱~^c2}| +/⛷$yKRVvvqc=<;7q(·yC1|3D9[Z1PgO'DY.ƩgCⰩ=L*ciw˅ բu .}XyM'ߟ!fqU2$Cj1D,v=ﻻ듭z85J^u:L\|멸srt 8Sa+:V&"jaY-_v +3;Ƿu<-}wחX*{r+ޭ-[ه +< :\q8Yۥy[Չۗui=^NyR}xc-zfݴx.}}Zg N[<+˫'bt|xr]aeԫc痊gO<ӖpɮaA*8ÃMٱj.foX*fvL4KDܒ\҅o[J[KՇ4\ڠv.-`^Nָp5vnQ!o9vgsٮ[sI[Vv_cW2,S1`uѓwr%->~^ h"۳<7CZ@V $sV.Vbd6xn.llk>xi Y?׵v?/;"ߚ+1,:\{:eChN^Z:x~gr]p[dV/ݲՃl[!8Vs +tU +Ǐn-X[o<ν3֧MĿ)Y×r6T +gL T{t`vXgW_lWNj<5L z\jwj>fC1+fivbuq:v8x*/ﰲb?6z֬MO_2m'r]|c=w^^Lȇه:-%oLur-cWniq~bի7̟[b#-}9bX V&: 5b;}{ƙplzt:t2qLL?(gZu)g/C鬛~"Mt*d10.ϰ0D]SOoo2qn\Zsvsae~;C`y^s/h_W~ :s{<9[dV䑼?<߫3ӱ sDlj bdt[;~|uǮtf8>ggܰIްF~6;Ka]5%;kr8m}ML7O23ƍd+7-隭x5r <ۅQa>hMg]<<;bSkC->̷PMʯS]oК*=ʏwRZl\5fM~;cMrqz0Zrmi#>q~-V& 1Yl[яbؖ,T l\뒵w].]C/MƑXo,rml縉2ν}$&iStY;Uqؘh?ɗ۾Eʼndg-:kO<<[2b巗id7LeM0/D>r$N<ν?v~!`=|~v3g}{gae׳qm]'_]xaX VpV˰+^\2hJ +F0hJ +F0hJ +F0V$I$IRcJI$I$ikbXGI$I$Iz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUz +%I$IUzaC149_CL$I'ktU2^<o+.Thg8~v{:[}~]ύMǻf4>|(N⺋ulL}k{mnc{b#ް6Sgۯ'D98 i%Kwcu 2v~fWom=g]í'[]r[ctMzC1|XlTPqr;EǰҰR$iŚٝʆrNjKz]8n + 7$wŮXk2;]9C۷]1~w6u"f2c^cZ ߴ9ƓN;PݐU/^_sFbމص{"^l=+>y,ο: Ъ+Oĭh={Nvǝ٠p4Yw?Pr79M~n1o}ퟕA 'cI2˰ҰR$i:N#7슙˗LgV9̇!aogNj)|O@<:?uA|4yñA[~[늷ξK>V|1TW .2jJmK׆[P`t6L:ο`ۡ^68}cbl_ODW]﫞فE'+X][o2:D-@l*-s[I굛͓<7ܹbAhO8GJN!pl=IʢHl}/bc"qZ^h]>1u6>m&+ɂ",Yh[8̠cw_02$IWĝzk唯kl}'w4UʓOg̩:ŶzcaXzjjm~&bb!m~|}x=_uy56,nΫO÷Md; 't7t8u]j+y}]jAњ/ޙ߆N};?nM HR'1Z8m;-dnKw3~wr?e?^zc <3EdKrY~gnXH+]6C!2NC1hаr(deYG_gd;;U8I vol铽j'taby,]Ge>/C5;ezK͝mVqIqsc>{M.uXYmzC] ̧s=1LݚNo)ԉz +_AhBimqW[u/;Bn8Z^,3$߭>GLӺ~&)b$ +oU-c)'z2;FfߡQɚB3~l[gwΘx8ݪ v- Abt ƾsLl0R1?g nOos^ht/|huܺH9>[؜mZu xxHR'1|2)ew%+vUbUʒ]Yd:QR;$v>fvo7Ođ9{e N +o5_Ȱ+_[Gij1zDm`gSub~7 xzYdR%}"Ѯ +Wξؐ>>ό}EV&ޢĞ5?dž.lјvzԎԀsFv'?yP=S*v)ԉzap[.w3 α{EBoa-Zbn+8NEp}{ܖE$IjpRNXWߖ$Bfܚ[-ݚ9o=_OJ V$Ko|~ʕgrɇc_ꯧ^3d/OL5<8 w%fH]Kv1.°r뵳~X*HR'·iqC -7hwA,Z>L75 F"I߲u! V΀7hA,YHt~\Z%=;p4lЦ`٤3*u |s竇CC[. ܽe%/o%۲rg.aezkowWjX/nMJ3|ӆt}%Iҕ(%􆉘uZp84hX[{Z{Tv|pk9,PvAO̷+Vg:[ng ٽaM7O틱>OK~,3b՛O߿%r;raz]J'c_Dcl%KV9<R-kX=pVݕߵLӽc_{K|}h G7Ǿ;3'HR'1Z`e]0' ¢s]1~wv<5[Px+6LMLiGn;n#y,[/!` Itvw,;K )[=NbaeR>Lkd[p* Ng{nk;['bkWEzŸ< ڻǓcv䶆o3]gf^8cMcô:3t=֌ve~NWT5h_-ؚ}vhలy9?ZۣAs. go?ؚ>'ck{xv_~߷ gϡjv~vOeʶuw$u#;aϻEPRpZdEiԷǝ$]$~q"Uz"@$5ߞe,f{SNE +Ο8_͆Sީk]ǞN.eP7<=56d5|XlToaTMU<[37L[Rn-k +Jy3}} +xj&y<{,~~>Nb֭1Lk|Oo-`}/sz,wm7HR'n$I$Iz +%I$IUz +%I$IUz +%I$n8u<ξ$钢JI$I;gbXf޿KzJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$ikbXJ$I$Iza%p3hJ +F0hJ +F0hJ +F0hJalL d=%X֬_oe mt$[C/숧~h + +o?5 8sg@g=ZjK|twkoLOñ3aeaޑx`4X~DQν#5#ql6g +KV9[Z[l#fő{Ak,{V&N-ggǩ#]^v`QV}*c,cCPr_-o>>]7SNP}8d,°r3.Jν;6f?sgXaev]Snt4@lJj2\2?e >cXZ' DZ*V9{iGa1Ř}=\7/'` +8g86>jqq:vfkuT/95!VVV08/ty#;"-zCkhJ +F0hJ +F0hJ +F0hJ +F0hkbXJ$I$Iz +%I$IUzaG}$I$I$561$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV91$I$IV9~OPFn[>+>w;,}1Sy +wlLWfb-~y 6qs~mu}ezN$I0O[G#1K$]'Qa =!ݰ9_VCw/VbX9w]c7 wC,ܞa$I8pwwMc$5pzᩘPl~*&nMžSK$]GQaeae1~Ed2-vgX)IR~{y:L^;$I>A΀l{rlј==pxwYp6f;iDzkpN{:G;V.]> +njamݻ7ǚZlx*gX)IROUO XH$]Qaa}1^=quae~?VZ_* +?wScn1!vugX)I8/ڢR$)bʕV薕,sX[V#8t~>;dzM̰R g잘$IuV԰rǬЅ옓 |zC벅Af~+ȭc;/uX>m=<_yyZzuPg'9a$IoDbq%IǰrK>xz\t7<'lv fywh[]|:?Lcy?|z6ݝ~x9Oړm9{^/ ,/$.3pL,솕$5ϵ0OżA$IRWcXbʤĮ[ܚgWzpsm{8?=8qk07k]'b#h\>6n;=1c_ook c}{LK鸐3D^m||,&uNR4>6UZ'kaXgZ'DZ_It:?5O=w$I^ʕV]8Swޚް66|q"gq1&Xw*zmݴ0xtg˞Y&ןyf뱌ݻ7ʏјl=^ylw{ i?;cCq6oٻ;xy$I)_[-Ւ$zza$I$ItJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$ikbXJ$I$Iza%p3hJ +F0hJ +F0hJ +F0hJ +X%c1`e\L{Gli8bƵKqsfI.K/dfsq f}l{H}vy]NMnN%_rxqsOto٘}c;b1yߦi  cooH{8Tʮ\yn}dXHl[>Αtd~tglɆ]r :`X ;ۀۉoO.-ϝ>ؒnxΘ^l8xpr-qt3U.5,286&ǒ|TVr]5mm6vuOĉ- <#őuce7Nlؖ~]q$ŒMeÏ!} VqGO@+v|{:[8&Md{a\{SzݝZ- Z ?,;{ M=ӿ.W-s˫}l8q,Y'dͽlHa}Fg4|/ spؙ>;FF-nn>['%a RN\|W@;cȎ9&±7>Q684)պl?zV;]*{|K{Xz]}f\&O?;'Y-[ %jS/b~}baZtA~A5_y2hl22=Hݰ#oXlTXf!-cetxt֓CCB}{~[b ;MO:\7O+gy÷M~[]7oY:=ew{~h^Smy~}K>'8hǰV&c@CνB+9U s/ǎN=1[≷^]A p,}<'Ue*_3Vf/:lXپܰ`9aŘ}4={_?MRF"Fc;Ńg5ۿp|!=>w߷u-*'ۙ緵=43 l~oeJƻ~O8΅;?B1V.Vg;"s[OvalaИ++_h/vWDsql}F;̳7n|ǬfX p+ .Ν}3igd̏+VnbÏ-)E艿O:Y2gNeXa%H5o?iyoęWɇSO|x ̥ a%9|شamlf[:wmؑr8n/+1*c,% =]w[ ߴ1v݇a%H >>[}XLo΃|nTLrٍ1tˆj>a%98;^30Y׽qKyCΙ*充jav-?;X^ }K8gƖ/lJ .#1+;dǝ~t9dҎd:n}~L| _/uf%Jv7Ѝkcx5;>;{'0;+Hlo}{ֶ?Ͷ8R\_gLgLWmFtm_gdlGL'kAe8.ܩa%HoIrҽT6TԿdϤkZ|Od]}3P\Xcuug Ϗ_sȝؒ }#[{,= +cVνTB6LiS8ճ?O}w5Y9'׭98<¶n<w3=s&ۥ;5-?bOb36sl]w>}t}){3y(FF 'j0 vF0hJ +F0hJ +F0hJ +F0hJ +F&$I$IبǰR$I$IZV~G$I$IRcJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$iJI$I$i+8gC1443_<~//$IW{cfh ف8[yy~y(60W\Vz,$Iˌz +h$]OMPRNǁ5å +WH$]BcXyE$KĞ϶2\"ItQaͰR$]clᡡٶ56/s@x؞sM7lwoXްr$IJD=٘yf19_mnSVM֭1qd/ޒ:/}hmzL/|NgOoWv6T[$IZڿ߇cCG31n^0[k Nmί~VH$Ps+;nk]ـl86?w%׿}{LÆzat:YT烳&=:ahlH˚Pr݇džlP7ɮۭ_~q|ӱT~R prY'tNk`8|u4v,Nհ[O۳a1ۅNAǻsr]$f3O:oP.wXy(&[xrʕx,$I+\1d;.m8? Fc[ ?`:\gxz`,ߪ։/\3~~,(tq- +n-֓CҖ[cKb;&}>cb8_~NrkFc3o8Z*_dzaeo.I.c+3 \;JItesXyhtX޵8`_ZAh.Yq|K|Xg׵xǗP}Ob啿Pdn~4~y/9>+}r<{aZ~i+oEeAr$ImX)IG9YYԋ=+8V^I}.>DxOˤ%}~I7+^soFcs +_{V+%IjJIb&c{[_k%'ַjcljBbÞoX9wƤXR>EWkؘ}4gXt6ۿ_uoyRw0y}q2?g٘i$Iw6ݱ2lxd]6}uul8~!w8?9~vfB3XNJ+[y:rv^o2ӏέ!z&Oʴސ HlmȾRg$Iʣ+%I$I+%I$I.G9JI$I$Q\Le\$I$IՉrwEW8~|Sz$I$It'c$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IR{b<{$I$IT(+c%P J@%+J0V`*X Tc%P J@%+J0V`*XyG]o"-=:5ܦ~Beꌕw1VT2V>Jƅ?Oc&ȇ$ǟH 5_:x9N.ͅ_oubxIh9nm1L\ˎ%ىĩ˿#VGx;gckޖGc''prYzR}GbbKGg1+6ľ_t޸g^meWs&:+Ǧq生{v ocmP߾k1S|t$yߙ_'l_?+?='\s{bpvZKNgf'#1Q<~`9zxnx49䤽~2\y} %=aOdxՙ[0?k"O:ǒ1#ֵɉzf}ɉmw&ϙl2\w~|kbߖZr>\N<8a}ͮ#u|LOvlcǶm@uUj1K?mH\ϋfsC?\`ZLZ_|'\k+_ؿ?|LZbZL?kskP %c;cwh K7ztLO ''-XOd'dœkql4=|(F^o;p,oPz"90';G.oO-|c༓ѥNeDZO?yyON;ߘ}#볱2=~׏}Ο׷ϼ~gZ>K>f}LNU9ZϽtK>tk:V,kP{c'=͆ҹgY]뾖|MҷՇG`Kָ`K:+|M|Fˠ7d~20ke' 17G +nj'c?sKa),9޲d s_3eFGw U5ޕޖpƱC7䒓G;Cz/rRbxcppT kôtqqx^6?ϳ>|1{(^|q/sž&K7X9Vfi9,OO<74Ḟ^JO'/đ- _/ O?'Q2׳v7nWycekq8މ8O=/Trˑר`Ip~n +{ t[jbnq8x9}tHG^!ޒ7cOzO {e>7oY<PM9Z~^s '_|K_`fP㏪wss_gD-2`fcmCuy͑zj}̥-5YҿpN#=+0V;=8|)9¾;| Pm`8̟SiT[b+'c=#[^gt}MozsZeW_۟(Ǖ#q42ʖWq>ҝ_NVʇ__0n/X8XD\.~ܹW,;U,ܫqNOWչ'X>;fm˾)U;_c}(6&_וmϕ?V'R,`Xl:lHOSXt%'dC/i=ɾtbDHL-;\5#铐ewT禳X|C5 y} mJkޛaʸ6?n(~ʫbv50 =1QQ'Y4OϭعWrdgkm`ةﭚ9ÿ뗍űO|t~MK7zwLݸ'~2O'KDpמn?>9A[~/2Z4/W< Xl&CaҪmN?~r|zr=?;!]2ury}"mX ++,ɗcdS9;3q9OgXpϵ̯H߯9J_6ܜ{)t7+SK7"z{*X Tc]!.-\IPEʻµ8qMa@+J0V`*X Tc%P J@%+J0V`*X T=1V={V$I$Ilc$I$Ix8P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc-xʶ.{0ϻϏF+Xgb򗗲oٟ~:;ƺωNbӶ]Վi[Pű/_gEXlEx陘tb+0G`H>_$Feʴ1OiCq_ނcرvEvT5P)e+ =k{i~XGXg;_vdGd|P?tbcen{7ZKCeS8A=iN=_.ΩMSO9/3o[b;b +?۾)Yg@͕cߒ'gy_?~]<1Lsxl ߞyc"X=aS;naV{ݖǦ>cǓpRҊ♓-9> Z1\1ogW:t<@+&olYgcQ{V<7g$OIpٟ0> 319TVKπ[[mn6Vj̼#̏YeIߓ?[V|ٖ'Jr9,vpꙹړGR?\oEJš H}߿Ϭ~ߦߞX A-vx± eޱٟnޞ\ȏ^߳s0[c$0 +s?k?Jp<}?Yz5fydgσ/5μ`q;cPS|{g[^ 8vXdTxo%~O~cwwww?OM;,XYajݞcH>ŭFUbmSrOҿ'4Z_ڰ5^oc`9+Âce*rzi*zp6ų:.4V.sPf$s+KOr:WvT+?ge?uo< IzOKgɂ?:":~T|[n{ jZ@l-2Va_ +/Xؽe:b-? ]2V.N/vs|2Wvʽ<zG27LPo)56xheDDZT 7+o6Eht59woxΊG.}mzۏ΍9V6@1V.Oh{^ESK_hX[߷y?.qlv.x[_'!@Z|,={v=1`fũ.^qܟ O/>I1wxFp{O}M_rplߓ?~_yxL%?Yf, XlL毎}_2&H_ }Ej +c嗿Ej?gGcG ++Q3;+9űfKf}e$/JϽJk=vZ|z/аS^~8f~)>\z&cewy'%b0^8^}Գ/$8~y[a6o/ K+;N&o_{r9,zp5A[\lE<q=мL#- +_cǓM=[|{Exor8ZxYRvL~fʾ7tpǵvB;ikh-4VO-lM\߭F:XhT=9}X ׫ŎㅟL>E-jsOUn, t9.cr(q0ֳk"M[O-bmuͥy&8/ =AL>)(^>#&9Wz6AGd|p6+9$cݶ{@qfy闓_~A+bO?Xtt`t Kk_~mlqǽ@e%=y؛2tum!w?L]wg?5zc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%+J0VpOgϞ$I$I*c%I$IF9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+h1W}7S/$It9} ? Ͼ><]|ЅN<.~8Ͼ|{;uNv'Ijrw4c$Islg1z?cC۱[p\Ծ7>-x7./I:E9JI$I+{,_)qz\c{1~|j˸'I+%I$vV+we__wxL/=t7er\,tmX7S;m|5c$Is-9+|/Nߕc/^hʯcwaq48ٸx?IR(Gx zxLuxX9Iot+x~CrۡO2}x~5Sq?>+'˅{H>s*{[}?IBQNovowXyJTjm~x h X BՋq<{5ɍ$IRϵԱW(x ;O/37*ޤoy}~QN? /jk6ݱ{hl^m 6e-;TKZ~|49~w7{U[o㲱r/ssR$I깖4V~lnxd]~8t!ӘW q޷~NQNOΡ[c`U:@l9S)O]{Q^\!s(Nן2++{`{9ROŖ{r+%Ik)c30*gqGc5|/&g䭾$=ݱ[͍%^9\$I$I=+;c$I$Iڢcrg$I$IR[c\$I$Ijr$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IR{b<{$I$IT(X)I$I$u9ʹ'Jg*X Tc%P J@%+J0V`*X Tc%P J@%+J0V~;}}ѷk:{p_0V1V~JX6=V޸ӯMĶKGǕc艗 .|9F6Zv#1c婉uMv Eߵ5C1΅N틱\_,=`[L~&.7'o>>#cZ4}qTz6fPn;Xy|R|51ܞ3hc@\9g%ο6)1p<9n|$Vo0VmJ~c[mzᵘ~n0m4bxMv;sCc6V><8Dc[9^MnW:דߦc/xKXy~P}[D=7Gr1YϦcb1v+1XDz%ն싙e7.lj]32=#qpgfuTKGb[GMgŵADtrم׆ؿ;07=+FE{\cTtD^9a2+?єr#$ב9V^8Ҹ#|&Npz3ņ|?V>VǑ}/X ݧ7ү ;1 E]ŏh䞕ٛ=+c޳X @o笜Ȟՙ>ñ{m_n/_0XyYi7X1jjӭ/s` q& /ű6LR_G2n+8Gb=gt8V8{N5' o=VZK+l-bxMv6gFLc%wԍ 1,诏9qS^HL|k}pb /tὗc/ b3q!JzKo@e+J0V`*X Tc%P J@%+J0V`*X Tc%P J@%cٳg%I$IF9XyuI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9ʛdl닾S/_NG_6|$I$I(Xy$I$IQf+%I$Ic2VJ$I$ F9=V^u]}$O +,2VΞ|%F9Iqjq>=oh{l\Sk|Uu8}pc^CquK$I$QNWᰶfsz1<~usL&;Xy%l}l=>:*~:Acfn/vGڞy9l\g/v>V$I$IwMӛc"/3yNx}H\hS; ceC-;6m࿎?XI$I$5Q{Vv؝gSoq~gJ$I$鮌rz9+lFʿ%\YW1kϻxۍppI$I7ѱz|75R79=ӱCƗjo?W_y|ʤO5nK$I$ݵQNώׯ/=OmapC>yq\緼S{6ݷj >ؼq}x>NGOb|]rl_-V?>;ƿ":ơdu+f_k2K$I$ݝQNiWgcxd]}$&c:f8s`W?|:.vxhO?A>r<Ӈ,0Vևտj| $I$I+%I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$I+%I$I.G9JI$I$QR$I$Irc$I$Icٳg%I$IF9JI$I$Q=1Vw?c%P J@%+J0V`*X Tc%P J@%+J0V`*X T.|1 G___R!;ґؖ+cd|Oyn_L_.bNMQ9LM;-+_zSo7.k\:x9N.<1~}06Zz8ǖ˦c"vM']|0f~/FԒcřkk?eɱnk1zfZs;wW[3c=F}A^Gb"9vlK6Z<Ga|M?C=e,VZYLĞu"[g11\_~{}ThcV=y2s+ow77&7G~l30s`Y{:SL㳱2yм粼Fӑy=_T3X:bCmr0>myBn+ċkoX(u4|\b)}uctu}go]>#6^_ؿ6>]a"lTAo4-G3]>V֟Kr3/=."{òq} ͎8xx{_:|>>>aTFo7 [d.4d8ay0+oVs_ħn䞕ٛ z:e0{m+=e'C3u`ޖ|"&Wٱ2n[ѯ/7D4ñ> ƞSc}_-Vo=M687&&z'Jmdî+S7.xt1<\CO'~]]'c,) ONąN\Кl /6=V17V.8޽:pg+oX fp[+oX c%P J@%+J0V`*X Tc%P J@%+J'ʳgJ$I$Ir$I$IRec$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]r$I$IRc$I$I(X)I$I$u91VJ$I$I]2"/TyIENDB` \ No newline at end of file diff -Naur a/docs/pictures/results.png b/docs/pictures/results.png --- a/docs/pictures/results.png 2022-09-21 14:21:00.532533970 +0800 +++ b/docs/pictures/results.png 2022-09-21 14:14:27.684533970 +0800 @@ -1,31 +1,36 @@ PNG  - IHDRE*IDATxInQm%xb<׈f+3⏈ yY& Jo߾K$I$I~;$I$I3@I$I$)J$I$I P$I$If$I$IR4$I$I$I$I%I$Ih(I$I$E3@I$I$))z7s۽/$I$I;tIT(I$Il Pѫ's[`sxߒ$I$I[>@U.z/5rYH$Iw;C#ǩT)jtں3~+K$I$][ P+=c[SG;@HmWWx$I$I PcP$I$I߾dZ}m4@U<q[ݺ_I$Iu3@ls=;;@͜$I$IUZ$I$I*mktںz+P^7ZPWx$I$I6@}ݫ/V7ueWx$I$I5:t8ur; P$I$IwJ$I$I_'$I$I$I$I%I$Ih(I$I$E3@I$I$)J$I$I P$I$If$I$IR4$I$I$I$I%I$Ih(I$I$E3@I$I$)J$I$I P$I$If$I$IR4$I$I$I$I]$I$IJ;2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD&[yܳU*~s{3zN^=_a23,\jugWnﹸ~ͮ:@y&* P3S}Ϥ3@MF*~s9CeXտ Pۻϑ[3uz?+h?˶αz{e{8t~Ppuz>xn]V} #V\w|gιu<3OQy{yL{_=[f5Z}d6@|ۻ:֨Ǿ[=ʇ׭~s9U.;ݻM5\96zG/K=Ѫ?z}V< zۥ:8nfn׺zs^<|ڊdz3jٻls3zg^]g -P!dTV?nhuj㩾N 'GF+έr_+UMG.W= y9nϜuѺǯOu>e+^{ns4s+=8]sL}Yyc_uیSJbFϮ<@2_}k0@u5{nwu㩪gz:@=_.T> Ppmr*]ucsuyV.Ow>{{8f~FlY=@>:UkGFIަu۵k帉s]gw=x*ﵙf{_#7ؕۨgq9lz{MrqzwëmgÑ<Ϸ3{=;zN/tfϭ{sz+++^|P [z?\[=von={}\~Y=e3?#mzc9Ye9~pP>D{dnψq;H(yfnψFpp[~F\z PD2@e @ -(Q( PD2@e @ -(Q( PD[$I$I7j5@ -(Q( PD2@e @ -(Q( PD2@e @ -(Q(ϟ?%I$ItVP~$I$IҍZ%I$IV3@I$I$ P$I$Izh5$I$IZ%I$IV3@I$I$ P$I$Izh5$I$IZ%I$IV3@I$I$ P$I$Izh5$I$IZRu_>Z׭9~8c4{Us>zn$I$ׯO<@A=O\Wf_ˑ'yWz{Wu{gux3q$I$I_n9@~S2DMT[73Uy߻V}]f{y9$I$k-F5C3}mo\k7zV_TF6Q$ItV3@5wzϵ5|Tw#שsujc7 {QIC׫$I$I.;@|pﹿj{K\{jfY\my]ɫ˷}>8B$I$%_UzsȹT1MPZ9:;@U}Wn3{߿$I$鋴e__Gz#Nou==k{kȹ#\}R=c1H$IV]j$I$If$I$IC$I$Ij(I$I$=J$I$If$I$IC$I$Ij(I$I$=J$I$If$I$IC$I$Ij(I$I$=J$I$I~)I$IZ|k3@e @ -(Q( PD2@e @ -K Pooou>oooUug=75{q_w#Fk:ld潻u i=~ӏs2s=}M*{sVwT53p\?f7Zȟⷎf~S|fL-ziV?@W~hx#2@ffuկ屍 I3# -O3@];@cKz#֫u=Q9ZX=Aa={s|nz^u!{_o]6{۽T P[}Ϋ۵3{[P=9{wV\22l<@}ysf`j]s P.3@^7`6=3ȭZ?~jX~9UMZnTUǏ?6|~<{>4WϷC_xuǷW>gWP#|ݶsԞc{9۶nbz=B>U*fVz(5r̻ P-zQ}f[5@r_ezOP= =3j8ZsYzj9<10ey]j|uޯnL{nz x6Կoq?}?_gKZuY=42,SQnuKzGγ~?ȟF\b=>9<1@q >̏|%( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PD2@e @ -(Q( PDJ曉IENDB` \ No newline at end of file + IHDR>kJksRGBgAMA a pHYs%%IR$IDATx^uϨɑ 6d!7L\fzlpcn͊w6=?zG͢w8 h ܓaI@"A cZ">׻tWU_uUUw]]Տ9/W}zU׵?`|#gſxSO`I$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r-?/8 {Ƨ^x,#?;N|*Y]:L:l:N:n:tZ$I$IWb𙆓F7^&_/v9o< ;;i AyL睖!-KZ&I%65=;8JpjtTL'Ik19y&Tm[=yNē\uWMf'IiASo_޸_=D\{ާ;,S:tiҲeJ&^W8Z?:rGfGN=ڕ-ϛ+{NĎ÷[w~{HUQi:ʻ6EˉsC벶b2ܽ!4rUl?Ic :<_W/M!ՇeI:tiҲeJ&-|k/IOk,psjqݼ֔}Ti6,3K4O-xMĩcc kc+U'7o{S1ӚtvnOlh5l=ꇛՐe*WX9T::8Rguy?3xc/IKiBFD|Ӈ}$!-KZliӲ7sR2S{^_ ގ=͆Ysfgd:3u-~\)ƎZ?L8{?ml;a7kwa[].Ů]uy?3xcK$-J|A^{9>{';|$huDqqſ}ߥäæliӲe&3?ŝm2N=#7'i- dk= 2$ugZ2 ox'O}3wIbtsGcijW?k/seUttttܼ$-[Zƴi3=?ŝJ+mGΗǴjdMG}x|L+*;xq.kyWbGOy[Si3K{b%gyv6?+οgKRgYPnf +yZס?>z/0vd|A*~oח߈eޮJ~~N#V:ͼj1-kZvx3L >3\|Nyj 6kNI *@2 w'bu6|\y<:nS4jàuvUMPfsMñ-]MB[xӠ̼!cQJN~_lurۿ~f1oċuaaqqiJN;G:eh9-{:tY2L >3\|jMe˵% =iͻ<8kkTڜg%~h=C|fݷ)Vʍ;l35gֹ)osWnw5ө/[|(>'c|v%^)ϟZ|luiؘ7\lN|+6~|3R`2 6|)x5[Xc|PUtttttiN+oeN˞.C,Ry|&|. >5ЊtQ%ZSۓ{C!Ǵ݀_'_]wXծ?6q$Xh3uy"Y?عHL)9<3 x;|)Xw۹ņ5O}#y'qv1y[S䥉cgbOpP3߭n_k&aaqqiJN;G:t!-{ 鲤d?*.QySj9 >wXeE_imvr8~zqgO׾X_[cZndw*-guyϽ{F>/_qoY6|0OÿY/7.Uwߪ{m;n/qiJN;G:t鼋ieH%]t٤g|g3`ßӔ=5d=8;<3c#|J:7_z(6.G~fu0o_G;у' |ttiN+gmPq2˒.Sl3L >3\|jz^{jRYcOvTrssqd9OIiȴmʹ';LL΅v6Wڗiyo.dl|`+cNtttyJ;-CZ˒.Sliϩɉ8up~p6?z8ɂo+ȣ3<~x-aSmthG˻zT;ᣧp|&sty*&NmQS x-}4{,f'>Mͷɽ{b|娞d2ڛsdz=۶<3y6{Oqw8纞OrT_ m=_\aO;X{ /gU.TktBO{Ξ=@k9w,۫zZ'o}|n%9?7w=X.bia =ޯ4WzAOIFu;XꎃNٱ:azpמVYdiH{Z,E;`J%]ttKGbbusvK^{޳je:Tx]Szgc׵9k>^x]:ɺyL;6+=?gxܫA畤2߾*\C|ژi0&hznOCgyj|=-CZLyIeI)]tRy-k{1h/L= /_\X{>L4Uv{]{xO z|fWnGgu(9>*kc^>T}ab|N0;n(G>zݻ}躚|x{1x1y!>\iy&=ܸ?dߑ;\.Ew,smσTӻbSϷrZ|rg_ʍ_=.cîN榲%wTlïO/%;LJ>9*]U]}2Ϥh(5ݹ>Ru?eÇs{bCcr >xpat]Mũ/wZ5c|Cc18sݹo9^e=9ue*[׃NT0_PIkTͳy?Ψ~+xl[c'jg1gW>eϤmKr5Jlhe32{n)9~]>`a=\ח9߯ >%-2L۾/~Nڙ=څg{I[S=p6sG㻯$>xrOg>yL睖!-KZlyOeJ-]ƅ0eX<{ +?|vYlF{ +/s~yh2L:Xwnkۇ1xnٿ ?O.h y<6vP?\^ЭkEucGM3&?Q<ܒ]wůy?Sq3Dg.Hҥ49zcs_.׺de߾{ۋs\Te5;{bǼGnue7 j]?~z_rs__^;ۻ+OI˼ >_~wŗNTE_sg~x䥉NҞq_vy7UwӋ'':t݇Z+\>6꾽&+b\?Tmr~l!67m̾_fÜm}kؔv33-1^5.q:>VslM!sQ7tv;=gT޵6ǖ۲-}=lklXH\}}klm-]J\-==>)v6[5l᭹3ΧCEU>y:خdy?M%V^9wE`m矽?ʞw#mݘ{Yjρ(,gw{} r1\חVUkcc>63玳GrIcmί]8Fվٿ?9N|;>?=Ru~?LosyNXl͓.cwNU8)^t'{0=wg/]?EgǞO{ Et|sSa˚=ę,j^/oeͦ`[xv};e-ka_7|mξ;]'lF}B>3=6w=>T8+Jޠ׾DVސcmFa*25O +&8yyMuq`З'b_q^󿯷]߯ǖ´m?]Md7]0m[ig8c{k{ ^ӞV_{}l?^{S,ٕ7=ޯ=_eW x^h_K{|;?b>l>t8~g]u[Țegirk 0pq_1v\qejyz:>޻#v<87}YY5(x.jp]v[Jg;V,n4?|}vdS5Vo:N;g9c cq.j+(=|_O{km|D"lP~= +,Fv{Y5i^e-n;ۺc;^yO{]]H\5|n*=|ΞzQǽ]LxL'?,ƹWs۴O;궶?/sv3Ek6UpU48-sA__ +skc4f&I >?{eް0IkR~8xo/Ŷ<>t˘z-x?9\ݱQ ZZ,iҲeL˚w$]ƴ內mU:Ʋ !tw'wxT!p7sݎ]ư`VkI7Ek hyb|N2QpPc~Ñ T=iwJs$!8[>.&bs)MN򑸭xlOU @=]9ɞo=zN/ZϺl~Xu;,t}l(*?s&s_۽Wf;MMDOE1۩DyuJ|vۮ{ +sygU:+|lQO/ý~})u6x_|J2 ņg>wXgL;z`|+T|4!-CZLi2e;l.ct{>, 튎۽a6Bu;9Sտ_On+!+ŗ_S_C)~cˠ~js-Xۨbfukuy{x(_ c˼ϊnazXSv7=4U!od8t{lwty +rsRO炿tU>+N<-OI@[~on0ߍz6~oc"֚;<՟Oo.Xu/E;L iY2eK˘5I鲖Y7=^ߖ"Va`{SuP0ƞ?9\]o; qax\v >a`g,=,:1muki>_C>U~Np +>v{.g}momQP5rx˜iKsr9i҇c|ά +:v__n?U|9ds UV@? ECowyNx;tYK +׼͞~&>??v}g]ssy-ϫg춍܁/|#W֋k}Y.r}{$&z>$-2s{J&7<__~#^zRǪkz;OxkgWZ,iҲeL˚w$]tYˬXk;)|T=uy1;\{f{lDZ[sc:Nnx^˸Ol=l;ۿͶ B]C-gڀ)t4g/{mTt9?֎=omH??,iq<5;{qaO>~ů{k?#͖cɽkhkm]۽< ^Z s|n^Si{⾪םZ6Pp:\s#0^-ٔ=;dx$3VꍵO >%v|)g'^{9~΄1}?۱ܑa۞o_^8ñ |ϴ iY2eK˘5I鲖 S3uY.C6fA{S:7s?~so|?[)l0ܱ|kol߽aCvs|3-N>pnΏt?to#o0mi@oHd/{BEdpKs+Kެ  ?v8 >98d=8`Xnj% >\gђ? w>|+x}7חuPٞF >@po seũͧUsž/O~/>wk_b<Mː%-SZiY˘.kyuVk67aWqz"˛A{S:7s?.o|SZ\ y3 E{-=Zd_}|kϩ;cnkMPGzhZkyLHbblh]M&::`쁭v CIu&G}s`0zlb߆ӟ&PൟVm_-uy>ν^;;oۚtoV೯Ĺs1,޶uvQ >6^_PCN*kc^vi)i[ԃƚ<?^|矊u[5!\]녯5>7xS80L]"oy >4yc9X&oT5A9?֜lǙwڮbf=:mf&ZWƛ>Zyd>eK]+0灮{$/wNkXH|6sSclM?kyV5kc [bSl޷\{̢Uer<&gm. ='WgSh7ԯѷ?q?rgҶ?TuP.#<_gj_;NF] pzk "M 4 >ۥoೡe>p?NUpǞ?z*&&'۶3Ut.sQװ2M;whٺVjN0\[WE+Ŗ{g;j0Ux[uNu]9u<-k? U|9*^Î؞kxex{_sJ\;b#qjb2&\t}uo]^7˹.|GgY Idk]^^Dk{ZaOIPwng'̣~VxoVoWǠxwqħ~W=w9-Ɲ0; Ta{{/o(foJ!oc߃s?.oC`aÉ9|X(PUt.:m^q[cY6նn:u:+W>-[N +oɜ[fUް6fh*o^%6-gYUN{iTmo~ s|Tc½<OQ,3wX١nɽ,s#4^Y:q<ǜuy=¾Lĉs޷^x|J2ws_n|ճ7ϞaEv}_7}oS}ƚ߈'3},-CZLi2e;l.ceVA +ftZMR)zf7!pM؜ޔW=nm΃|X(`ayW|{| 7zmka{g]ðrwg|v+\b+u^wpoe)ߦvVg7x]:Uv4_û,\?tچEa|hȺkQgV~_0`2H__.O٣bk_dmf)iS/< +O?'nշ^&pPui;_|Duxvկ?u߿U?Ke/o9ڥeH˒)7-cqeL셧׏?n=CYkE8;mV 4n951ˇ;nIgCe-nom.s슍4u>cúNIzLwx~蹴`8_wb4C](6*ʺ{r>ɘxV/\s0O\<s`%mC5xȺ=q) NgΗݾ~6;\/quV7$-2LkM!wX8xoVwP'OGo.~0y>vdx+г_6/C iY2eK˘5I鲖[ײu[lUa;57Togfem:g Zfo0 >/km-ܴh 4Lka]wBU/ú˲akjvZsts-\CC|{Mlg2}8&Y˓qXlxW%1m^v6^=!Wty}3}mϞ_#|i8r[h ֜u> /+kr\/{}tG$i d{sN?xWw&4cӿc/Nŷ~WD?Y~>9Cɸk6/C iY2eK˘5I鲖ZbՖ_4CȊX]u7Iui>:OlofnhɪqW-w)\`~]ބo{prxu[`ܞ 7<0]xo0˿ ?wCB >{خbc!{~4߶}eukFP!pM뢝RLeσݮ|5=_e}|8<(ne<{ip\n#nϖw-{{Lw}5M'N~ 0񪊞Kk.58x׸nyUk\6Ls:huBuͼaaާ^]2 3s}/8r|?y,~aw^y!'^=[{QZ3 7J{|g:ti˓--cZּ$2ZzS sZI3\ ه۞*[p^>eTl?xeΞ]\A+Wz#qxؚ 8i SiYg˲͋z9G^}1ɮMfSk|X3^˖yWl]Gv 8W>5lȿ]WŎGێW 65decO>=Q{,L#֜?|ކu\pa;4x]Vm='>(r<ܲ0!Yg/wbu8iS1qp{K}]۷4`N^g^k6yE=sI+XmCXq߳T޿#cNϤk?G/dzp0{pʜö*gP?gisaz[&re𙵠//cJ7s~Kg>JҢh ϗ]u;igEE_1Xݖo_'/M]/q}ƙ'w+/TyNː%-SZ'8鲥˘.B:Eg޺\kH/aºU}kVkK Ϭʻf k >kik-\] Vu9ce>u=O}f 8YgLc2V772sD%>S=Y?wJ;v<|H{M>ݺo7_z{~h)}9We 7Ҧ`o;gY;sA__s= [bc1V=3ЌnS< X@v|?O: &=څg;[SKV˓߫~=e=xWM{Tto_ok]t^hj_7s^+[c8ׇ^BZs|vE'j!Y^uvVem#7s~>̶[Ɲo|:[gxl)iϢ2ZI^5{VGw'{7ypK 7u^~8;}Nu*@y3P/]s=+]>/B/6g_tg';g!lÉy==mWol >C*jYs> >mtF#e;y> v:)Ϭcn NhzT]n_Oq&Tbm=ny:ܱoX[ঞsWu5 >y7g?s{9_9/=@`$=.>}'i['zݞ]jK\3}E?]z->?SrvW?{rziU׮<'x&ٱ/?}"v]xfN~?ĥoơ_n+ڜkc;yNː%-SeI)]t=v:K@M#e{n3V|o'{ٓʻהB>Sgc>ixdڻu+qU oq>S +k;%[\~pJ>0춷架[^;cw]q*aZg1vC?]qwnģlCW+cr}o編<e:>Ħ9ΤfÕ]_=ƝZgZ0}=nz_1`>޷3|%/FMܺsYח.nV0{|X3B/6/QO.g^7~|畟o_ `3 &ӠO:ٓɗϗx-9[W\zՋյA_(v:t^MwtSK}?ún{]1{γKkiwF&ݍupغTvzՐ&:z0|sݠ<=;7ٲ#i?v5ꪏbb>P>wOg0EsTeWti<JzǞ{|{?- +^CGs"N.g˗^Nu#q]ksr}l;{-vmWyצ^I{o(˕I#a>.Of۞OAZSwn;|}N|7=1(S -~IK >Sf<S/wçO?KKIMIMN+f:ty =2˒.Sl%OI >{pl-k;$i to_=NCᎃv6N;o}9~s5|)x.>{qOOJ~~N#V:tNGO.6K;J>/ v8_UoxoVozib]tttttiNQ#i˲2$I*SˠɉMƩ/o(܃/[Sx*ezr/K/<;dlߏ} '?c1oח߈eޮJ~~N#V:ͼj1-kZ2, >%I +22rUݸG+g[Ǘ^%I 2} g!mߏϜ;'z6'^{9/ߥäæf'i2eM˼3$I*SˠVg}SOIg^{:PLkS ;I{VO_OOC?ߍO ~$Y]:L:l:NeK˘5-rS$ O-|Vb^_q$-J|AbZ2}gg)ITA>+˧r*I+}\nfiPc߮? #˖3wZ,iҲ$IRAZ4r}=1Y?$I|=|llqm}7,S:tiҲeZ;2S$ O-kUɹW5b#1#$i[x;~kK?zᱸbS(ut{rn?G_NuO?KKIMIMa)I$I$-S$I$I$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF~ O`|#91F'0r >c O`|#91F'0r >c O`|#91F'0r >c O`|#91F'0r >c O`|#s!|সiCaOĭn1]` uq֙Aʹߘsv7ڏM_].]zorR>l[Jz}69,f#2+036T6B2+y'0 +F{ykkn~^=@Fl9 %geyMX=ҳ߈r|!s@}x«,XYWm[7rNg167H>jf0|V/]jg] ү[kviK?}$2m{cqW;6'WtÝӭ5mxꁦ h~_liыȶ\AG &_=':{8?}a Guq-wv?z~t]|񇭷gx/=T<(l)S_}1zv{ۏ32Tsv4=!N?VNƋ/|oG^lYAR}MnϿFm^Zݙ~}>'6}cv][Vw߈5O߲r=.aHpv3|>o>57ƍ6)@|;n{Ŧ̀bc mƏOv7uh[ӯm@e'zցG|gir5|gf\xk;lᄍu{ou~1r6>=j;23o]T2'/Ǿ1k8㶖!WT`PF{ٶ-N5~]~߲]W[R,6K{\?ڶc~в^vԪ>xi\D_uO>[քn ƈ>7=Լ qݼ#ge.>k|–|l6Y_>_lڣ{xqѽ>pk|qx>0s[Ϛسf)3;|㩼Kn9\so`su|۬mZW51[j|W㱶=7 ھh'mol[+0H?񍦵 +-k|qWݗ>+m{gOk|lqMʿ3zyMg;neRm-Oof)i](2|fNݭg_}>x(zxqv >GY캶5;3/5n|ɫ/z$vv:X<կ /9dv7H}svr7Ѹ4 Pe2|w|៚Ai3m/|f8>̙O=pk۠s4|lZ/#Uw#϶NW_:lƅ۩Mjz}x照;?0%c9{~k.xCO\#2\(M[o/|mw|a'⦼3B>pS|b[?=ҝqkԍ5;b_ޔfcoO4amw֖g/ߝ_Fc O`|#91F'0r >c O`|#91F'0r >c O`|#91F'0r >c O`|#91F'0r >S$I$IR$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i[o@f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' s+8{3qߟ6q1Yßgf?nn;lsˌ'e_if.[.՟;Vn:R]G]:xKwud4f~v{Rڸti |v??Jm9\?\:l[3}_|VšKOڰ} /šMmAcϋ'v-'~.ulUvY[e >?|W\tYKqU39.kw8O(r3szkt&p3_Z3}aSͦ|v1n|a۟ͬ=;\Qɽ>_.3C[I*JaރEv^g9\pA >[Z7n/uXsz>3'Cf9cG> vz+zqJ>TY箮.;LೋQ@N_Q4l˴>P}*M*L >3OW7]'ЭkvX{NFt3tXҺe}p߿3\?|@?Tnr[> |E׆[װk|޲1,i_lfu͛[4 >[iqX̷n}gӰXq׷5m)@<N.o坱#wŁ:aLXSf9c4lnZgZ6sЩu?k2m۠lYƦ5R:(XUqMƭn߱\8-qm}Uqwžm\e?>6Y6:1_೦>󇞮>ն6֮kֽ~1}UqEi;?/LEfᯎ=s?}(l˘.qm=W=>Flm[N?fܚ?l_s⾭Mϗ96olѵiMǞX(*7A/66>eװkh{`}ym_AnLnjL}2Pj2mÚݙ5ڭJN!fiŶ4U庻دfNw~z||]˻*n?:kZ.c{8s6P. 24>:d?jPkf|4nu-LߥqUqf8k˳C7f ˋv%ܱg ڶm˚nSm21 +6lJ6ݧ >*up 6<! 7o .8=Tm6x&)۰~JH8k]Wї/XpbC_`! >kUwg[VlZiӃ?+hzK!t6ƾ5 +. `9|+3_ok~5:Ԁ~gmCgw\TwTͮ~Ϫa_fk}Ϯm:Q<<ʇ}Z6Ko +Lf9@_woۖo렰6Px|{dkmkLx`_k20k|(n6[?>+oW;/=mpƸR?мvޠ=cwcy.<wYچ:k5]K׶jhMÖ}ؖir&vM4m̬7̴ _k [tmvNk{?6m3IsYLm_|-ϣf?f::<ܿxnY{eok3Mf9PkZ|n]d2ۇO06Dis H}HM?nӢ63s|fڶq|mMۘn -N靖疟w3psZv,TP5lX;,Mٚ*ad^p}_w=e[VMkͱ+z{uީL >{Z݇Dfið-2|Z@WM[u|?͖}3Z]Ϳjb25ZmXǿu2Lc.NfW=g +s= Iuϯ}ENǟ}j^ϖ׀дvt3sK4m|ɼ̹?w}s63ær3ipWri|hM`,|@!YW#gWgoNW6o+?wd4òe!x +EsPݶm|ǿNÖbߓgҬG뚆Wo=n6ltZy[ 3}sB>{Qt2w؋}Z1m|ܿ:up`;Q|:}meR3.϶ 3=4״4ځu-[?2hL~}M_l[9Ц8[8ĚZ֠jf﷕hxݬaKۚ5Mk`yrpe(v>$lcDm45kIħܧ2|v]j ڮU>T|]Ūߵ]C|>z9='/ťˀ/.^ݗ3eZ5pyEs߫{i/MySMU/;S|e &:>Nv| ƚMm}2x]?d_x<u Bok 54g6:juUL3i9{8ټC.Ϯ`zʇgKq[[tmøuЗ2t%f  >۶0}fY/˪c~>rsOovZ6Uvܖߖ|Z[<ǠNڟ޿ +~oom9n}Mz<=tM>5{;u>ٮ&nAc`I>39\󚝳]q} _Y+bs6I @fY4TT˚dmktdV>^f˰& w._Q}Qyg\{]Dy4k8}+s}l¡8M8LXӸM@i{9ZӰ׵;gZ.Öeh3kΥH]_o@дgCq7FWĪn{ι<}=xkVp2v[UAOgWa?|Olj͏`ܧK|frxv_6Y>'}zPfҦuWl`g}O,gWn]:w(|[NnOšK`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P|2o>VXџ9UO|ۛ'~gJkbйKMYKoJ|aCfɼ-g+> >UK-=`/+3i y[ϖ3či:x3OfI)>l-{+.]<=mZ sŊkӭK5_oof Rۮ7ƾMX?h 8|r3\".P+7;pERTe.w|Ԫ}kp>27uC*{,|mMe:Ԩ >4Xamr\_h]g֐Hf f;bP3s[bTb͝i؃ϖo|Rr3\F{qbzx}qGVӧəwy)m9Y{uOXjTnKmҡerT▃Zۗ/Ǿ^2m uٶ?{,pf KsHCƚ鿋y/Ɓgt+*7}Oַ8s~,q*aioC3lY?'I?t1|Ry㗷}'躖eᎊ*̯7 D߳-~^O76Y.̜W:^ִ 2s:E%n|LiN$2FfI)zzo찍dNҀ; ?s۟h[}qmanyE\ck|r3=oWcUꇿ"V}psp813=kbw~YcΘ|-' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @fzx`qS|.c郲$i42GfLzJe Ks22e)I',-*7eHX"Vtrt85Y?xsCdŽ^;]b]OQj:F5OXZTnTuk=_)T?g-ԐoAҰ3EfL>ܑ_~gwx'=7yf);OXZTn 1u8\=ѧg|Sw > >gsѝ3__2VoOO8|XqQyW}{hpܼ.t9\cXY=J\- z#w +=]NCSMeZ|պwUr{iX{7K_r>ubmZNNf:H\eZΝs0_W25 b]:-o5G>:ZyÎsp~pglZucYa֎_>džJ5b=w &gKݷf㆕աچ͇oy;%le|Ұ66ݻ2U>2k*>+cm]?^&OgMŴc"ghyk^c[c}u:NرWbGf]'[4]uzUv+X[n}#蕆~W?V)OXZTnS1yzl>ܘ NUvi@:[;le{nX}W%6=ؾvT<-ˆ_?EǞ9/6Į2[28md.vPQܖ{{0?2Z-c Ӿ.ks27sezb{\v3iٯOSvϧ,U]+7[靱6;gfٷԣ۫w]'Oku%_+ή떵vƮa?iDJ4,~6=a2EfL>,2{i5{u0/+Sos{k֎W:[>kmuY/ٿ+w_{6v/[5d=;Y>ΕcjyYTl͵iu2&g}YHKg?Cϔ',-*7erX{S5k|bSL_[owָ*;YS1yxvsXlݸϦbiձ铻bS1yz\rXuظk|=Wu;k'&ڎemHX 2kϩɉ8u4;wm[bY^ίh}mz헣hY{续u*-=S >E;7j}ZTڻ56ɕcmkSr<4fe05'ۚ:7޲#غT}emL9|/kʑnq~jͦzu;] >}3e KsZٿ-}xg97SMkor೩sc|PyҵooQھߴ폦gu֐o9Â2<c՝%ɦY֞ί5>{,k~& =S >|ʻ갯=찳~:1~ڸjUΐmVlձE}ceQԮߛ.m?5}WuX˛6i&^*V~c(-q;mv}6`mPc3e KsZgՁ5n^na)W=כ:1˲vzg}=Oo 1aWmDEie˵n~>f5k@~e糫|& {3^Wgokv=gv1xm`:dz~e[\'+Ve潺wuF4K >|;VWZ+o{Mk0|u18\jS|0gXbelX1 Mv>}%ƛvtvWOQߖ嬁\^vtرwz^TiZ_513}c0 9lM_-oI1xaJ۶';l>Ǫ˞ ik6WtWbGڮv7:TjY;{+iv >|֚xtglyUd]-|b,65"^*mӉxmwV<{>~fFiwSO6&OW׆6Ŏ;&ꡉ16w{8&" >Se߼æejz6<k5-ScN]OkفS:N6ne{mtOũwĦ5$}u]WGU/Þ6z2})[2evd) 3OXZTn˔dx<W4XJ҂g Ks2:cG*iA2EfL|Je Ks32>(=@f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O'vcŊ8sZ:(>iowOzyf;߻&6n/tv鍱*"VƸ[ży[ǡ^W}|6ĵ;ZN{.e˰w~5c?.0|*7Om>[o(Ϥ7>p\0lӏ| + R}8x>o[V\x>9p{\26;z:kgNrc.ZtOfI)z6?|}zGX|W;Qr1'6pMszX߮Zkynw\XYkiONk}|iRۚ- R2xp[PUv1ľ\s3/[3cͿ3n>x~P2}X >)E ECɆJl~hfKoJwWo?/O¡=3ǻ+5 Xqc;9 >]:x4k +,*7%⪫ӿX>/šOU_}qBwۨ|eo 7뚇þv?`o`[aK^W2 EGf!lb~mӗB?]]YMn[/?y>.~3>l9lϋCﱶ',f*7%g/¯6=plQP+ڛgiY2f Df f;bP3s[bTb͝i؃ϖo\:y_tT+]?,x >^sswHx}qGVӧəwy)m9AlғwŵCJI;}9 >Cq-//_'}ez3gEv3kz^##XJTnKDr6L]CϤxx1e*7}Oַ8s~,q*aWfٲ,zO<~~Rc8}=[^ =`Q|RkY:z@= 祸dv.+Jk=ݚAf^0c[͎}nUu0 >)E/_/?љIQp>~皙gUqmk/9 >p, ӹ >/m;xje R]➛5TXq3lwľ+;Ϊw xϚx=_=wև}"33;{}lle] >`qR| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >[o!{N2>xK'r3\ =%i~r3\ >%i|r3\ >%i|r3\ >%i|r3\>Ď+bE+WMwSW=7daO*h%6d6id ˛s}.=c-ǖ_+7G1콅-0S7OXTnTwvꚻbs#9gZ|r3\1ñ5걨> >{svZ"_u;wΥ|_+?OXTn@y9ΙǯX7mDwK_8슨=i4q8vn^WU.kرquf%zDJ k=~r;.k!ǦX}e2x>jݻ*=rG4f=ӛ%ȯv9NuotOكci6_''Nqdu$]2[Ie~zW?V)OXTn@/u0öTk ;bqiM֭Gcgf='>Z;~e|^*lf{t<ܱ6vW,uvߦϧVfS {ԓ;bu??v6铇Kب˚7|iO]#oڳ}2&Qκt\?UwσϢekOtmX659bc1bg9x$N5ԛ}UM3}m[&'|cmfz9ۿ>m_e=W;5֩p?z >`yS|.S>vn^૗F38wkm 4+Wc1ɵٿ֦|xiPXwc?adykN5uneGJUu6䫭s_#1Vi*q՚Mpw~|sy3L|r3\fy6yF4y`KmAGMTڛg3lj߻#6ptk[b[}oz$7u|ie5{b{[ >XugIcbbi}֩ñ+q7K ߡg720ϴt{YyZ5vNOg"oYW2 jӂm|^:}hTyXKϹd ˛sZgՁ5n^na)W=כ:1˲vzg}=Oo 1aWmDEie˵n~>f5k@~e糫|& {3^Wgokv=gv1xm`:dz~e[\'+Ve潺wu\ >|;VWZ+o{Mk0|u18\jS|0gXbelX1 Mv>}%ƛvtvWOQߖ嬁\^vtرwz^TiZ_513}c0 9lM_-oI1xaJ۶';l>Ǫ˞ ik6WtWbGڮv7:TjY;{+B >|֚xtglyUd]-|b,65"^*mӉxmwV<{>~fFiwSO6&OW׆6Ŏ;&ꡉ16w{8&" >Se߼æejz6<k5-ScN]OkفS:N6ne{mtOũwĦ5$}u]WGU/Þ6hf ˛s>[R~ >)O &O >`yS|.S\qK^Ѵc)I]3MfL|jpqy#',o*7eS'r3\o^s2 >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' @f Cr3!P| >`Tn0*7O' md?O`R|Fv)I$I$if)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4r|J$I$I >%I$I$\$I$IF.OI$I$I#$I$IS$I$Ie)I$I$i2$I$I4bE(dIENDB` \ No newline at end of file diff -Naur a/hwcompatible/cert_info.py b/hwcompatible/cert_info.py --- a/hwcompatible/cert_info.py 2022-09-21 14:21:00.532533970 +0800 +++ b/hwcompatible/cert_info.py 2022-09-21 14:14:27.684533970 +0800 @@ -62,7 +62,7 @@ oec_json["chipVendor"] = chip_vendor[0].strip("\n") oec_json["boardModel"] = device.board oec_json["chipModel"] = device.chip - oec_json["type"] = device.name + oec_json["type"] = device.name.upper() arch = self.command.run_cmd("uname -m", log_print=False) oec_json["architecture"] = arch[0].strip("\n") os_cmd = self.command.run_cmd( diff -Naur a/hwcompatible/config_ip.py b/hwcompatible/config_ip.py --- a/hwcompatible/config_ip.py 1970-01-01 08:00:00.000000000 +0800 +++ b/hwcompatible/config_ip.py 2022-09-21 14:14:27.684533970 +0800 @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +# Copyright (c) 2022 Huawei Technologies Co., Ltd. +# oec-hardware is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# Author: @cuixucui +# Create: 2022-09-02 + +import time +import hashlib +from urllib.parse import urlencode +from urllib.request import urlopen, Request + +from .command import Command +from .document import CertDocument +from .env import CertEnv +from .command_ui import CommandUI + + +class ConfigIP: + """ + Configure the IP address of the client and server ports + """ + + def __init__(self, config_data, logger, testcase): + self.config_data = config_data + self.logger = logger + self.command = Command(self.logger) + self.client_ip = "" + self.server_ip = "" + self.service_ip = "" + self.device = "" + self.client_mac = "" + self.testcase = testcase + + def config_ip(self): + """ + Configure the IP address on the network card port. + """ + cert = CertDocument(CertEnv.certificationfile, self.logger) + self.service_ip = cert.get_server() + + self.device = self.config_data.get("device", "") + if not self.device: + self.logger.error( + "Get the device failed from the configuration file.") + return False + + if self.get_port_status(): + self.logger.error("%s port is down." % self.device) + return False + + # If the client has configured the IP address, obtain the server + # IP address from the configuration file. + self.get_ip() + if self.client_ip: + self.logger.info("The client IP address already configured.") + if self.server_ip and self.ping_ip(self.server_ip): + return True + self.client_ip = "" + self.server_ip = "" + self.logger.info("Generate IP due to invalid server ip.") + + # If the client does not configured an IP address, obtain the IP + # of the client and server from the configuration file. + client_ip = self.config_data.get("client_ip", "") + server_ip = self.config_data.get("server_ip", "") + if client_ip and server_ip: + self.logger.info( + "Configure IP obtained from the configuration file.") + self.client_ip = client_ip + self.server_ip = server_ip + + if not self.client_ip or not self.server_ip: + self.logger.info("Start generating IP address...") + self.generate_ip() + + # Configure the IP addresses of the client and server. + if self.client_ip and self.server_ip: + if not self.config_client_ip(): + self.logger.error("Configure the client ip address failed.") + return False + if not self.config_server_ip(): + self.logger.error("Configure the server ip address failed.") + return False + else: + self.logger.error("Generate ip failed") + return False + + return True + + def get_port_status(self): + """ + Get the port with up status. + """ + result = self.command.run_cmd( + "ip link show %s | grep 'state UP'" % self.device) + return result[2] + + def get_ip(self): + """ + Obtain the IP address and MAC address of the client and the IP + address of the server. + """ + self.client_ip = self.command.run_cmd( + "ifconfig %s | grep '.*inet' | awk '{print $2}'" % self.device)[0] + self.client_mac = self.command.run_cmd( + "ifconfig %s | grep '.*ether' | awk '{print $2}'" % self.device)[0] + self.server_ip = self.config_data.get('server_ip', '') + + def generate_ip(self): + """ + Generate IP addresses of clients and servers. + """ + self.client_ip = "" + self.server_ip = "" + v4network = self.generate_network(self.client_mac) + num = 2 + while True: + ip = v4network + '.' + str(num) + if not self.ping_ip(ip): + if not self.client_ip: + self.client_ip = ip + else: + self.server_ip = ip + if self.client_ip and self.server_ip: + self.config_data["client_ip"] = self.client_ip + self.config_data["server_ip"] = self.server_ip + break + num += 1 + + def config_client_ip(self): + """ + Configure the IP address of the client. + """ + result = CommandUI().prompt_confirm( + "Are you sure to configure %s on port %s?" % ( + self.client_ip, self.device)) + + if result: + self.command.run_cmd( + "ifconfig %s:0 %s\/24" % (self.device, self.client_ip)) + return True + self.logger.warning( + "User won't use the generate IP address, stop the test.") + return False + + def config_server_ip(self): + """ + Configure the IP address of the server. + """ + result = CommandUI().prompt_confirm( + "Are you sure to configure %s on server port?" % self.server_ip) + if not result: + self.logger.warning( + "User won't use the generate IP address, stop the test.") + return False + + card_id = self.testcase.quad + + form = {'serverip': self.server_ip, 'cardid': card_id} + + url = 'http://{}/api/config/ip'.format(self.service_ip) + data = urlencode(form).encode('utf8') + headers = { + 'Content-type': 'application/x-www-form-urlencoded', + 'Accept': 'text/plain' + } + req = Request(url, data=data, headers=headers) + res = urlopen(req) + if res.code != 200: + return False + return True + + def ping_ip(self, ip): + """ + Determine whether the IP address can be pinged. + """ + count = 1 + cmd = "ping -q -c %d -W 1 %s | grep 'packet loss' | awk '{print $6}'"\ + % (count, ip) + result = self.command.run_cmd(cmd) + if result[0].strip() == "0%": + return True + return False + + def generate_network(self, mac): + """ + Generate the first three IP addresses according to the mac and time. + """ + while True: + now_time = str(time.time()) + ip0 = self._str_to_netip(mac + now_time, maxnum=223) + ip1 = self._str_to_netip(mac) + ip2 = self._str_to_netip(now_time) + ip = "%d.%d.%d" % (ip0, ip1, ip2) + if ip0 <= 223 and ip0 != 127: + return ip + + @staticmethod + def _str_to_netip(strs, maxnum=255): + """ + Generate each value of IP address. + """ + ipn = int(hashlib.sha512(strs.encode('utf-8')).hexdigest(), 16) % maxnum + return ipn diff -Naur a/hwcompatible/document.py b/hwcompatible/document.py --- a/hwcompatible/document.py 2022-09-21 14:21:00.532533970 +0800 +++ b/hwcompatible/document.py 2022-09-21 14:14:27.684533970 +0800 @@ -164,7 +164,6 @@ self.document.append(device.properties) - class FactoryDocument(Document): """ Get factory from file or factory parameter diff -Naur a/hwcompatible/job.py b/hwcompatible/job.py --- a/hwcompatible/job.py 2022-09-21 14:21:00.532533970 +0800 +++ b/hwcompatible/job.py 2022-09-21 14:14:27.684533970 +0800 @@ -24,6 +24,7 @@ from .reboot import Reboot from .cert_info import CertInfo from .constants import NO_CONFIG_DEVICES, NODEVICE +from .config_ip import ConfigIP class Job(): @@ -202,6 +203,11 @@ test = None logger = Logger(logname, self.job_id, sys.stdout, sys.stderr) logger.start() + if testcase['name'] in ('ethernet', 'infiniband'): + auto_config_ip = ConfigIP(config_data, logger, testcase["device"]) + if not auto_config_ip.config_ip(): + self.logger.error("Config IP address failed.") + return False try: test = testcase["test"] if subtests_filter and name != "system": diff -Naur a/oec-hardware.spec b/oec-hardware.spec --- a/oec-hardware.spec 2022-09-21 14:21:00.532533970 +0800 +++ b/oec-hardware.spec 2022-09-21 14:14:27.684533970 +0800 @@ -24,7 +24,8 @@ %package server Summary: openEuler Hardware Compatibility Test Server Group: Development/Tools -Requires: python3, python3-devel, nginx, tar, qperf, psmisc +Requires: python3, python3-devel, python3-flask, python3-uWSGI +Requires: nginx, tar, qperf, psmisc %description openEuler Hardware Compatibility Test Suite diff -Naur a/README.md b/README.md --- a/README.md 2022-09-21 14:21:00.532533970 +0800 +++ b/README.md 2022-09-21 14:14:27.676533970 +0800 @@ -50,7 +50,7 @@ ## 整机兼容性结论继承策略 -如果验证适配的服务器共主板,可以继承兼容性结论。 +如果验证适配的服务器共主板、CPU代次相同,可以继承兼容性结论。 ## 板卡兼容性结论继承策略 @@ -66,7 +66,6 @@ - 通过iBMC查看 - 在系统中执行命令"lspci -nvv"查看 - 板卡兼容性结论继承有以下三点: 1. vendorID和deviceID不同 @@ -93,7 +92,7 @@ ## 版本维护声明 -oec-hardware-1.1.0 版本将不再进行更新维护,请获取最新版本的oec-hardware进行安装使用。 +oec-hardware-1.1.1 版本将不再进行更新维护,请获取最新版本的 oec-hardware 进行安装使用。 # 工具使用 @@ -119,32 +118,37 @@ | 项目 | 要求 | |-----------|---------------------------------------------| -| 服务器型号 | Taishan200(Model 2280)、2288H V5或同等类型的服务器,对于x86_64服务器,icelake/cooperlake/cascade可任选一种,优选icelake | +| 服务器型号 | Taishan200(Model 2280)、2288H V5或同等类型的服务器(详见社区 [整机兼容性清单](https://www.openeuler.org/zh/compatibility/)),对于x86_64服务器,icelake/cooperlake/cascade可任选一种,优选icelake | | RAID卡 | 需要组raid,至少组raid0 | | NIC/IB卡 | 服务端和测试端需要分别插入一张同类型板卡,配置同网段IP,保证直连互通 | | FC卡 | 需要连接磁阵,至少组两个lun | -### 依赖组件 +**注意** -#### 客户端依赖组件 + 如果要测试外部驱动,请提前安装驱动,配置测试环境。 -| 组件 | 组件描述 | 可获得性 | -| --------- | ------ | ----------- | -| python3 | python3 及以上 | 可使用dnf进行安装 | - -#### 服务端依赖组件 - -| 组件 | 组件描述 | 可获得性 | -| --------- | ------- | ----------- | -| python3 | python3 及以上 | 可使用dnf进行安装 | -| Flask | v2.1.2 及以上版本 | 可使用pip3进行安装 | -| Flask-bootstrap | v3.3.7.1 及以上版本 | 可使用pip3进行安装 | -| uwsgi | v2.0.20 及以上版本 | 可使用pip3进行安装 | + GPU、VGPU、keycard等测试项需要提前安装外部驱动,保证环境部署完成,然后使用本工具进行测试。 ### 运行环境组网 ![test-network](docs/pictures/test-network.png) +# 离线安装环境部署要求 + +1. 下载 openEuler 官方的 everything iso,挂载本地 repo 源。 + + 如果在 everything iso 中无法找到依赖的软件包,请手动从 [openEuler 官方 repo](https://repo.openeuler.org/) 中下载软件包,上传至测试机中进行安装。 + +2. 根部不同的测试项,配置离线测试依赖 + + | 测试项 | 文件名 | 路径 | + | ---- | ----- | ----- | + | kabi | 下载对应版本和架构的内核白名单,此处以openEuler 22.03LTS、aarch64为例:https://gitee.com/src-openeuler/kernel/blob/openEuler-22.03-LTS/kabi_whitelist_aarch64 | `/var/oech` | + | GPU | https://github.com/wilicc/gpu-burn | `/opt` | + | | https://github.com/NVIDIA/cuda-samples/archive/refs/heads/master.zip | `/opt` | + | VGPU | nvidia vgpu client驱动软件包 | /root | + | | 下载对应版本和架构的虚拟机镜像文件,此处以openEuler 22.03LTS、x86_64为例:https://repo.openeuler.org/openEuler-22.03-LTS/virtual_machine_img/x86_64/openEuler-22.03-LTS-x86_64.qcow2.xz | `/opt` | + # 工具安装 ## 前提条件 @@ -175,20 +179,14 @@ dnf install oec-hardware-server ``` -2. 服务端 web 展示页面需要的部分组件系统本身不提供,需要使用 `pip3` 安装(请自行配置可用 pip 源)。 - - ``` - pip3 install Flask Flask-bootstrap uwsgi - ``` - -3. 启动服务。本服务默认使用 8080 端口,同时搭配 nginx(默认端口 80)提供 web 服务,请保证这些端口未被占用。 +2. 启动服务。本服务通过搭配 nginx 服务提供 web 服务,默认使用 80 端口,可以通过 nginx 服务配置文件修改对外端口,启动前请保证这些端口未被占用。 ``` systemctl start oech-server.service systemctl start nginx.service ``` -4. 关闭防火墙和 SElinux。 +3. 关闭防火墙和 SElinux。 ``` systemctl stop firewalld @@ -201,7 +199,9 @@ ## 前提条件 * `/usr/share/oech/kernelrelease.json` 文件中列出了当前支持的所有系统版本,使用`uname -a` 命令确认当前系统内核版本是否属于框架支持的版本。 + * 框架默认会扫描所有网卡,对网卡进行测试前,请自行筛选被测网卡,并给它配上能 `ping` 通服务端的 ip;如果客户端是对 InfiniBand 网卡进行测试,服务端也必须有一个 InfiniBand 网卡并提前配好 ip 。建议不要使用业务网口进行网卡测试。 + * `/usr/share/oech/lib/config/test_config.yaml ` 是硬件测试项配置文件模板,`fc`、`raid`、`disk`、`ethernet`、`infiniband`硬件测试前需先根据实际环境进行修改,其它硬件测试不需要修改。 ## 使用步骤 @@ -367,7 +367,8 @@ 9. **perf** - 测试 perf 工具是否能正常使用。 + - 收集系统中硬件产生的事件。 + - 收集采样信息,查看统计结果。 10. **cdrom** @@ -412,6 +413,16 @@ - 使用 perftest 测试 infiniband(IB) 网络协议的延迟和带宽。 - **注意** 进行网络带宽测试时,请提前确认服务端网卡速率不小于客户端,并保证测试网络无其他流量干扰。 +20. **kabi** + + - 测试内核 kabi 和标准系统相比是否发生变化。 + +21. **VGPU** + + - 测试 VGPU 服务端基本功能。 + - 部署 VGPU 客户端虚拟机,测试驱动安装,测试客户端 VGPU 功能。 + - VGPU 服务端监控客户端的运行。 + # 社区开发者参与介绍 ## 环境部署 @@ -436,7 +447,7 @@ ``` dnf install -y rpm-build cd oec-hardware - tar oec-hardware-1.0.0.tar.bz2 * + tar jcvf oec-hardware-1.0.0.tar.bz2 * mkdir -p /root/rpmbuild/SOURCES cp oec-hardware-1.0.0.tar.bz2 /root/rpmbuild/SOURCES/ rpmbuild -ba oec-hardware.spec @@ -462,6 +473,8 @@ # FAQ - [鲲鹏小智](https://ic-openlabs.huawei.com/chat/#/)提供了oec-hardware测试过程中可能遇到的问题的解决方案,如果在适配过程中遇到问题,可以优先通过鲲鹏小智获取支撑。 + [鲲鹏小智](https://ic-openlabs.huawei.com/chat/#/) 提供了oec-hardware测试过程中可能遇到的问题的解决方案,用户可以通过检索获取问题的解决方法。另外 [鲲鹏论坛] (https://bbs.huaweicloud.com/forum/forum-927-1.html)上提供了完整的[oec-hardware安装使用问题解答](https://bbs.huaweicloud.com/forum/thread-0210979171291590002-1-1.html) ,用户可以根据场景获取解决方案。 + + 如果在适配过程中遇到问题,建议用户优先通过鲲鹏小智或鲲鹏论坛获取支撑。 如果鲲鹏小智无法解决,可在本仓库下提issue反馈或者发邮件至openEuler社区兼容性SIG组邮箱:oecompatibility@openeuler.org。 \ No newline at end of file diff -Naur a/scripts/oech-server.service b/scripts/oech-server.service --- a/scripts/oech-server.service 2022-09-21 14:21:00.532533970 +0800 +++ b/scripts/oech-server.service 2022-09-21 14:14:27.684533970 +0800 @@ -5,8 +5,8 @@ [Service] Type=notify ExecStartPre=/usr/bin/bash /usr/share/oech/lib/server/oech-server-pre.sh -ExecStart=/usr/local/bin/uwsgi --ini /usr/share/oech/lib/server/uwsgi.ini -ExecStop=/usr/local/bin/uwsgi --stop /usr/share/oech/lib/server/uwsgi.pid +ExecStart=/usr/bin/uwsgi --ini /usr/share/oech/lib/server/uwsgi.ini +ExecStop=/usr/bin/uwsgi --stop /usr/share/oech/lib/server/uwsgi.pid [Install] WantedBy=multi-user.target diff -Naur a/server/server.py b/server/server.py --- a/server/server.py 2022-09-21 14:21:00.532533970 +0800 +++ b/server/server.py 2022-09-21 14:14:27.684533970 +0800 @@ -19,20 +19,21 @@ import time import subprocess import base64 +import re +import operator +import stat from urllib.parse import urlencode from urllib.request import urlopen, Request from urllib.error import HTTPError - from flask import Flask, render_template, redirect, url_for, abort, request, send_from_directory, flash -from flask_bootstrap import Bootstrap app = Flask(__name__) app.secret_key = os.urandom(24) -bootstrap = Bootstrap(app) dir_server = os.path.dirname(os.path.realpath(__file__)) dir_results = os.path.join(dir_server, 'results') dir_files = os.path.join(dir_server, 'files') +ip_file = os.path.join(dir_server, "ip.txt") @app.errorhandler(400) @@ -135,7 +136,7 @@ for testcase in results: device = testcase.get('device') if device and device.get('INTERFACE') == interface: - return render_template('device.html', device=device, interface=interface) + return render_template('device.html', host=host, id=oec_id, job=job, device=device, interface=interface) else: abort(404) @@ -161,7 +162,7 @@ sys.stderr.write("The file %s is not json file.\n") return False - return render_template('devices.html', devices=devices) + return render_template('devices.html', host=host, id=oec_id, job=job, devices=devices) @app.route('/results////attachment') @@ -198,7 +199,7 @@ with open(logpath, 'r') as file_content: log = file_content.read().split('\n') - return render_template('log.html', name=name, log=log) + return render_template('log.html', host=host, id=oec_id, job=job, name=name, log=log) @app.route('/results////submit') @@ -263,7 +264,7 @@ oec_id = request.values.get('id', '').strip().replace(' ', '-') job = request.values.get('job', '').strip().replace(' ', '-') filetext = request.values.get('filetext', '') - if not(all([host, oec_id, job, filetext])): + if not (all([host, oec_id, job, filetext])): return render_template('upload.html', host=host, id=id, job=job, filetext=filetext, ret='Failed'), 400 @@ -307,7 +308,7 @@ """ filename = request.values.get('filename', '') filetext = request.values.get('filetext', '') - if not(all([filename, filetext])): + if not (all([filename, filetext])): return render_template('upload.html', filename=filename, filetext=filetext, ret='Failed'), 400 @@ -322,6 +323,35 @@ ret='Successful') +@app.route('/api/config/ip', methods=['GET', 'POST']) +def config_ip(): + """ + config server ip + """ + sever_ip = request.values.get('serverip', '') + card_id = request.values.get('cardid', '') + cmd_result = subprocess.getoutput("ip link show up | grep 'state UP'") + + ports = [] + for port in cmd_result.split('\n'): + ports.append(port.split(':')[1].strip()) + + for pt in ports: + cmd_result = subprocess.getoutput("ethtool -i %s" % pt) + for data in cmd_result.split('\n'): + if "bus-info" in data: + pci_num = data.split(':', 1)[1].strip() + quad = __get_quad(pci_num) + if operator.eq(quad, eval(card_id)): + subprocess.getoutput("ifconfig %s:0 %s/24" % (pt, sever_ip)) + with os.fdopen(os.open(ip_file, os.O_WRONLY | os.O_CREAT, + stat.S_IRUSR), 'w+') as f: + f.write('{},{}'.format(pt, sever_ip)) + break + + return render_template('index.html') + + @app.route('/api/', methods=['GET', 'POST']) def test_server(act): """ @@ -353,6 +383,7 @@ if cmd[0] == 'all': for process_name in valid_commands: __stop_process(process_name) + __delete_ip() else: __stop_process(cmd[0]) else: @@ -362,7 +393,8 @@ def __stop_process(process_name): - check_cmd = subprocess.getstatusoutput("ps -ef | grep %s | grep -v grep" % process_name) + check_cmd = subprocess.getstatusoutput( + "ps -ef | grep %s | grep -v grep" % process_name) if check_cmd[0] != 0: return kill_cmd = ['killall', '-9', process_name] @@ -403,5 +435,35 @@ return None, None +def __get_quad(pci_num): + """ + Get network card quad + """ + cmd_result = subprocess.getoutput("lspci -xs %s" % pci_num).split('\n') + + quad = [] + for ln in cmd_result: + if re.match("00: ", ln): + tmp = ln.split(" ")[1:5] + quad.extend([tmp[1] + tmp[0], tmp[3] + tmp[2]]) + if re.match("20: ", ln): + tmp = ln.split(" ")[-4:] + quad.extend([tmp[-3] + tmp[-4], tmp[-1] + tmp[-2]]) + return quad + + +def __delete_ip(): + """ + Delete the IP configured on the server + """ + if not os.path.exists(ip_file): + return + + with open(ip_file, 'r') as f: + ip = f.read().split(',') + subprocess.Popen(['ip', 'addr', 'del', ip[1], "dev", ip[0]]) + os.remove(ip_file) + + if __name__ == '__main__': app.run(host='0.0.0.0', port=80) diff -Naur a/server/static/style.css b/server/static/style.css --- a/server/static/style.css 1970-01-01 08:00:00.000000000 +0800 +++ b/server/static/style.css 2022-09-21 14:14:27.684533970 +0800 @@ -0,0 +1,151 @@ +.topnav { + overflow: hidden; + font-weight: bold; + bottom: 10%; + margin-bottom: 20px; + padding-top: 6px; + padding-bottom: 6px; + background-color: #333; +} + +.topnav a { + float: left; + display: block; + text-align: center; + padding: 14px 16px; + margin-left: 10px; + margin-right: 20px; + color: #f2f2f2; +} + +.topnav a:hover { + text-align: center; + border-radius: 5px; + color: black; + background-color: #ddd; +} + +img { + text-align: center; + font-weight: bold; + font-size: 10px; + font-weight: 100; + vertical-align: middle; + margin-left: 5px; + margin-right: 5px; +} + +li { + font-weight: bold; + padding: 6px; +} + +p { + margin-left: 10px; + font-weight: bold; + color: #000000; +} + +.dropdown-toggle { + font-size: medium; + color: #000000; +} + +.dropdown-menu { + border-radius: 5px; + font-size: medium; +} + +.dropdown-content { + font-weight: bold; + color: #000000; + min-width: 160px; + padding: 12px; +} + +.navbar a { + display: inline-block; + width: 100px; + height: 40px; + font-size: medium; + font-weight: bold; + text-align: center; + line-height: 40px; + padding-top: 5px; + padding-bottom: 5px; + color: #337ab7; + text-decoration: none; + margin-bottom: 20px; + margin-left: 10px; + margin-right: 20px; +} + +.navbar a:hover { + text-align: center; + border-radius: 5px; + color: white; + background-color: #337ab7; +} + +button { + width: 100px; + height: 40px; + font-size: medium; + font-weight: bold; + text-align: center; + background-color: #337ab7; + color: #fff; + border: none; + border-radius: 5px; +} + +table { + margin-left: 30px; + margin-bottom: 50px; + padding: 8px; + line-height: 1.5; + vertical-align: top; + border-spacing: 0; + border-collapse: collapse; + width: 360px; + border-collapse: collapse; + min-width: 800px; + +} + +th { + font-weight: bold; + vertical-align: bottom; + text-align: left; + background-color: #fff; + color: #333; + font: 14px Arial; + padding: 8px; +} + +td { + color: #333; + font: 14px Arial; + padding: 8px; + text-indent: 0; + width: 360px; + border-collapse: collapse; +} + +tr { + color: #333; + border-top: none; + border-bottom: 1px solid #ddd; + background-color: rgb(250, 250, 250); +} + +tr:nth-child(even) { + color: #333; + background-color: #fff; +} + +a { + font-weight: bold; + color: #337ab7; + text-decoration: none +} \ No newline at end of file diff -Naur a/server/templates/base.html b/server/templates/base.html --- a/server/templates/base.html 2022-09-21 14:21:00.532533970 +0800 +++ b/server/templates/base.html 2022-09-21 14:14:27.684533970 +0800 @@ -1,37 +1,33 @@ -{% extends "bootstrap/base.html" %} -{% include "flash.html" %} + + + {% include 'flash.html' %} + + + -{% block title %}OECH{% endblock %} - -{% block head %} -{{ super() }} - - -{% endblock %} - -{% block navbar %} -