fixing iscsi mapping for library

This commit is contained in:
2025-12-22 19:50:28 +00:00
parent 6a5ead9dbf
commit 4c3ea0059d
3 changed files with 175 additions and 21 deletions

View File

@@ -176,6 +176,10 @@ func (a *App) routes() {
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLMediaChangers(w, r) },
nil, nil, nil, nil,
))
a.mux.HandleFunc("/api/v1/vtl/devices/iscsi", methodHandler(
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLDevicesForISCSI(w, r) },
nil, nil, nil, nil,
))
a.mux.HandleFunc("/api/v1/vtl/changer/status", methodHandler(
func(w http.ResponseWriter, r *http.Request) { a.handleGetVTLMediaChangerStatus(w, r) },
nil, nil, nil, nil,

View File

@@ -285,3 +285,42 @@ func (a *App) handleEjectTape(w http.ResponseWriter, r *http.Request) {
"drive_id": fmt.Sprintf("%d", req.DriveID),
})
}
// handleListVTLDevicesForISCSI returns all tape devices (drives and medium changers) for iSCSI passthrough
func (a *App) handleListVTLDevicesForISCSI(w http.ResponseWriter, r *http.Request) {
devices := []map[string]interface{}{}
// Get drives
drives, err := a.vtlService.ListDrives()
if err == nil {
for _, drive := range drives {
devices = append(devices, map[string]interface{}{
"type": "drive",
"device": drive.Device,
"id": drive.ID,
"library_id": drive.LibraryID,
"vendor": drive.Vendor,
"product": drive.Product,
"description": fmt.Sprintf("Tape Drive %d (Library %d) - %s %s", drive.ID, drive.LibraryID, drive.Vendor, drive.Product),
})
}
}
// Get medium changers
changers, err := a.vtlService.ListMediaChangers()
if err == nil {
for _, changer := range changers {
devices = append(devices, map[string]interface{}{
"type": "changer",
"device": changer.Device,
"id": changer.ID,
"library_id": changer.LibraryID,
"slots": changer.Slots,
"drives": changer.Drives,
"description": fmt.Sprintf("Media Changer (Library %d) - %d slots, %d drives", changer.LibraryID, changer.Slots, changer.Drives),
})
}
}
writeJSON(w, http.StatusOK, devices)
}

View File

@@ -85,13 +85,17 @@
<!-- Add LUN Modal -->
<div id="add-lun-modal" class="hidden fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div class="bg-slate-800 rounded-lg border border-slate-700 p-6 max-w-md w-full mx-4">
<div class="bg-slate-800 rounded-lg border border-slate-700 p-6 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto">
<h3 class="text-xl font-semibold text-white mb-4">Add LUN to Target</h3>
<form id="add-lun-form" onsubmit="addLUN(event)" class="space-y-4">
<input type="hidden" name="target_id" id="lun-target-id">
<input type="hidden" name="target_type" id="lun-target-type" value="disk">
<!-- Disk Mode: Storage Volume -->
<div id="lun-disk-mode" class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-300 mb-1">Select Storage Volume</label>
<select name="zvol" id="lun-zvol-select" required class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
<select name="zvol" id="lun-zvol-select" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
<option value="">Loading volumes...</option>
</select>
<p class="text-xs text-slate-400 mt-1">Or enter manually below</p>
@@ -100,6 +104,24 @@
<label class="block text-sm font-medium text-slate-300 mb-1">Or Enter Volume Name Manually</label>
<input type="text" name="zvol-manual" id="lun-zvol-manual" placeholder="pool/zvol" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
</div>
</div>
<!-- Tape Mode: Tape Devices -->
<div id="lun-tape-mode" class="space-y-4 hidden">
<div>
<label class="block text-sm font-medium text-slate-300 mb-1">Select Tape Device</label>
<select name="device" id="lun-device-select" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
<option value="">Loading devices...</option>
</select>
<p class="text-xs text-slate-400 mt-1">Physical medium changer or tape drive device</p>
</div>
<div>
<label class="block text-sm font-medium text-slate-300 mb-1">Or Enter Device Path Manually</label>
<input type="text" name="device-manual" id="lun-device-manual" placeholder="/dev/sg0 or /dev/st0" class="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-600">
<p class="text-xs text-slate-400 mt-1">Medium changer: /dev/sg* | Tape drive: /dev/st* or /dev/nst*</p>
</div>
</div>
<div class="flex gap-2 justify-end">
<button type="button" onclick="closeModal('add-lun-modal')" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded text-sm">
Cancel
@@ -212,10 +234,10 @@ async function loadISCSITargets(type = 'disk') {
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-2">
<span class="text-slate-400">LUN ${lun.id}:</span>
<span class="text-slate-300 font-mono">${lun.zvol}</span>
<span class="text-slate-300 font-mono">${lun.zvol || lun.device || 'N/A'}</span>
</div>
<div class="flex items-center gap-2">
<span class="text-slate-300">${formatBytes(lun.size)}</span>
${lun.size > 0 ? `<span class="text-slate-300">${formatBytes(lun.size)}</span>` : ''}
<button onclick="removeLUN('${target.id}', ${lun.id})" class="ml-2 px-2 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs" title="Remove LUN">
Remove
</button>
@@ -281,12 +303,38 @@ function showCreateISCSIModal(type = 'disk') {
document.getElementById('create-iscsi-modal').classList.remove('hidden');
}
async function showAddLUNModal(targetId) {
async function showAddLUNModal(targetId, targetType) {
document.getElementById('lun-target-id').value = targetId;
document.getElementById('add-lun-modal').classList.remove('hidden');
// Load available storage volumes
// Use provided target type, or fallback to current tab
const type = targetType || (currentTab === 'tape' ? 'tape' : 'disk');
document.getElementById('lun-target-type').value = type;
// Show/hide appropriate sections
const diskMode = document.getElementById('lun-disk-mode');
const tapeMode = document.getElementById('lun-tape-mode');
const zvolSelect = document.getElementById('lun-zvol-select');
const deviceSelect = document.getElementById('lun-device-select');
if (targetType === 'tape') {
diskMode.classList.add('hidden');
tapeMode.classList.remove('hidden');
zvolSelect.removeAttribute('required');
deviceSelect.setAttribute('required', 'required');
// Load tape devices
await loadTapeDevicesForLUN();
} else {
diskMode.classList.remove('hidden');
tapeMode.classList.add('hidden');
zvolSelect.setAttribute('required', 'required');
deviceSelect.removeAttribute('required');
// Load storage volumes
await loadZVOLsForLUN();
}
document.getElementById('add-lun-modal').classList.remove('hidden');
}
async function loadZVOLsForLUN() {
@@ -339,6 +387,56 @@ async function loadZVOLsForLUN() {
}
}
async function loadTapeDevicesForLUN() {
try {
const res = await fetch('/api/v1/vtl/devices/iscsi', { headers: getAuthHeaders() });
const selectEl = document.getElementById('lun-device-select');
if (!res.ok) {
selectEl.innerHTML = '<option value="">Error loading devices</option>';
return;
}
const devices = await res.json();
if (!Array.isArray(devices)) {
selectEl.innerHTML = '<option value="">No devices available</option>';
return;
}
if (devices.length === 0) {
selectEl.innerHTML = '<option value="">No tape devices found. Make sure mhvtl is running.</option>';
return;
}
// Clear and populate dropdown
selectEl.innerHTML = '<option value="">Select a device...</option>';
devices.forEach(device => {
const option = document.createElement('option');
option.value = device.device;
option.textContent = device.description || `${device.type}: ${device.device}`;
selectEl.appendChild(option);
});
// Update manual input when dropdown changes
selectEl.addEventListener('change', function() {
if (this.value) {
document.getElementById('lun-device-manual').value = this.value;
}
});
// Update dropdown when manual input changes
document.getElementById('lun-device-manual').addEventListener('input', function() {
if (this.value && !selectEl.value) {
// Allow manual entry even if not in dropdown
}
});
} catch (err) {
console.error('Error loading tape devices:', err);
document.getElementById('lun-device-select').innerHTML = '<option value="">Error loading devices</option>';
}
}
async function showConnectionInstructions(targetId) {
try {
const res = await fetch(`/api/v1/iscsi/targets/${targetId}/connection`, { headers: getAuthHeaders() });
@@ -419,15 +517,27 @@ async function addLUN(e) {
let requestBody = {};
if (targetType === 'tape') {
// Tape mode: use device
const device = document.getElementById('lun-device-input').value.trim();
// Tape mode: use device (medium changer or tape drive)
const deviceSelect = document.getElementById('lun-device-select').value;
const deviceManual = document.getElementById('lun-device-manual').value.trim();
const device = deviceSelect || deviceManual;
if (!device) {
alert('Please enter a tape device path');
alert('Please select or enter a tape device path (e.g., /dev/sg0 for medium changer or /dev/st0 for tape drive)');
return;
}
// Determine backstore type based on device
let backstore = 'pscsi'; // Default for tape devices
if (device.startsWith('/dev/sg')) {
backstore = 'pscsi'; // Medium changer
} else if (device.startsWith('/dev/st') || device.startsWith('/dev/nst')) {
backstore = 'pscsi'; // Tape drive
}
requestBody = {
device: device,
backstore: 'pscsi'
backstore: backstore
};
} else {
// Disk mode: use ZVOL
@@ -456,7 +566,8 @@ async function addLUN(e) {
e.target.reset();
document.getElementById('lun-zvol-select').innerHTML = '<option value="">Loading volumes...</option>';
document.getElementById('lun-zvol-manual').value = '';
document.getElementById('lun-device-input').value = '';
document.getElementById('lun-device-select').innerHTML = '<option value="">Loading devices...</option>';
document.getElementById('lun-device-manual').value = '';
// Reload targets for the current tab
loadISCSITargets(targetType);
alert('LUN added successfully');