Complete VTL implementation with SCST and mhVTL integration
- Installed and configured SCST with 7 handlers - Installed and configured mhVTL with 2 Quantum libraries and 8 LTO-8 drives - Implemented all VTL API endpoints (8/9 working) - Fixed NULL device_path handling in drives endpoint - Added comprehensive error handling and validation - Implemented async tape load/unload operations - Created SCST installation guide for Ubuntu 24.04 - Created mhVTL installation and configuration guide - Added VTL testing guide and automated test scripts - All core API tests passing (89% success rate) Infrastructure status: - PostgreSQL: Configured with proper permissions - SCST: Active with kernel module loaded - mhVTL: 2 libraries (Quantum Scalar i500, Scalar i40) - mhVTL: 8 drives (all Quantum ULTRIUM-HH8 LTO-8) - Calypso API: 8/9 VTL endpoints functional Documentation added: - src/srs-technical-spec-documents/scst-installation.md - src/srs-technical-spec-documents/mhvtl-installation.md - VTL-TESTING-GUIDE.md - scripts/test-vtl.sh Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
50
backend/internal/common/database/database.go
Normal file
50
backend/internal/common/database/database.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/atlasos/calypso/internal/common/config"
|
||||
)
|
||||
|
||||
// DB wraps sql.DB with additional methods
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// NewConnection creates a new database connection
|
||||
func NewConnection(cfg config.DatabaseConfig) (*DB, error) {
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Database, cfg.SSLMode,
|
||||
)
|
||||
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
||||
}
|
||||
|
||||
// Configure connection pool
|
||||
db.SetMaxOpenConns(cfg.MaxConnections)
|
||||
db.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
|
||||
|
||||
// Test connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (db *DB) Close() error {
|
||||
return db.DB.Close()
|
||||
}
|
||||
|
||||
167
backend/internal/common/database/migrations.go
Normal file
167
backend/internal/common/database/migrations.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/atlasos/calypso/internal/common/logger"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
// RunMigrations executes all pending database migrations
|
||||
func RunMigrations(ctx context.Context, db *DB) error {
|
||||
log := logger.NewLogger("migrations")
|
||||
|
||||
// Create migrations table if it doesn't exist
|
||||
if err := createMigrationsTable(ctx, db); err != nil {
|
||||
return fmt.Errorf("failed to create migrations table: %w", err)
|
||||
}
|
||||
|
||||
// Get all migration files
|
||||
migrations, err := getMigrationFiles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read migration files: %w", err)
|
||||
}
|
||||
|
||||
// Get applied migrations
|
||||
applied, err := getAppliedMigrations(ctx, db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get applied migrations: %w", err)
|
||||
}
|
||||
|
||||
// Apply pending migrations
|
||||
for _, migration := range migrations {
|
||||
if applied[migration.Version] {
|
||||
log.Debug("Migration already applied", "version", migration.Version)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info("Applying migration", "version", migration.Version, "name", migration.Name)
|
||||
|
||||
// Read migration SQL
|
||||
sql, err := migrationsFS.ReadFile(fmt.Sprintf("migrations/%s", migration.Filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read migration file %s: %w", migration.Filename, err)
|
||||
}
|
||||
|
||||
// Execute migration in a transaction
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("failed to execute migration %s: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
// Record migration
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"INSERT INTO schema_migrations (version, applied_at) VALUES ($1, NOW())",
|
||||
migration.Version,
|
||||
); err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("failed to record migration %s: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit migration %s: %w", migration.Version, err)
|
||||
}
|
||||
|
||||
log.Info("Migration applied successfully", "version", migration.Version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Migration represents a database migration
|
||||
type Migration struct {
|
||||
Version int
|
||||
Name string
|
||||
Filename string
|
||||
}
|
||||
|
||||
// getMigrationFiles returns all migration files sorted by version
|
||||
func getMigrationFiles() ([]Migration, error) {
|
||||
entries, err := fs.ReadDir(migrationsFS, "migrations")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var migrations []Migration
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := entry.Name()
|
||||
if !strings.HasSuffix(filename, ".sql") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse version from filename: 001_initial_schema.sql
|
||||
parts := strings.SplitN(filename, "_", 2)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
version, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(parts[1], ".sql")
|
||||
migrations = append(migrations, Migration{
|
||||
Version: version,
|
||||
Name: name,
|
||||
Filename: filename,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by version
|
||||
sort.Slice(migrations, func(i, j int) bool {
|
||||
return migrations[i].Version < migrations[j].Version
|
||||
})
|
||||
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
// createMigrationsTable creates the schema_migrations table
|
||||
func createMigrationsTable(ctx context.Context, db *DB) error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
)
|
||||
`
|
||||
_, err := db.ExecContext(ctx, query)
|
||||
return err
|
||||
}
|
||||
|
||||
// getAppliedMigrations returns a map of applied migration versions
|
||||
func getAppliedMigrations(ctx context.Context, db *DB) (map[int]bool, error) {
|
||||
rows, err := db.QueryContext(ctx, "SELECT version FROM schema_migrations ORDER BY version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
applied := make(map[int]bool)
|
||||
for rows.Next() {
|
||||
var version int
|
||||
if err := rows.Scan(&version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applied[version] = true
|
||||
}
|
||||
|
||||
return applied, rows.Err()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
-- AtlasOS - Calypso
|
||||
-- Initial Database Schema
|
||||
-- Version: 1.0
|
||||
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
last_login_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Roles table
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Permissions table
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
resource VARCHAR(100) NOT NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- User roles junction table
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
assigned_by UUID REFERENCES users(id),
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
|
||||
-- Role permissions junction table
|
||||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
|
||||
granted_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (role_id, permission_id)
|
||||
);
|
||||
|
||||
-- Sessions table
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
last_activity_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Audit log table
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id),
|
||||
username VARCHAR(255),
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource_type VARCHAR(100) NOT NULL,
|
||||
resource_id VARCHAR(255),
|
||||
method VARCHAR(10),
|
||||
path TEXT,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
request_body JSONB,
|
||||
response_status INTEGER,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tasks table (for async operations)
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type VARCHAR(100) NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
progress INTEGER NOT NULL DEFAULT 0,
|
||||
message TEXT,
|
||||
error_message TEXT,
|
||||
created_by UUID REFERENCES users(id),
|
||||
started_at TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
metadata JSONB
|
||||
);
|
||||
|
||||
-- Alerts table
|
||||
CREATE TABLE IF NOT EXISTS alerts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
severity VARCHAR(20) NOT NULL,
|
||||
source VARCHAR(100) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
resource_type VARCHAR(100),
|
||||
resource_id VARCHAR(255),
|
||||
is_acknowledged BOOLEAN NOT NULL DEFAULT false,
|
||||
acknowledged_by UUID REFERENCES users(id),
|
||||
acknowledged_at TIMESTAMP,
|
||||
resolved_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
metadata JSONB
|
||||
);
|
||||
|
||||
-- System configuration table
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key VARCHAR(255) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
is_encrypted BOOLEAN NOT NULL DEFAULT false,
|
||||
updated_by UUID REFERENCES users(id),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_alerts_severity ON alerts(severity);
|
||||
CREATE INDEX IF NOT EXISTS idx_alerts_acknowledged ON alerts(is_acknowledged);
|
||||
CREATE INDEX IF NOT EXISTS idx_alerts_created_at ON alerts(created_at);
|
||||
|
||||
-- Insert default system roles
|
||||
INSERT INTO roles (name, description, is_system) VALUES
|
||||
('admin', 'Full system access and configuration', true),
|
||||
('operator', 'Day-to-day operations and monitoring', true),
|
||||
('readonly', 'Read-only access for monitoring and reporting', true)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
-- Insert default permissions
|
||||
INSERT INTO permissions (name, resource, action, description) VALUES
|
||||
-- System permissions
|
||||
('system:read', 'system', 'read', 'View system information'),
|
||||
('system:write', 'system', 'write', 'Modify system configuration'),
|
||||
('system:manage', 'system', 'manage', 'Full system management'),
|
||||
|
||||
-- Storage permissions
|
||||
('storage:read', 'storage', 'read', 'View storage information'),
|
||||
('storage:write', 'storage', 'write', 'Modify storage configuration'),
|
||||
('storage:manage', 'storage', 'manage', 'Full storage management'),
|
||||
|
||||
-- Tape permissions
|
||||
('tape:read', 'tape', 'read', 'View tape library information'),
|
||||
('tape:write', 'tape', 'write', 'Perform tape operations'),
|
||||
('tape:manage', 'tape', 'manage', 'Full tape management'),
|
||||
|
||||
-- iSCSI permissions
|
||||
('iscsi:read', 'iscsi', 'read', 'View iSCSI configuration'),
|
||||
('iscsi:write', 'iscsi', 'write', 'Modify iSCSI configuration'),
|
||||
('iscsi:manage', 'iscsi', 'manage', 'Full iSCSI management'),
|
||||
|
||||
-- IAM permissions
|
||||
('iam:read', 'iam', 'read', 'View users and roles'),
|
||||
('iam:write', 'iam', 'write', 'Modify users and roles'),
|
||||
('iam:manage', 'iam', 'manage', 'Full IAM management'),
|
||||
|
||||
-- Audit permissions
|
||||
('audit:read', 'audit', 'read', 'View audit logs'),
|
||||
|
||||
-- Monitoring permissions
|
||||
('monitoring:read', 'monitoring', 'read', 'View monitoring data'),
|
||||
('monitoring:write', 'monitoring', 'write', 'Acknowledge alerts')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
-- Assign permissions to roles
|
||||
-- Admin gets all permissions
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r, permissions p
|
||||
WHERE r.name = 'admin'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Operator gets read and write (but not manage) for most resources
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r, permissions p
|
||||
WHERE r.name = 'operator'
|
||||
AND p.action IN ('read', 'write')
|
||||
AND p.resource IN ('storage', 'tape', 'iscsi', 'monitoring')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ReadOnly gets only read permissions
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r, permissions p
|
||||
WHERE r.name = 'readonly'
|
||||
AND p.action = 'read'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
-- AtlasOS - Calypso
|
||||
-- Storage and Tape Component Schema
|
||||
-- Version: 2.0
|
||||
|
||||
-- Disk repositories table
|
||||
CREATE TABLE IF NOT EXISTS disk_repositories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
volume_group VARCHAR(255) NOT NULL,
|
||||
logical_volume VARCHAR(255) NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
used_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
filesystem_type VARCHAR(50),
|
||||
mount_point TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
warning_threshold_percent INTEGER NOT NULL DEFAULT 80,
|
||||
critical_threshold_percent INTEGER NOT NULL DEFAULT 90,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Physical disks table
|
||||
CREATE TABLE IF NOT EXISTS physical_disks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
device_path VARCHAR(255) NOT NULL UNIQUE,
|
||||
vendor VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
serial_number VARCHAR(255),
|
||||
size_bytes BIGINT NOT NULL,
|
||||
sector_size INTEGER,
|
||||
is_ssd BOOLEAN NOT NULL DEFAULT false,
|
||||
health_status VARCHAR(50) NOT NULL DEFAULT 'unknown',
|
||||
health_details JSONB,
|
||||
is_used BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Volume groups table
|
||||
CREATE TABLE IF NOT EXISTS volume_groups (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
free_bytes BIGINT NOT NULL,
|
||||
physical_volumes TEXT[],
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- SCST iSCSI targets table
|
||||
CREATE TABLE IF NOT EXISTS scst_targets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
iqn VARCHAR(512) NOT NULL UNIQUE,
|
||||
target_type VARCHAR(50) NOT NULL, -- 'disk', 'vtl', 'physical_tape'
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
single_initiator_only BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- SCST LUN mappings table
|
||||
CREATE TABLE IF NOT EXISTS scst_luns (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
target_id UUID NOT NULL REFERENCES scst_targets(id) ON DELETE CASCADE,
|
||||
lun_number INTEGER NOT NULL,
|
||||
device_name VARCHAR(255) NOT NULL,
|
||||
device_path VARCHAR(512) NOT NULL,
|
||||
handler_type VARCHAR(50) NOT NULL, -- 'vdisk', 'sg', 'tape'
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(target_id, lun_number)
|
||||
);
|
||||
|
||||
-- SCST initiator groups table
|
||||
CREATE TABLE IF NOT EXISTS scst_initiator_groups (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
target_id UUID NOT NULL REFERENCES scst_targets(id) ON DELETE CASCADE,
|
||||
group_name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(target_id, group_name)
|
||||
);
|
||||
|
||||
-- SCST initiators table
|
||||
CREATE TABLE IF NOT EXISTS scst_initiators (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
group_id UUID NOT NULL REFERENCES scst_initiator_groups(id) ON DELETE CASCADE,
|
||||
iqn VARCHAR(512) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(group_id, iqn)
|
||||
);
|
||||
|
||||
-- Physical tape libraries table
|
||||
CREATE TABLE IF NOT EXISTS physical_tape_libraries (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
serial_number VARCHAR(255),
|
||||
vendor VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
changer_device_path VARCHAR(512),
|
||||
changer_stable_path VARCHAR(512),
|
||||
slot_count INTEGER,
|
||||
drive_count INTEGER,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
discovered_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
last_inventory_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Physical tape drives table
|
||||
CREATE TABLE IF NOT EXISTS physical_tape_drives (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
library_id UUID NOT NULL REFERENCES physical_tape_libraries(id) ON DELETE CASCADE,
|
||||
drive_number INTEGER NOT NULL,
|
||||
device_path VARCHAR(512),
|
||||
stable_path VARCHAR(512),
|
||||
vendor VARCHAR(255),
|
||||
model VARCHAR(255),
|
||||
serial_number VARCHAR(255),
|
||||
drive_type VARCHAR(50), -- 'LTO-8', 'LTO-9', etc.
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'unknown', -- 'idle', 'loading', 'ready', 'error'
|
||||
current_tape_barcode VARCHAR(255),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(library_id, drive_number)
|
||||
);
|
||||
|
||||
-- Physical tape slots table
|
||||
CREATE TABLE IF NOT EXISTS physical_tape_slots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
library_id UUID NOT NULL REFERENCES physical_tape_libraries(id) ON DELETE CASCADE,
|
||||
slot_number INTEGER NOT NULL,
|
||||
barcode VARCHAR(255),
|
||||
tape_present BOOLEAN NOT NULL DEFAULT false,
|
||||
tape_type VARCHAR(50),
|
||||
last_updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(library_id, slot_number)
|
||||
);
|
||||
|
||||
-- Virtual tape libraries table
|
||||
CREATE TABLE IF NOT EXISTS virtual_tape_libraries (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
mhvtl_library_id INTEGER,
|
||||
backing_store_path TEXT NOT NULL,
|
||||
slot_count INTEGER NOT NULL DEFAULT 10,
|
||||
drive_count INTEGER NOT NULL DEFAULT 2,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Virtual tape drives table
|
||||
CREATE TABLE IF NOT EXISTS virtual_tape_drives (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
library_id UUID NOT NULL REFERENCES virtual_tape_libraries(id) ON DELETE CASCADE,
|
||||
drive_number INTEGER NOT NULL,
|
||||
device_path VARCHAR(512),
|
||||
stable_path VARCHAR(512),
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'idle',
|
||||
current_tape_id UUID,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(library_id, drive_number)
|
||||
);
|
||||
|
||||
-- Virtual tapes table
|
||||
CREATE TABLE IF NOT EXISTS virtual_tapes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
library_id UUID NOT NULL REFERENCES virtual_tape_libraries(id) ON DELETE CASCADE,
|
||||
barcode VARCHAR(255) NOT NULL,
|
||||
slot_number INTEGER,
|
||||
image_file_path TEXT NOT NULL,
|
||||
size_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
used_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
tape_type VARCHAR(50) NOT NULL DEFAULT 'LTO-8',
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'idle', -- 'idle', 'in_drive', 'exported'
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(library_id, barcode)
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_disk_repositories_name ON disk_repositories(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_disk_repositories_active ON disk_repositories(is_active);
|
||||
CREATE INDEX IF NOT EXISTS idx_physical_disks_device_path ON physical_disks(device_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_scst_targets_iqn ON scst_targets(iqn);
|
||||
CREATE INDEX IF NOT EXISTS idx_scst_targets_type ON scst_targets(target_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_scst_luns_target_id ON scst_luns(target_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_scst_initiators_group_id ON scst_initiators(group_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_physical_tape_libraries_name ON physical_tape_libraries(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_physical_tape_drives_library_id ON physical_tape_drives(library_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_physical_tape_slots_library_id ON physical_tape_slots(library_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_virtual_tape_libraries_name ON virtual_tape_libraries(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_virtual_tape_drives_library_id ON virtual_tape_drives(library_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_library_id ON virtual_tapes(library_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_barcode ON virtual_tapes(barcode);
|
||||
|
||||
Reference in New Issue
Block a user