Files
sunhpc-go/internal/db/helper.go
2026-02-14 05:36:00 +08:00

625 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}