2222import com .cloud .agent .IAgentControl ;
2323import com .cloud .agent .api .Answer ;
2424import com .cloud .agent .api .Command ;
25+ import com .cloud .agent .api .CreateVhbaDeviceCommand ;
2526import com .cloud .agent .api .ListHostDeviceAnswer ;
2627import com .cloud .agent .api .ListHostHbaDeviceAnswer ;
2728import com .cloud .agent .api .ListHostLunDeviceAnswer ;
2829import com .cloud .agent .api .ListHostUsbDeviceAnswer ;
30+ import com .cloud .agent .api .ListVhbaDevicesCommand ;
2931import com .cloud .agent .api .StartupCommand ;
3032import com .cloud .agent .api .UpdateHostHbaDeviceAnswer ;
3133import com .cloud .agent .api .UpdateHostLunDeviceAnswer ;
3234import com .cloud .agent .api .UpdateHostUsbDeviceAnswer ;
35+ import com .cloud .agent .api .UpdateHostVhbaDeviceCommand ;
3336import com .cloud .utils .net .NetUtils ;
3437import com .cloud .utils .script .OutputInterpreter ;
3538import com .cloud .utils .script .Script ;
5659import org .apache .commons .lang3 .StringUtils ;
5760import org .apache .logging .log4j .LogManager ;
5861import org .apache .logging .log4j .Logger ;
59- import org .json .JSONArray ;
60- import org .json .JSONObject ;
6162import javax .naming .ConfigurationException ;
6263
63-
64- // import org.apache.cloudstack.storage.command.browser.ListRbdObjectsAnswer;
65-
66- // import com.cloud.agent.api.ListHostLunDeviceCommand;
67-
6864public abstract class ServerResourceBase implements ServerResource {
6965 protected Logger logger = LogManager .getLogger (getClass ());
7066 protected String name ;
@@ -300,6 +296,8 @@ private boolean hasPartitionRecursive(JSONObject device) {
300296 protected Answer listHostHbaDevices (Command command ) {
301297 List <String > hostDevicesText = new ArrayList <>();
302298 List <String > hostDevicesNames = new ArrayList <>();
299+
300+ // 물리 HBA 장치 조회
303301 Script listCommand = new Script ("/bin/bash" );
304302 listCommand .add ("-c" );
305303 listCommand .add ("lspci | grep -i 'scsi\\ |sas\\ |fibre\\ |raid\\ |hba'" );
@@ -311,10 +309,238 @@ protected Answer listHostHbaDevices(Command command) {
311309 String [] parts = line .split (" " , 2 );
312310 if (parts .length >= 2 ) {
313311 hostDevicesNames .add (parts [0 ].trim ());
314- hostDevicesText .add (parts [1 ].trim ());
312+ hostDevicesText .add ("Physical HBA: " + parts [1 ].trim ());
313+ }
314+ }
315+ }
316+
317+ // vHBA 장치 조회 (KVM 환경)
318+ try {
319+ Script vhbaCommand = new Script ("/bin/bash" );
320+ vhbaCommand .add ("-c" );
321+ vhbaCommand .add ("virsh nodedev-list | grep vhba" );
322+ OutputInterpreter .AllLinesParser vhbaParser = new OutputInterpreter .AllLinesParser ();
323+ String vhbaResult = vhbaCommand .execute (vhbaParser );
324+
325+ if (vhbaResult == null && vhbaParser .getLines () != null ) {
326+ String [] vhbaLines = vhbaParser .getLines ().split ("\\ n" );
327+ for (String vhbaLine : vhbaLines ) {
328+ String vhbaName = vhbaLine .trim ();
329+ if (!vhbaName .isEmpty ()) {
330+ // vHBA 상세 정보 조회
331+ Script vhbaInfoCommand = new Script ("/bin/bash" );
332+ vhbaInfoCommand .add ("-c" );
333+ vhbaInfoCommand .add ("virsh nodedev-dumpxml " + vhbaName );
334+ OutputInterpreter .AllLinesParser vhbaInfoParser = new OutputInterpreter .AllLinesParser ();
335+ String vhbaInfoResult = vhbaInfoCommand .execute (vhbaInfoParser );
336+
337+ String vhbaDescription = "Virtual HBA Device" ;
338+ if (vhbaInfoResult == null && vhbaInfoParser .getLines () != null ) {
339+ String [] infoLines = vhbaInfoParser .getLines ().split ("\\ n" );
340+ for (String infoLine : infoLines ) {
341+ if (infoLine .contains ("<name>" )) {
342+ String name = infoLine .replaceAll ("<[^>]*>" , "" ).trim ();
343+ vhbaDescription = "Virtual HBA: " + name ;
344+ break ;
345+ }
346+ }
347+ }
348+
349+ hostDevicesNames .add (vhbaName );
350+ hostDevicesText .add (vhbaDescription );
351+ }
315352 }
316353 }
354+ } catch (Exception e ) {
355+ logger .debug ("vHBA 조회 중 오류 발생 (일반적인 상황): " + e .getMessage ());
317356 }
357+
358+ return new ListHostHbaDeviceAnswer (true , hostDevicesNames , hostDevicesText );
359+ }
360+
361+ protected Answer createHostVHbaDevices (Command command ) {
362+ try {
363+ CreateVhbaDeviceCommand cmd = (CreateVhbaDeviceCommand ) command ;
364+ String parentHbaName = cmd .getParentHbaName ();
365+ String wwnn = cmd .getWwnn ();
366+ String wwpn = cmd .getWwpn ();
367+ String vhbaName = cmd .getVhbaName ();
368+ String xmlContent = cmd .getXmlContent ();
369+
370+ // Vue에서 받은 XML을 임시 파일로 저장
371+ String xmlFilePath = "/tmp/" + vhbaName + ".xml" ;
372+ try (FileWriter writer = new FileWriter (xmlFilePath )) {
373+ writer .write (xmlContent );
374+ }
375+
376+ // virsh nodedev-create 명령 실행
377+ Script createCommand = new Script ("/bin/bash" );
378+ createCommand .add ("-c" );
379+ createCommand .add ("virsh nodedev-create " + xmlFilePath );
380+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
381+ String result = createCommand .execute (parser );
382+
383+ if (result != null ) {
384+ logger .error ("vHBA 생성 실패: " + result );
385+ return new com .cloud .agent .api .CreateVhbaDeviceAnswer (false , vhbaName , null );
386+ }
387+
388+ // 생성된 디바이스 이름 추출
389+ String createdDeviceName = null ;
390+ if (parser .getLines () != null ) {
391+ String [] lines = parser .getLines ().split ("\\ n" );
392+ for (String line : lines ) {
393+ if (line .contains ("created from" )) {
394+ String [] parts = line .split (" " );
395+ if (parts .length >= 2 ) {
396+ createdDeviceName = parts [1 ];
397+ }
398+ break ;
399+ }
400+ }
401+ }
402+
403+ // 임시 XML 파일 삭제
404+ // new File(xmlFilePath).delete();
405+
406+ logger .info ("vHBA 디바이스 생성 성공: " + vhbaName + " -> " + createdDeviceName );
407+ return new com .cloud .agent .api .CreateVhbaDeviceAnswer (true , vhbaName , createdDeviceName );
408+
409+ } catch (Exception e ) {
410+ logger .error ("vHBA 디바이스 생성 중 오류: " + e .getMessage (), e );
411+ return new com .cloud .agent .api .CreateVhbaDeviceAnswer (false , null , null );
412+ }
413+ }
414+
415+ protected Answer listHostVHbaDevices (Command command ) {
416+ try {
417+ ListVhbaDevicesCommand cmd = (ListVhbaDevicesCommand ) command ;
418+ String keyword = cmd .getKeyword ();
419+ List <ListVhbaDevicesCommand .VhbaDeviceInfo > vhbaDevices = new ArrayList <>();
420+
421+ // vHBA 장치 조회 (KVM 환경)
422+ Script vhbaCommand = new Script ("/bin/bash" );
423+ vhbaCommand .add ("-c" );
424+ vhbaCommand .add ("virsh nodedev-list | grep vhba" );
425+ OutputInterpreter .AllLinesParser vhbaParser = new OutputInterpreter .AllLinesParser ();
426+ String vhbaResult = vhbaCommand .execute (vhbaParser );
427+
428+ if (vhbaResult == null && vhbaParser .getLines () != null ) {
429+ String [] vhbaLines = vhbaParser .getLines ().split ("\\ n" );
430+ for (String vhbaLine : vhbaLines ) {
431+ String vhbaName = vhbaLine .trim ();
432+ if (!vhbaName .isEmpty ()) {
433+ // 키워드 필터링
434+ if (keyword != null && !keyword .isEmpty () && !vhbaName .contains (keyword )) {
435+ continue ;
436+ }
437+
438+ // vHBA 상세 정보 조회
439+ Script vhbaInfoCommand = new Script ("/bin/bash" );
440+ vhbaInfoCommand .add ("-c" );
441+ vhbaInfoCommand .add ("virsh nodedev-dumpxml " + vhbaName );
442+ OutputInterpreter .AllLinesParser vhbaInfoParser = new OutputInterpreter .AllLinesParser ();
443+ String vhbaInfoResult = vhbaInfoCommand .execute (vhbaInfoParser );
444+
445+ String parentHbaName = "" ;
446+ String wwnn = "" ;
447+ String wwpn = "" ;
448+ String description = "Virtual HBA Device" ;
449+ String status = "Active" ;
450+
451+ if (vhbaInfoResult == null && vhbaInfoParser .getLines () != null ) {
452+ String [] infoLines = vhbaInfoParser .getLines ().split ("\\ n" );
453+ for (String infoLine : infoLines ) {
454+ if (infoLine .contains ("<parent>" )) {
455+ parentHbaName = infoLine .replaceAll ("<[^>]*>" , "" ).trim ();
456+ } else if (infoLine .contains ("wwnn=" )) {
457+ wwnn = infoLine .replaceAll (".*wwnn='([^']*)'.*" , "$1" ).trim ();
458+ } else if (infoLine .contains ("wwpn=" )) {
459+ wwpn = infoLine .replaceAll (".*wwpn='([^']*)'.*" , "$1" ).trim ();
460+ } else if (infoLine .contains ("<name>" )) {
461+ String name = infoLine .replaceAll ("<[^>]*>" , "" ).trim ();
462+ description = "Virtual HBA: " + name ;
463+ }
464+ }
465+ }
466+
467+ // vHBA 상태 확인
468+ Script statusCommand = new Script ("/bin/bash" );
469+ statusCommand .add ("-c" );
470+ statusCommand .add ("virsh nodedev-info " + vhbaName + " | grep State" );
471+ OutputInterpreter .AllLinesParser statusParser = new OutputInterpreter .AllLinesParser ();
472+ String statusResult = statusCommand .execute (statusParser );
473+
474+ if (statusResult == null && statusParser .getLines () != null ) {
475+ String statusLine = statusParser .getLines ().trim ();
476+ if (statusLine .contains ("State:" )) {
477+ status = statusLine .replaceAll (".*State:\\ s*([^\\ s]+).*" , "$1" ).trim ();
478+ }
479+ }
480+
481+ com .cloud .agent .api .ListVhbaDevicesCommand .VhbaDeviceInfo vhbaInfo =
482+ new com .cloud .agent .api .ListVhbaDevicesCommand .VhbaDeviceInfo (
483+ vhbaName , parentHbaName , wwnn , wwpn , description , status
484+ );
485+ vhbaDevices .add (vhbaInfo );
486+ }
487+ }
488+ }
489+
490+ logger .info ("vHBA 디바이스 조회 완료: " + vhbaDevices .size () + "개 발견" );
491+ return new com .cloud .agent .api .ListVhbaDevicesAnswer (true , vhbaDevices );
492+
493+ } catch (Exception e ) {
494+ logger .error ("vHBA 디바이스 조회 중 오류: " + e .getMessage (), e );
495+ return new com .cloud .agent .api .ListVhbaDevicesAnswer (false , new ArrayList <>());
496+ }
497+ }
498+
499+ protected Answer listVhbaCapableHbas (Command command ) {
500+ List <String > hostDevicesText = new ArrayList <>();
501+ List <String > hostDevicesNames = new ArrayList <>();
502+
503+ try {
504+ // vHBA를 지원하는 HBA 조회
505+ Script vportsCommand = new Script ("/bin/bash" );
506+ vportsCommand .add ("-c" );
507+ vportsCommand .add ("virsh nodedev-list --cap vports" );
508+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
509+ String result = vportsCommand .execute (parser );
510+
511+ if (result == null && parser .getLines () != null ) {
512+ String [] lines = parser .getLines ().split ("\\ n" );
513+ for (String line : lines ) {
514+ String hbaName = line .trim ();
515+ if (!hbaName .isEmpty ()) {
516+ // HBA 상세 정보 조회
517+ Script hbaInfoCommand = new Script ("/bin/bash" );
518+ hbaInfoCommand .add ("-c" );
519+ hbaInfoCommand .add ("virsh nodedev-dumpxml " + hbaName );
520+ OutputInterpreter .AllLinesParser hbaInfoParser = new OutputInterpreter .AllLinesParser ();
521+ String hbaInfoResult = hbaInfoCommand .execute (hbaInfoParser );
522+
523+ String hbaDescription = "vHBA Capable HBA: " + hbaName ;
524+ if (hbaInfoResult == null && hbaInfoParser .getLines () != null ) {
525+ String [] infoLines = hbaInfoParser .getLines ().split ("\\ n" );
526+ for (String infoLine : infoLines ) {
527+ if (infoLine .contains ("<max_vports>" )) {
528+ String maxVports = infoLine .replaceAll ("<[^>]*>" , "" ).trim ();
529+ hbaDescription = "vHBA Capable HBA: " + hbaName + " (Max vPorts: " + maxVports + ")" ;
530+ break ;
531+ }
532+ }
533+ }
534+
535+ hostDevicesNames .add (hbaName );
536+ hostDevicesText .add (hbaDescription );
537+ }
538+ }
539+ }
540+ } catch (Exception e ) {
541+ logger .debug ("vHBA 지원 HBA 조회 중 오류 발생: " + e .getMessage ());
542+ }
543+
318544 return new ListHostHbaDeviceAnswer (true , hostDevicesNames , hostDevicesText );
319545 }
320546
@@ -771,4 +997,53 @@ protected Answer updateHostHbaDevices(Command command, String vmName, String xml
771997 return new UpdateHostHbaDeviceAnswer (false , vmName , xmlConfig , isAttach );
772998 }
773999 }
1000+
1001+ protected Answer updateHostVHbaDevices (Command command , String vmName , String xmlConfig , boolean isAttach ) {
1002+ try {
1003+ UpdateHostVhbaDeviceCommand cmd = (UpdateHostVhbaDeviceCommand ) command ;
1004+ String vhbaName = cmd .getVhbaName ();
1005+
1006+ String vhbaXmlPath = String .format ("/tmp/vhba_device_%s.xml" , vmName );
1007+ try {
1008+ // XML 파일이 없을 경우에만 생성
1009+ File xmlFile = new File (vhbaXmlPath );
1010+ if (!xmlFile .exists ()) {
1011+ try (PrintWriter writer = new PrintWriter (vhbaXmlPath )) {
1012+ writer .write (xmlConfig );
1013+ }
1014+ logger .info ("Generated XML file: {} for VM: {}" , vhbaXmlPath , vmName );
1015+ }
1016+
1017+ Script virshCmd = new Script ("virsh" );
1018+ if (isAttach ) {
1019+ virshCmd .add ("attach-device" , vmName , vhbaXmlPath );
1020+ } else {
1021+ virshCmd .add ("detach-device" , vmName , vhbaXmlPath );
1022+ logger .info ("Executing detach command for VM: {} with XML: {}" , vmName , xmlConfig );
1023+ }
1024+
1025+ logger .info ("isAttach value: {}" , isAttach );
1026+
1027+ String result = virshCmd .execute ();
1028+
1029+ if (result != null ) {
1030+ String action = isAttach ? "attach" : "detach" ;
1031+ logger .error ("Failed to {} vHBA device: {}" , action , result );
1032+ return new com .cloud .agent .api .UpdateHostVhbaDeviceAnswer (false , vhbaName , vmName , xmlConfig , isAttach );
1033+ }
1034+
1035+ String action = isAttach ? "attached to" : "detached from" ;
1036+ logger .info ("Successfully {} vHBA device for VM {}" , action , vmName );
1037+ return new com .cloud .agent .api .UpdateHostVhbaDeviceAnswer (true , vhbaName , vmName , xmlConfig , isAttach );
1038+
1039+ } catch (Exception e ) {
1040+ String action = isAttach ? "attaching" : "detaching" ;
1041+ logger .error ("Error {} vHBA device: {}" , action , e .getMessage (), e );
1042+ return new com .cloud .agent .api .UpdateHostVhbaDeviceAnswer (false , vhbaName , vmName , xmlConfig , isAttach );
1043+ }
1044+ } catch (Exception e ) {
1045+ logger .error ("Error in updateHostVhbaDevices: " + e .getMessage (), e );
1046+ return new com .cloud .agent .api .UpdateHostVhbaDeviceAnswer (false , null , null , null , false );
1047+ }
1048+ }
7741049}
0 commit comments