This commit is contained in:
2026-02-14 05:36:00 +08:00
commit d7cd899983
37 changed files with 4169 additions and 0 deletions

794
internal/db/db.go Normal file
View File

@@ -0,0 +1,794 @@
package db
import (
"context"
"database/sql"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
"sunhpc/internal/log"
_ "github.com/mattn/go-sqlite3"
)
// 全局单例
var (
globalDB *DB
once sync.Once
)
// DB 核心数据库类 - 对应Rocks的Database类
type DB struct {
// 连接参数
dbUser string
dbPasswd string
dbHost string
dbName string
dbPath string
dbSocket string
verbose bool
forceInit bool
// 连接对象
engine *sql.DB // 连接池
conn *sql.Conn // 当前连接
results *sql.Rows // 当前结果集
// 线程本地存储模拟
sessions sync.Map
mu sync.RWMutex
}
// NewDB 创建新实例
func NewDB() *DB {
return &DB{
dbUser: "",
dbPasswd: "",
dbHost: "localhost",
dbName: "sunhpc",
dbPath: "/var/lib/sunhpc",
dbSocket: "/var/lib/sunhpc/mysql/mysql.sock",
verbose: false,
}
}
// ==================== 连接参数设置/获取 ====================
func (db *DB) SetDBPasswd(passwd string) {
db.mu.Lock()
defer db.mu.Unlock()
db.dbPasswd = passwd
}
func (db *DB) GetDBPasswd() string {
db.mu.RLock()
if db.dbPasswd != "" {
db.mu.RUnlock()
return db.dbPasswd
}
db.mu.RUnlock()
db.mu.Lock()
defer db.mu.Unlock()
// 从配置文件读取密码
username := db.GetDBUsername()
var filename string
switch username {
case "root":
filename = "/root/.sunhpc.my.cnf"
default:
filename = fmt.Sprintf("/home/%s/.sunhpc.my.cnf", username)
}
data, err := ioutil.ReadFile(filename)
if err != nil {
return ""
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
parts := strings.Split(line, "=")
if len(parts) == 2 && strings.TrimSpace(parts[0]) == "password" {
db.dbPasswd = strings.TrimSpace(parts[1])
break
}
}
return db.dbPasswd
}
func (db *DB) SetDBUsername(name string) {
db.mu.Lock()
defer db.mu.Unlock()
db.dbUser = name
}
func (db *DB) GetDBUsername() string {
db.mu.RLock()
if db.dbUser != "" {
db.mu.RUnlock()
return db.dbUser
}
db.mu.RUnlock()
db.mu.Lock()
defer db.mu.Unlock()
db.dbUser = os.Getenv("USER")
return db.dbUser
}
func (db *DB) SetDBHostname(host string) {
db.mu.Lock()
defer db.mu.Unlock()
db.dbHost = host
}
func (db *DB) GetDBHostname() string {
db.mu.RLock()
defer db.mu.RUnlock()
return db.dbHost
}
func (db *DB) SetDBName(name string) {
db.mu.Lock()
defer db.mu.Unlock()
db.dbName = name
}
func (db *DB) GetDBName() string {
db.mu.RLock()
defer db.mu.RUnlock()
return db.dbName
}
func (db *DB) SetDBPath(path string) {
db.mu.Lock()
defer db.mu.Unlock()
db.dbPath = path
}
func (db *DB) GetDBPath() string {
db.mu.RLock()
defer db.mu.RUnlock()
return db.dbPath
}
func (db *DB) SetVerbose(verbose bool) {
db.mu.Lock()
defer db.mu.Unlock()
db.verbose = verbose
}
func (db *DB) SetForceInit(force bool) {
db.mu.Lock()
defer db.mu.Unlock()
db.forceInit = force
}
// ==================== 连接管理 ====================
// Connect 连接数据库
func (db *DB) Connect() error {
log.Debug("连接数据库...")
db.mu.Lock()
defer db.mu.Unlock()
log.Debug("检查 SUNHPCDEBUG 环境变量...")
if os.Getenv("SUNHPCDEBUG") != "" {
db.verbose = true
}
// 使用SQLite
dbFullPath := filepath.Join(db.dbPath, db.dbName+".db")
log.Debugf("数据库路径: %s", dbFullPath)
// 确保目录存在
log.Debug("确保数据库目录存在...")
os.MkdirAll(db.dbPath, 0755)
engine, err := sql.Open("sqlite3", dbFullPath+"?_foreign_keys=on&_journal_mode=WAL")
log.Debugf("打开数据库连接...")
if err != nil {
return fmt.Errorf("打开数据库失败: %v", err)
}
engine.SetMaxOpenConns(10)
engine.SetMaxIdleConns(5)
engine.SetConnMaxLifetime(time.Hour)
db.engine = engine
conn, err := engine.Conn(context.Background())
log.Debugf("获取数据库连接...")
if err != nil {
return fmt.Errorf("获取连接失败: %v", err)
}
db.conn = conn
// 初始化数据库表
if err := db.initSchema(); err != nil {
return fmt.Errorf("初始化数据库表失败: %v", err)
}
if db.verbose {
log.Infof("数据库连接成功: %s", dbFullPath)
}
return nil
}
// initSchema 初始化数据库表结构 - 所有表定义在这里
func (db *DB) initSchema() error {
log.Debug("初始化数据库表结构...")
// 检查 nodes 表是否已存在
var tableName string
err := db.engine.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='nodes'").Scan(&tableName)
if err == nil && !db.forceInit {
log.Debug("数据库表已存在,跳过初始化")
return nil
}
if db.forceInit {
log.Warn("强制重新初始化数据库表结构...")
} else {
log.Info("首次初始化数据库表结构...")
}
// 如果强制初始化,先删除所有表
if db.forceInit {
log.Info("删除现有表...")
dropSQLs := []string{
`DROP TABLE IF EXISTS resolvechain;`,
`DROP TABLE IF EXISTS hostselections;`,
`DROP TABLE IF EXISTS attributes;`,
`DROP TABLE IF EXISTS catindexes;`,
`DROP TABLE IF EXISTS categories;`,
`DROP TABLE IF EXISTS node_attrs;`,
`DROP TABLE IF EXISTS aliases;`,
`DROP TABLE IF EXISTS networks;`,
`DROP TABLE IF EXISTS subnets;`,
`DROP TABLE IF EXISTS software_installs;`,
`DROP TABLE IF EXISTS memberships;`,
`DROP TABLE IF EXISTS appliances;`,
`DROP TABLE IF EXISTS nodes;`,
}
for _, sql := range dropSQLs {
if _, err := db.engine.Exec(sql); err != nil {
log.Warnf("删除表失败: %v", err)
}
}
log.Info("现有表已删除")
}
// 开启事务
tx, err := db.engine.Begin()
if err != nil {
return fmt.Errorf("开启事务失败: %v", err)
}
// 使用exec执行每条SQL单独执行
sqls := []string{
// 创建表 - 注意创建顺序(先创建主表,再创建有外键的表)
`CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
rack INTEGER DEFAULT 0,
rank INTEGER DEFAULT 0,
membership_id INTEGER,
cpus INTEGER DEFAULT 0,
memory INTEGER DEFAULT 0,
disk INTEGER DEFAULT 0,
os TEXT,
kernel TEXT,
last_state_change DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`,
`CREATE TABLE IF NOT EXISTS appliances (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
node_type TEXT DEFAULT 'compute'
);`,
`CREATE TABLE IF NOT EXISTS memberships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
appliance_id INTEGER,
FOREIGN KEY (appliance_id) REFERENCES appliances(id) ON DELETE SET NULL
);`,
`CREATE TABLE IF NOT EXISTS subnets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
network TEXT,
netmask TEXT,
gateway TEXT,
dns_zone TEXT,
is_private INTEGER DEFAULT 1
);`,
`CREATE TABLE IF NOT EXISTS networks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
name TEXT,
ip TEXT UNIQUE,
mac TEXT UNIQUE,
subnet_id INTEGER,
interface TEXT DEFAULT 'eth0',
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (subnet_id) REFERENCES subnets(id) ON DELETE SET NULL
);`,
`CREATE TABLE IF NOT EXISTS aliases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,
UNIQUE(node_id, name)
);`,
`CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);`,
`CREATE TABLE IF NOT EXISTS catindexes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category_id INTEGER NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(name, category_id)
);`,
`CREATE TABLE IF NOT EXISTS attributes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
attr TEXT NOT NULL,
value TEXT,
category_id INTEGER NOT NULL,
catindex_id INTEGER NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
FOREIGN KEY (catindex_id) REFERENCES catindexes(id) ON DELETE CASCADE,
UNIQUE(attr, category_id, catindex_id)
);`,
`CREATE TABLE IF NOT EXISTS node_attrs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
attr TEXT NOT NULL,
value TEXT,
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,
UNIQUE(node_id, attr)
);`,
`CREATE TABLE IF NOT EXISTS hostselections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
host_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
selection TEXT NOT NULL,
FOREIGN KEY (host_id) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(host_id, category_id, selection)
);`,
`CREATE TABLE IF NOT EXISTS resolvechain (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
precedence INTEGER NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
UNIQUE(category_id, precedence)
);`,
`CREATE TABLE IF NOT EXISTS software_installs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
version TEXT,
install_type TEXT,
node_id INTEGER,
status TEXT,
installed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
installed_by TEXT,
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE SET NULL
);`,
// 创建索引
`CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);`,
`CREATE INDEX IF NOT EXISTS idx_networks_ip ON networks(ip);`,
`CREATE INDEX IF NOT EXISTS idx_networks_mac ON networks(mac);`,
`CREATE INDEX IF NOT EXISTS idx_attributes_lookup ON attributes(attr, category_id, catindex_id);`,
`CREATE INDEX IF NOT EXISTS idx_node_attrs_lookup ON node_attrs(node_id, attr);`,
`CREATE INDEX IF NOT EXISTS idx_hostselections_host ON hostselections(host_id);`,
`CREATE INDEX IF NOT EXISTS idx_resolvechain_precedence ON resolvechain(precedence);`,
}
// 逐条执行SQL
for i, sql := range sqls {
if strings.TrimSpace(sql) == "" {
continue
}
log.Debugf("执行SQL[%d]: %s", i, strings.TrimSpace(strings.Split(sql, "\n")[0]))
_, err := tx.Exec(sql)
if err != nil {
tx.Rollback()
return fmt.Errorf("执行SQL[%d]失败: %v\nSQL: %s", i, err, sql)
}
}
// 提交事务
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交事务失败: %v", err)
}
log.Info("数据库表结构创建成功")
// 插入默认数据
return db.insertDefaultData()
}
// insertDefaultData 插入默认数据
func (db *DB) insertDefaultData() error {
log.Debug("插入默认数据...")
// 默认类别
categories := []string{"global", "host", "os", "appliance", "network"}
for _, cat := range categories {
_, err := db.engine.Exec(
"INSERT OR IGNORE INTO categories (name) VALUES (?)",
cat,
)
if err != nil {
return err
}
}
log.Debug("插入默认类别索引...")
// 默认类别索引
catIndexes := []struct {
catName string
idxName string
}{
{"global", "global"},
{"os", "linux"},
{"network", "private"},
}
for _, ci := range catIndexes {
_, err := db.engine.Exec(`
INSERT OR IGNORE INTO catindexes (name, category_id)
SELECT ?, id FROM categories WHERE name = ?
`, ci.idxName, ci.catName)
if err != nil {
return err
}
}
log.Debug("插入默认解析链优先级...")
// 默认解析链优先级
precedence := []struct {
catName string
level int
}{
{"global", 1},
{"os", 2},
{"appliance", 3},
{"host", 4},
{"network", 5},
}
for _, p := range precedence {
_, err := db.engine.Exec(`
INSERT OR IGNORE INTO resolvechain (category_id, precedence)
SELECT id, ? FROM categories WHERE name = ?
`, p.level, p.catName)
if err != nil {
return err
}
}
log.Debug("插入默认设备类型...")
// 默认设备类型
appliances := []struct {
name string
desc string
typ string
}{
{"frontend", "管理节点", "master"},
{"compute", "计算节点", "compute"},
{"login", "登录节点", "login"},
{"storage", "存储节点", "storage"},
}
for _, a := range appliances {
_, err := db.engine.Exec(
"INSERT OR IGNORE INTO appliances (name, description, node_type) VALUES (?, ?, ?)",
a.name, a.desc, a.typ,
)
if err != nil {
return err
}
}
log.Debug("插入默认数据完成...")
return nil
}
// ==================== 核心查询方法 ====================
// Execute 执行SQL语句 - 对应Rocks的execute()
func (db *DB) Execute(query string, args ...interface{}) (int64, error) {
db.mu.RLock()
conn := db.conn
verbose := db.verbose
db.mu.RUnlock()
if conn == nil {
return 0, fmt.Errorf("没有活动数据库连接")
}
if verbose {
log.Debugf("执行SQL: %s %v", query, args)
}
// 判断SQL类型
upperQuery := strings.ToUpper(strings.TrimSpace(query))
isSelect := strings.HasPrefix(upperQuery, "SELECT")
if isSelect {
// SELECT 查询使用 QueryContext
rows, err := conn.QueryContext(context.Background(), query, args...)
if err != nil {
// 尝试重连一次
db.RenewConnection()
db.mu.RLock()
conn = db.conn
db.mu.RUnlock()
rows, err = conn.QueryContext(context.Background(), query, args...)
}
if err != nil {
return 0, err
}
// 关闭旧结果
db.mu.Lock()
if db.results != nil {
db.results.Close()
}
db.results = rows
db.mu.Unlock()
return 0, nil
} else {
// INSERT/UPDATE/DELETE 使用 Exec自动提交
result, err := conn.ExecContext(context.Background(), query, args...)
if err != nil {
// 尝试重连一次
db.RenewConnection()
db.mu.RLock()
conn = db.conn
db.mu.RUnlock()
result, err = conn.ExecContext(context.Background(), query, args...)
}
if err != nil {
return 0, err
}
// 获取影响行数
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
if verbose {
log.Debugf("影响行数: %d", rowsAffected)
}
return rowsAffected, nil
}
}
// FetchOne 获取一行 - 对应Rocks的fetchone()
// 返回map[string]interface{}格式key为列名
func (db *DB) FetchOne() (map[string]interface{}, error) {
db.mu.RLock()
results := db.results
db.mu.RUnlock()
if results == nil {
return nil, nil
}
if !results.Next() {
return nil, nil
}
columns, err := results.Columns()
if err != nil {
return nil, err
}
values := make([]interface{}, len(columns))
scanArgs := make([]interface{}, len(columns))
for i := range values {
scanArgs[i] = &values[i]
}
err = results.Scan(scanArgs...)
if err != nil {
return nil, err
}
row := make(map[string]interface{})
for i, col := range columns {
val := values[i]
if b, ok := val.([]byte); ok {
row[col] = string(b)
} else {
row[col] = val
}
}
return row, nil
}
// FetchAll 获取所有行 - 对应Rocks的fetchall()
// 返回[]map[string]interface{}格式
func (db *DB) FetchAll() ([]map[string]interface{}, error) {
db.mu.RLock()
results := db.results
db.mu.RUnlock()
if results == nil {
return nil, nil
}
columns, err := results.Columns()
if err != nil {
return nil, err
}
var rows []map[string]interface{}
for results.Next() {
values := make([]interface{}, len(columns))
scanArgs := make([]interface{}, len(columns))
for i := range values {
scanArgs[i] = &values[i]
}
err = results.Scan(scanArgs...)
if err != nil {
return nil, err
}
row := make(map[string]interface{})
for i, col := range columns {
val := values[i]
if b, ok := val.([]byte); ok {
row[col] = string(b)
} else {
row[col] = val
}
}
rows = append(rows, row)
}
return rows, nil
}
// ==================== 连接维护 ====================
// RenewConnection 续期连接
func (db *DB) RenewConnection() error {
db.mu.Lock()
defer db.mu.Unlock()
if db.conn != nil {
db.conn.Close()
}
conn, err := db.engine.Conn(context.Background())
if err != nil {
return err
}
db.conn = conn
return nil
}
// Close 关闭连接
func (db *DB) Close() error {
db.mu.Lock()
defer db.mu.Unlock()
if db.results != nil {
db.results.Close()
db.results = nil
}
if db.conn != nil {
db.conn.Close()
db.conn = nil
}
if db.engine != nil {
return db.engine.Close()
}
return nil
}
// CloseConnection 只关闭当前连接,不关闭连接池
func (db *DB) CloseConnection() error {
db.mu.Lock()
defer db.mu.Unlock()
if db.results != nil {
db.results.Close()
db.results = nil
}
if db.conn != nil {
db.conn.Close()
db.conn = nil
}
return nil
}
// ==================== 单例模式 ====================
var (
instanceConfigured bool
instanceDBPath string
instanceDBName string
)
func GetInstance() (*DB, error) {
return GetInstanceWithConfig("", "")
}
func GetInstanceWithConfig(dbPath, dbName string) (*DB, error) {
var err error
once.Do(func() {
globalDB = NewDB()
log.Debug("创建数据库实例...")
globalDB.SetDBUsername(globalDB.GetDBUsername())
if dbPath != "" {
globalDB.SetDBPath(dbPath)
log.Debugf("设置数据库路径: %s", dbPath)
}
if dbName != "" {
globalDB.SetDBName(dbName)
log.Debugf("设置数据库名称: %s", dbName)
}
instanceConfigured = (dbPath != "" || dbName != "")
if dbPath != "" {
instanceDBPath = dbPath
}
if dbName != "" {
instanceDBName = dbName
}
err = globalDB.Connect()
})
return globalDB, err
}
func IsInstanceConfigured() bool {
return instanceConfigured
}
func GetInstanceConfig() (dbPath, dbName string) {
return instanceDBPath, instanceDBName
}

624
internal/db/helper.go Normal file
View File

@@ -0,0 +1,624 @@
package db
import (
"fmt"
"net"
"os"
"strings"
"sync"
)
/*
// 获取数据库实例
database, err := db.GetInstance()
if err != nil {
log.Fatal(err)
}
defer database.Close()
// 创建Helper
helper, _ := db.NewDBHelper()
// 执行查询
helper.Execute("SELECT * FROM nodes WHERE rack = ?", 1)
// 获取一行
row, _ := helper.FetchOne()
if row != nil {
log.Infof("节点: %v", row["name"])
}
// 获取所有行
rows, _ := helper.FetchAll()
log.Infof("共 %d 个节点", len(rows))
// 使用Helper方法
hostname, _ := helper.GetHostname("192.168.1.1")
log.Infof("解析主机名: %s", hostname)
// 设置属性
helper.SetCategoryAttr("global", "global", "Kickstart_PrivateHostname", "sunhpc-master")
// 获取属性
val := helper.GetCategoryAttr("global", "global", "Kickstart_PrivateHostname")
log.Infof("前端主机名: %s", val)
*/
const (
attrPostfix = "_old"
)
// DBHelper DatabaseHelper类 - 继承DB扩展业务方法
type DBHelper struct {
*DB
appliancesList []string
frontendName string
cacheAttrs sync.Map
}
func NewDBHelper() (*DBHelper, error) {
db, err := GetInstance()
if err != nil {
return nil, err
}
return &DBHelper{
DB: db,
appliancesList: nil,
frontendName: "",
}, nil
}
// ==================== 节点查询 ====================
// GetListHostnames 获取所有主机名列表
func (h *DBHelper) GetListHostnames() ([]string, error) {
_, err := h.Execute("SELECT name FROM nodes ORDER BY name")
if err != nil {
return nil, err
}
rows, err := h.FetchAll()
if err != nil {
return nil, err
}
var names []string
for _, row := range rows {
if name, ok := row["name"]; ok {
names = append(names, name.(string))
}
}
return names, nil
}
// GetNodesFromNames 从名称列表获取节点
func (h *DBHelper) GetNodesFromNames(names []string, managedOnly bool) ([]map[string]interface{}, error) {
// 如果没有提供名称,返回所有节点
if len(names) == 0 {
query := "SELECT * FROM nodes"
if managedOnly {
query = `
SELECT n.* FROM nodes n
JOIN node_attrs a ON n.id = a.node_id
WHERE a.attr = 'managed' AND a.value = 'true'
`
}
_, err := h.Execute(query)
if err != nil {
return nil, err
}
return h.FetchAll()
}
// 构建查询条件
conditions := []string{}
args := []interface{}{}
for _, name := range names {
if strings.HasPrefix(name, "select ") {
conditions = append(conditions, fmt.Sprintf("name IN (%s)", name[7:]))
} else if strings.Contains(name, "%") {
conditions = append(conditions, "name LIKE ?")
args = append(args, name)
} else if strings.HasPrefix(name, "rack") {
rackNum := strings.TrimPrefix(name, "rack")
conditions = append(conditions, "rack = ?")
args = append(args, rackNum)
} else if h.IsApplianceName(name) {
conditions = append(conditions, `id IN (
SELECT node_id FROM node_attrs
WHERE attr = 'appliance' AND value = ?
)`)
args = append(args, name)
} else {
hostname, err := h.GetHostname(name)
if err == nil {
conditions = append(conditions, "name = ?")
args = append(args, hostname)
}
}
}
if len(conditions) == 0 {
return []map[string]interface{}{}, nil
}
query := "SELECT * FROM nodes WHERE " + strings.Join(conditions, " OR ")
_, err := h.Execute(query, args...)
if err != nil {
return nil, err
}
nodes, err := h.FetchAll()
if err != nil {
return nil, err
}
// 过滤受管节点
if managedOnly {
var managed []map[string]interface{}
for _, node := range nodes {
val := h.GetHostAttr(node["name"].(string), "managed")
if val == "true" {
managed = append(managed, node)
}
}
return managed, nil
}
return nodes, nil
}
// ==================== 设备类型 ====================
// GetAppliancesListText 获取所有设备类型名称
func (h *DBHelper) GetAppliancesListText() []string {
if h.appliancesList != nil {
return h.appliancesList
}
_, err := h.Execute("SELECT DISTINCT value FROM node_attrs WHERE attr = 'appliance'")
if err != nil {
return []string{}
}
rows, _ := h.FetchAll()
var apps []string
for _, row := range rows {
if val, ok := row["value"]; ok {
apps = append(apps, val.(string))
}
}
h.appliancesList = apps
return apps
}
// IsApplianceName 检查是否为设备类型名称
func (h *DBHelper) IsApplianceName(name string) bool {
for _, app := range h.GetAppliancesListText() {
if app == name {
return true
}
}
return false
}
// ==================== 主机名解析 ====================
// GetHostname 规范化主机名 - 完全参考Rocks实现
func (h *DBHelper) GetHostname(hostname string) (string, error) {
// 如果hostname为空使用系统主机名
if hostname == "" {
hostname, _ = os.Hostname()
hostname = strings.Split(hostname, ".")[0]
return h.GetHostname(hostname)
}
// 1. 直接在nodes表中查找
_, err := h.Execute("SELECT * FROM nodes WHERE name = ?", hostname)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return hostname, nil
}
}
// 2. 尝试IP地址反向解析
addr := net.ParseIP(hostname)
if addr != nil {
names, err := net.LookupAddr(hostname)
if err == nil && len(names) > 0 {
return h.GetHostname(strings.Split(names[0], ".")[0])
}
}
// 3. 在networks表中查找IP
if addr != nil {
_, err := h.Execute(`
SELECT n.name FROM nodes n
JOIN networks net ON n.id = net.node_id
WHERE net.ip = ?
`, addr.String())
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return row["name"].(string), nil
}
}
}
// 4. 尝试MAC地址
mac := strings.ReplaceAll(hostname, "-", ":")
_, err = h.Execute(`
SELECT n.name FROM nodes n
JOIN networks net ON n.id = net.node_id
WHERE net.mac = ?
`, mac)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return row["name"].(string), nil
}
}
// 5. 检查别名
_, err = h.Execute(`
SELECT n.name FROM nodes n
JOIN aliases a ON n.id = a.node_id
WHERE a.name = ?
`, hostname)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return row["name"].(string), nil
}
}
// 6. 尝试FQDN
if strings.Contains(hostname, ".") {
parts := strings.Split(hostname, ".")
name := parts[0]
domain := strings.Join(parts[1:], ".")
_, err := h.Execute(`
SELECT n.name FROM nodes n
JOIN networks net ON n.id = net.node_id
JOIN subnets s ON net.subnet_id = s.id
WHERE s.dns_zone = ? AND (net.name = ? OR n.name = ?)
`, domain, name, name)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return row["name"].(string), nil
}
}
}
// 7. 如果以上都失败,抛出异常
return "", fmt.Errorf("无法解析主机名: %s", hostname)
}
// CheckHostnameValidity 检查主机名有效性
func (h *DBHelper) CheckHostnameValidity(hostname string) error {
// 不能包含点
if strings.Contains(hostname, ".") {
return fmt.Errorf("主机名 %s 不能包含点号", hostname)
}
// 不能是rack<数字>格式
if strings.HasPrefix(hostname, "rack") {
num := strings.TrimPrefix(hostname, "rack")
if _, err := fmt.Sscanf(num, "%d", new(int)); err == nil {
return fmt.Errorf("主机名 %s 不能是rack<数字>格式", hostname)
}
}
// 不能是设备类型名称
if h.IsApplianceName(hostname) {
return fmt.Errorf("主机名 %s 不能与设备类型名称相同", hostname)
}
// 检查是否已存在
_, err := h.GetHostname(hostname)
if err == nil {
return fmt.Errorf("节点 %s 已存在", hostname)
}
return nil
}
// ==================== 前端节点 ====================
// GetFrontendName 获取前端节点名称
func (h *DBHelper) GetFrontendName() string {
if h.frontendName != "" {
return h.frontendName
}
name := h.GetCategoryAttr("global", "global", "Kickstart_PrivateHostname")
if name != "" {
h.frontendName = name
}
return h.frontendName
}
// ==================== 属性管理 ====================
// GetCategoryIndex 获取类别索引
func (h *DBHelper) GetCategoryIndex(categoryName, categoryIndex string) (map[string]interface{}, map[string]interface{}, error) {
// 查询类别和索引
_, err := h.Execute(`
SELECT c.id as cid, c.name as cname, i.id as iid, i.name as iname
FROM categories c
JOIN catindexes i ON c.id = i.category_id
WHERE c.name = ? AND i.name = ?
`, categoryName, categoryIndex)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
category := map[string]interface{}{
"id": row["cid"],
"name": row["cname"],
}
catindex := map[string]interface{}{
"id": row["iid"],
"name": row["iname"],
"category_id": row["cid"],
}
return category, catindex, nil
}
}
// 不存在则创建
// 创建类别
_, err = h.Execute("INSERT INTO categories (name) VALUES (?)", categoryName)
if err != nil {
return nil, nil, err
}
var catID int64
h.Execute("SELECT last_insert_rowid()")
row, _ := h.FetchOne()
if row != nil {
catID = row["last_insert_rowid()"].(int64)
}
// 创建索引
_, err = h.Execute(
"INSERT INTO catindexes (name, category_id) VALUES (?, ?)",
categoryIndex, catID,
)
if err != nil {
return nil, nil, err
}
h.Execute("SELECT last_insert_rowid()")
row, _ = h.FetchOne()
var idxID int64
if row != nil {
idxID = row["last_insert_rowid()"].(int64)
}
category := map[string]interface{}{
"id": catID,
"name": categoryName,
}
catindex := map[string]interface{}{
"id": idxID,
"name": categoryIndex,
"category_id": catID,
}
return category, catindex, nil
}
// SetCategoryAttr 设置类别属性
func (h *DBHelper) SetCategoryAttr(categoryName, catindexName, attr, value string) error {
cat, catindex, err := h.GetCategoryIndex(categoryName, catindexName)
if err != nil {
return err
}
// 查询现有属性
_, err = h.Execute(`
SELECT id, value FROM attributes
WHERE attr = ? AND category_id = ? AND catindex_id = ?
`, attr, cat["id"], catindex["id"])
if err == nil {
row, _ := h.FetchOne()
if row != nil {
// 更新现有属性
oldValue := row["value"]
attrID := row["id"]
_, err = h.Execute(
"UPDATE attributes SET value = ? WHERE id = ?",
value, attrID,
)
if err != nil {
return err
}
// 保存旧值
if !strings.HasSuffix(attr, attrPostfix) {
h.SetCategoryAttr(categoryName, catindexName, attr+attrPostfix, oldValue.(string))
}
return nil
}
}
// 创建新属性
_, err = h.Execute(`
INSERT INTO attributes (attr, value, category_id, catindex_id)
VALUES (?, ?, ?, ?)
`, attr, value, cat["id"], catindex["id"])
return err
}
// GetCategoryAttr 获取类别属性
func (h *DBHelper) GetCategoryAttr(categoryName, catindexName, attrName string) string {
cat, catindex, err := h.GetCategoryIndex(categoryName, catindexName)
if err != nil {
return ""
}
_, err = h.Execute(`
SELECT value FROM attributes
WHERE attr = ? AND category_id = ? AND catindex_id = ?
`, attrName, cat["id"], catindex["id"])
if err != nil {
return ""
}
row, _ := h.FetchOne()
if row == nil {
return ""
}
return row["value"].(string)
}
// RemoveCategoryAttr 移除类别属性
func (h *DBHelper) RemoveCategoryAttr(categoryName, catindexName, attrName string) error {
cat, catindex, err := h.GetCategoryIndex(categoryName, catindexName)
if err != nil {
return err
}
_, err = h.Execute(`
DELETE FROM attributes
WHERE attr = ? AND category_id = ? AND catindex_id = ?
`, attrName, cat["id"], catindex["id"])
if err != nil {
return err
}
// 同时删除对应的_old属性
_, _ = h.Execute(`
DELETE FROM attributes
WHERE attr = ? AND category_id = ? AND catindex_id = ?
`, attrName+attrPostfix, cat["id"], catindex["id"])
return nil
}
// ==================== 主机属性 ====================
// GetHostAttr 获取主机属性
func (h *DBHelper) GetHostAttr(hostname, attr string) string {
// 先从节点直接属性查询
_, err := h.Execute(`
SELECT value FROM node_attrs
WHERE node_id = (SELECT id FROM nodes WHERE name = ?)
AND attr = ?
`, hostname, attr)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
return row["value"].(string)
}
}
// 使用Rocks的属性解析链查询
query := `
SELECT a.value FROM attributes a
JOIN resolvechain r ON a.category_id = r.category_id
JOIN hostselections h ON a.category_id = h.category_id
AND a.catindex_id = h.selection
WHERE h.host_id = (SELECT id FROM nodes WHERE name = ?)
AND a.attr = ?
ORDER BY r.precedence DESC
LIMIT 1
`
_, err = h.Execute(query, hostname, attr)
if err != nil {
return ""
}
row, _ := h.FetchOne()
if row == nil {
return ""
}
return row["value"].(string)
}
// GetHostAttrs 获取主机所有属性
func (h *DBHelper) GetHostAttrs(hostname string, showSource bool) map[string]interface{} {
attrs := make(map[string]interface{})
// 获取节点基本信息
_, err := h.Execute(`
SELECT n.id, n.name, n.rack, n.rank, m.name as membership, a.name as appliance
FROM nodes n
LEFT JOIN memberships m ON n.membership_id = m.id
LEFT JOIN appliances a ON m.appliance_id = a.id
WHERE n.name = ?
`, hostname)
if err == nil {
row, _ := h.FetchOne()
if row != nil {
if showSource {
attrs["hostname"] = []interface{}{row["name"], "I"}
attrs["rack"] = []interface{}{row["rack"], "I"}
attrs["rank"] = []interface{}{row["rank"], "I"}
attrs["appliance"] = []interface{}{row["appliance"], "I"}
attrs["membership"] = []interface{}{row["membership"], "I"}
} else {
attrs["hostname"] = row["name"]
attrs["rack"] = row["rack"]
attrs["rank"] = row["rank"]
attrs["appliance"] = row["appliance"]
attrs["membership"] = row["membership"]
}
}
}
// 获取所有属性
query := `
SELECT a.attr, a.value,
CASE
WHEN h.host_id IS NOT NULL THEN 'H'
ELSE UPPER(SUBSTR(c.name, 1, 1))
END as source
FROM attributes a
JOIN categories c ON a.category_id = c.id
LEFT JOIN hostselections h ON a.category_id = h.category_id
AND a.catindex_id = h.selection
AND h.host_id = (SELECT id FROM nodes WHERE name = ?)
UNION
SELECT attr, value, 'N' as source
FROM node_attrs
WHERE node_id = (SELECT id FROM nodes WHERE name = ?)
`
_, err = h.Execute(query, hostname, hostname)
if err == nil {
rows, _ := h.FetchAll()
for _, row := range rows {
attr := row["attr"].(string)
value := row["value"]
if showSource {
attrs[attr] = []interface{}{value, row["source"]}
} else {
attrs[attr] = value
}
}
}
return attrs
}