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

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
}