4040import javax .naming .ConfigurationException ;
4141
4242import org .apache .cloudstack .storage .command .browser .ListRbdObjectsAnswer ;
43-
4443import org .apache .cloudstack .storage .command .browser .ListDataStoreObjectsAnswer ;
4544import org .apache .commons .collections .CollectionUtils ;
4645import org .apache .commons .lang3 .StringUtils ;
47- import org .apache .logging .log4j .Logger ;
4846import org .apache .logging .log4j .LogManager ;
49-
47+ import org .apache .logging .log4j .Logger ;
48+ import org .json .JSONArray ;
49+ import org .json .JSONObject ;
5050import com .cloud .agent .IAgentControl ;
5151import com .cloud .agent .api .Answer ;
5252import com .cloud .agent .api .Command ;
53+ import com .cloud .agent .api .ListHostDeviceAnswer ;
54+ import com .cloud .agent .api .ListHostLunDeviceAnswer ;
55+ import com .cloud .agent .api .ListHostUsbDeviceAnswer ;
5356import com .cloud .agent .api .StartupCommand ;
57+ import com .cloud .agent .api .UpdateHostUsbDeviceAnswer ;
5458import com .cloud .utils .net .NetUtils ;
5559import com .cloud .utils .script .OutputInterpreter ;
5660import com .cloud .utils .script .Script ;
57- import com .cloud .agent .api .ListHostDeviceAnswer ;
61+ // import com.cloud.agent.api.ListHostLunDeviceCommand ;
5862
5963public abstract class ServerResourceBase implements ServerResource {
6064 protected Logger logger = LogManager .getLogger (getClass ());
@@ -181,6 +185,95 @@ protected Answer listHostDevices() {
181185 return new ListHostDeviceAnswer (true , hostDevicesText );
182186 }
183187
188+ protected Answer listHostUsbDevices (Command command ) {
189+ List <String > hostDevicesText = new ArrayList <>();
190+ List <String > hostDevicesNames = new ArrayList <>();
191+ Script listCommand = new Script ("lsusb" );
192+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
193+ String result = listCommand .execute (parser );
194+ if (result == null && parser .getLines () != null ) {
195+ String [] lines = parser .getLines ().split ("\\ n" );
196+ for (String line : lines ) {
197+ String [] parts = line .split ("\\ s+" , 2 );
198+ if (parts .length >= 2 ) {
199+ hostDevicesNames .add (parts [0 ].trim ());
200+ hostDevicesText .add (parts [1 ].trim ());
201+ }
202+ }
203+ }
204+ return new ListHostUsbDeviceAnswer (true , hostDevicesNames , hostDevicesText );
205+ }
206+
207+ public Answer listHostLunDevices (Command command ) {
208+ try {
209+ List <String > hostDevicesNames = new ArrayList <>();
210+ List <String > hostDevicesText = new ArrayList <>();
211+ List <Boolean > hasPartitions = new ArrayList <>();
212+
213+ Script cmd = new Script ("/usr/bin/lsblk" );
214+ cmd .add ("--json" , "--paths" , "--output" , "NAME,TYPE,SIZE,MOUNTPOINT" );
215+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
216+ String result = cmd .execute (parser );
217+
218+ if (result != null ) {
219+ logger .error ("Failed to execute lsblk command: " + result );
220+ return new ListHostLunDeviceAnswer (false , hostDevicesNames , hostDevicesText , hasPartitions );
221+ }
222+
223+ JSONObject json = new JSONObject (parser .getLines ());
224+ JSONArray blockdevices = json .getJSONArray ("blockdevices" );
225+
226+ // multipath 서비스 상태 확인
227+ boolean isMultipathActive = checkMultipathStatus ();
228+
229+ for (int i = 0 ; i < blockdevices .length (); i ++) {
230+ JSONObject device = blockdevices .getJSONObject (i );
231+
232+ // 파티션이 아닌 디스크만 처리
233+ if (!"part" .equals (device .getString ("type" ))) {
234+ String name = device .getString ("name" );
235+ String size = device .getString ("size" );
236+
237+ // 파티션 존재 여부 확인
238+ JSONArray children = device .optJSONArray ("children" );
239+ boolean hasPartition = (children != null && children .length () > 0 );
240+
241+ StringBuilder info = new StringBuilder ();
242+ if (isMultipathActive && name .startsWith ("/dev/disk/by-path/" )) {
243+ info .append ("Multipath LUN Device: " ).append (name );
244+ } else {
245+ info .append ("LUN Device: " ).append (name );
246+ }
247+ info .append (" Size: " ).append (size );
248+ if (hasPartition ) {
249+ info .append (" (" ).append (children .length ()).append (" partitions)" );
250+ }
251+
252+ hostDevicesNames .add (name );
253+ hostDevicesText .add (info .toString ());
254+ hasPartitions .add (hasPartition );
255+
256+ if (logger .isDebugEnabled ()) {
257+ logger .debug ("Found LUN device: " + info .toString ());
258+ }
259+ }
260+ }
261+
262+ return new ListHostLunDeviceAnswer (true , hostDevicesNames , hostDevicesText , hasPartitions );
263+
264+ } catch (Exception e ) {
265+ logger .error ("Error listing LUN devices: " + e .getMessage (), e );
266+ return new ListHostLunDeviceAnswer (false , new ArrayList <>(), new ArrayList <>(), new ArrayList <>());
267+ }
268+ }
269+
270+ private boolean checkMultipathStatus () {
271+ Script cmd = new Script ("systemctl" );
272+ cmd .add ("is-active" , "multipathd" );
273+ String result = cmd .execute (null );
274+ return "active" .equals (result != null ? result .trim () : "" );
275+ }
276+
184277 protected Answer createImageRbd (String poolUuid , String skey , String authUserName , String host , String names , long sizes , String poolPath ) {
185278 createRBDSecretKeyFileIfNoExist (poolUuid , DEFAULT_LOCAL_STORAGE_PATH , skey );
186279 String cmdout = Script .runSimpleBashScript ("rbd -p " + poolPath + " --id " + authUserName + " -m " + host + " -K " + DEFAULT_LOCAL_STORAGE_PATH + poolUuid + " create -s " + (sizes * 1024 ) + " " + names );
@@ -512,4 +605,81 @@ public boolean start() {
512605 public boolean stop () {
513606 return true ;
514607 }
608+
609+ protected Answer updateHostUsbDevices (Command command , String vmName , String xmlConfig , boolean isAttach ) {
610+ String usbXmlPath = String .format ("/tmp/usb_device_%s.xml" , vmName );
611+ try {
612+ // XML 파일이 없을 경우에만 생성
613+ File xmlFile = new File (usbXmlPath );
614+ if (!xmlFile .exists ()) {
615+ try (PrintWriter writer = new PrintWriter (usbXmlPath )) {
616+ writer .write (xmlConfig );
617+ }
618+ logger .info ("Generated XML file: {} for VM: {}" , usbXmlPath , vmName );
619+ }
620+
621+ Script virshCmd = new Script ("virsh" );
622+ if (isAttach ) {
623+ virshCmd .add ("attach-device" , vmName , usbXmlPath );
624+ } else {
625+ virshCmd .add ("detach-device" , vmName , usbXmlPath );
626+ logger .info ("Executing detach command for VM: {} with XML: {}" , vmName , xmlConfig );
627+ }
628+
629+ String result = virshCmd .execute ();
630+
631+ if (result != null ) {
632+ String action = isAttach ? "attach" : "detach" ;
633+ logger .error ("Failed to {} USB device: {}" , action , result );
634+ return new UpdateHostUsbDeviceAnswer (false , vmName , xmlConfig , isAttach );
635+ }
636+
637+ String action = isAttach ? "attached to" : "detached from" ;
638+ logger .info ("Successfully {} USB device for VM {}" , action , vmName );
639+ return new UpdateHostUsbDeviceAnswer (true , vmName , xmlConfig , isAttach );
640+
641+ } catch (Exception e ) {
642+ String action = isAttach ? "attaching" : "detaching" ;
643+ logger .error ("Error {} USB device: {}" , action , e .getMessage (), e );
644+ return new UpdateHostUsbDeviceAnswer (false , vmName , xmlConfig , isAttach );
645+ }
646+ }
647+ protected Answer updateHostLunDevices (Command command , String vmName , String xmlConfig , boolean isAttach ) {
648+ String lunXmlPath = String .format ("/tmp/lun_device_%s.xml" , vmName );
649+ try {
650+ // XML 파일이 없을 경우에만 생성
651+ File xmlFile = new File (lunXmlPath );
652+ if (!xmlFile .exists ()) {
653+ try (PrintWriter writer = new PrintWriter (lunXmlPath )) {
654+ writer .write (xmlConfig );
655+ }
656+ logger .info ("Generated XML file: {} for VM: {}" , lunXmlPath , vmName );
657+ }
658+
659+ Script virshCmd = new Script ("virsh" );
660+ if (isAttach ) {
661+ virshCmd .add ("attach-device" , vmName , lunXmlPath );
662+ } else {
663+ virshCmd .add ("detach-device" , vmName , lunXmlPath );
664+ logger .info ("Executing detach command for VM: {} with XML: {}" , vmName , xmlConfig );
665+ }
666+
667+ String result = virshCmd .execute ();
668+
669+ if (result != null ) {
670+ String action = isAttach ? "attach" : "detach" ;
671+ logger .error ("Failed to {} USB device: {}" , action , result );
672+ return new UpdateHostUsbDeviceAnswer (false , vmName , xmlConfig , isAttach );
673+ }
674+
675+ String action = isAttach ? "attached to" : "detached from" ;
676+ logger .info ("Successfully {} USB device for VM {}" , action , vmName );
677+ return new UpdateHostUsbDeviceAnswer (true , vmName , xmlConfig , isAttach );
678+
679+ } catch (Exception e ) {
680+ String action = isAttach ? "attaching" : "detaching" ;
681+ logger .error ("Error {} USB device: {}" , action , e .getMessage (), e );
682+ return new UpdateHostUsbDeviceAnswer (false , vmName , xmlConfig , isAttach );
683+ }
684+ }
515685}
0 commit comments