五、devicefactory/customname/public_name_config.go — 设备自定义命名

5.1 模块定位

维度 说明
业务职责 管理设备的自定义公共名称映射。允许用户通过 JSON 配置文件自定义设备在 Kubernetes 中暴露的资源名称、设备名称前缀和 Pod 注解名称
功能定位 Name Mapping / Adapter:在内部设备类型名称与外部公开名称之间建立映射关系,支持新旧两套命名体系
在系统中的位置 entry.go 在工厂初始化最早期调用,被 server 层在设备注册、节点标注、Pod注解处理时调用

调用方

customname/public_name_config.go

配置文件

loadFaultCodeFromFile()

checkName() 校验

ReplaceDevicePublicName

ReplaceDevicePublicType

ReplacePodAnnotation

ReplaceDeviceInfoPublicName

ReplaceDeviceInnerName

/usr/local/deviceNameCustomization.json
用户自定义命名配置

InitPublicNameConfig()
加载并校验配置

devNameMap
map[string]DevName
全局命名映射表

Replace* 系列函数
名称替换

entry.go
InitFunction()

server/manager.go
设备注册/节点更新

Pod注解处理

替换设备公共名称前缀

替换设备类型名

替换Pod注解key/value

替换设备信息中的名称

替换设备内部名称

5.2 模块整体结构

5.2.1 常量与正则定义
const nameConfigFilePath = "/usr/local/deviceNameCustomization.json"  // 配置文件路径

// 设备类型正则:字母开头,允许字母数字./,长度10-32,字母数字结尾
var deviceTypePattern = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9./]{8,30}[a-zA-Z0-9]$`)

// 设备名称前缀正则:字母开头,允许字母数字-,长度2-16
var deviceNamePattern = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]{1,15}$`)

// Pod配置名称正则:字母开头,允许字母数字./-,长度10-63,字母数字结尾
var podConfigurationPattern = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9./-]{8,61}[a-zA-Z0-9]$`)
5.2.2 全局变量
var devNameMap = map[string]DevName{}  // 核心映射表:ResourceType → DevName

// 旧设备类型集合 — 判断是否使用旧命名体系
var oldDeviceTypes = map[string]bool{
    api.Ascend310:   true,   // "Ascend310"
    api.Ascend310P:  true,   // "Ascend310P"
    api.Ascend310B:  true,   // "Ascend310B"
    api.Ascend910A:  true,   // "Ascend910" (910A)
    api.Ascend910B:  true,   // "Ascend910B"
    api.Ascend910A3: true,   // "Ascend910A3"
}

oldDeviceTypes 的意义

  • 旧设备类型使用 huawei.com/Ascend910huawei.com/Ascend310 等资源名
  • 新设备类型(如 A5)统一使用 huawei.com/npu 资源名
  • 这个映射表决定了名称替换的分支逻辑
5.2.3 DevName 结构体

DevName

+ResourceType string // 原始设备类型名(如 "Ascend910")

+DevicePublicType string // 自定义公开设备类型(如 "huawei.com/custom910")

+OldDevicePublicType string // 旧公开设备类型(如 "huawei.com/Ascend910")

+DevicePublicNamePre string // 自定义设备名前缀(如 "custom910-")

+OldDevicePublicNamePre string // 旧设备名前缀(如 "Ascend910-")

+PodConfigurationName string // 自定义Pod配置注解名

+OldPodConfigurationName string // 旧Pod配置注解名

ResourceType 是 key\n其他字段是映射值\nOld* 字段由 setDefaultName 自动填充

5.2.4 核心方法清单
方法 签名 作用
InitPublicNameConfig func() 从 JSON 文件加载配置,校验后填充 devNameMap
setDefaultName func(DevName) DevName 为旧设备类型填充 Old* 字段
IsOldDeviceType func(string) bool 判断设备类型是否属于旧设备类型
getResourceName func(string) string 根据设备类型返回资源名(旧名 or huawei.com/npu)
getResourceNamePrefix func(string) string 根据设备类型返回资源名前缀
getResourceNamePrefixInner func(string, DevName) string 内部使用的资源名前缀替换
getPodAnnotationWithPublicName func(map, DevName) map 获取带公共名称的Pod注解
ReplaceDevicePublicName func(string, string) string 替换设备公共名称前缀
ReplaceDeviceInnerName func(string, []string) []string 替换设备内部名称
ReplaceDevicePublicType func(string, string) string 替换设备公共类型
ReplacePodAnnotation func(string, map) map 替换Pod注解的key和value
ReplaceDeviceInfoPublicName func(string, map, string, string) (map, string, string) 批量替换设备信息中的名称
checkName func([]DevName) bool 校验自定义命名配置的合法性
loadFaultCodeFromFile func() ([]DevName, error) 从文件加载JSON配置

5.3 核心业务逻辑深度解析

5.3.1 InitPublicNameConfig() 完整流程

Yes

No

No

Yes

Yes

No

InitPublicNameConfig()

loadFaultCodeFromFile()
从 /usr/local/deviceNameCustomization.json
加载JSON配置

err != nil?

hwlog.Info: 不使用自定义命名
return(使用默认命名)

checkName(devNames)
校验配置合法性

有效?

hwlog.Warn: 配置无效
return(使用默认命名)

遍历 devNames

setDefaultName(devName)
填充Old*字段

devNameMap[devName.ResourceType] = devName
写入全局映射表

还有?

hwlog.Info: 自定义命名生效
输出 devNameMap

完成

func InitPublicNameConfig() {
    // 从文件加载自定义命名配置
    devNames, err := loadFaultCodeFromFile()
    if err != nil {
        // 配置文件不存在或解析失败不报错,使用默认命名
        hwlog.RunLog.Infof("do not use the custom device name, because %v", err)
        return
    }
    // 校验配置内容合法性
    if !checkName(devNames) {
        hwlog.RunLog.Warn("the custom name configuration is invalid")
        return
    }
    // 逐个处理并写入全局映射表
    for _, devName := range devNames {
        devName = setDefaultName(devName)           // 填充 Old* 字段
        devNameMap[devName.ResourceType] = devName   // 以 ResourceType 为 key
    }
    hwlog.RunLog.Infof("the custom name takes effect: %v", devNameMap)
}
5.3.2 setDefaultName() — 旧名称默认值填充
func setDefaultName(devName DevName) DevName {
    // 根据 ResourceType 设置对应的旧名称体系
    // 这些旧名称是硬编码的,对应 Kubernetes 资源注册时的历史命名
    switch devName.ResourceType {
    case api.Ascend910:    // "Ascend910"
        devName.OldDevicePublicType = api.HuaweiAscend910         // "huawei.com/Ascend910"
        devName.OldDevicePublicNamePre = api.Ascend910MinuxPrefix // "Ascend910-"
        devName.OldPodConfigurationName = api.Pod910DeviceAnno    // "ascend.kubectl.kubernetes.io/ascend-910-configuration"
    case api.Ascend310:    // "Ascend310"
        devName.OldDevicePublicType = api.HuaweiAscend310         // "huawei.com/Ascend310"
        devName.OldDevicePublicNamePre = api.Ascend310MinuxPrefix // "Ascend310-"
        // 注意:310 没有 PodConfigurationName
    case api.Ascend310P:   // "Ascend310P"
        devName.OldDevicePublicType = api.HuaweiAscend310P        // "huawei.com/Ascend310P"
        devName.OldDevicePublicNamePre = api.Ascend310PMinuxPrefix // "Ascend310P-"
    default:
        hwlog.RunLog.Errorf("devicePublicType(%s) is invalid ", devName.DevicePublicType)
    }
    return devName
}

命名映射关系图

NPU统一命名

新命名体系(自定义)

旧命名体系

ReplaceDevicePublicType

ReplaceDevicePublicType

ReplaceDevicePublicType

非oldDeviceTypes
getResourceName

非oldDeviceTypes
getResourceName

非oldDeviceTypes
getResourceName

huawei.com/Ascend910
Ascend910-
ascend.kubectl.kubernetes.io/
ascend-910-configuration

huawei.com/Ascend310
Ascend310-

huawei.com/Ascend310P
Ascend310P-

DevicePublicType
DevicePublicNamePre
PodConfigurationName

DevicePublicType
DevicePublicNamePre

DevicePublicType
DevicePublicNamePre

huawei.com/npu
npu-
ascend.kubectl.kubernetes.io/
ascend-npu-configuration

5.3.3 checkName() — 配置校验
func checkName(devNames []DevName) bool {
    for _, devName := range devNames {
        // 必填字段检查
        if len(devName.DevicePublicNamePre) == 0 || len(devName.ResourceType) == 0 ||
            len(devName.DevicePublicType) == 0 {
            hwlog.RunLog.Error("all name should not be null")
            return false
        }
        // ResourceType 枚举校验:仅支持 310, 310P, 910
        if devName.ResourceType != api.Ascend310 &&
           devName.ResourceType != api.Ascend310P &&
           devName.ResourceType != api.Ascend910 {
            hwlog.RunLog.Errorf("resourceType only support %s, %s, %s",
                api.Ascend310, api.Ascend310P, api.Ascend910)
            return false
        }
        // DevicePublicType 正则校验:字母开头,允许字母数字./,长度10-32
        if !deviceTypePattern.MatchString(devName.DevicePublicType) {
            hwlog.RunLog.Errorf("devicePublicType(%s) is invalid", devName.DevicePublicType)
            return false
        }
        // DevicePublicNamePre 正则校验:字母开头,允许字母数字-,长度2-16
        if !deviceNamePattern.MatchString(devName.DevicePublicNamePre) {
            hwlog.RunLog.Errorf("devicePublicNamePre(%s) is invalid", devName.DevicePublicNamePre)
            return false
        }
        // PodConfigurationName 可选,非空时校验
        if len(devName.PodConfigurationName) != 0 &&
           !podConfigurationPattern.MatchString(devName.PodConfigurationName) {
            hwlog.RunLog.Errorf("podConfigurationName(%s) is invalid", devName.PodConfigurationName)
            return false
        }
    }
    return true
}
5.3.4 Replace 系列函数 — 名称替换核心逻辑

ReplaceDevicePublicName()

func ReplaceDevicePublicName(resourceType string, oldName string) string {
    // 从全局映射表获取该设备类型的命名配置
    devName := devNameMap[resourceType]
    // 如果没有自定义名称前缀,使用默认逻辑
    if len(devName.DevicePublicNamePre) == 0 {
        return getResourceNamePrefix(oldName)
    }
    // 有自定义名称前缀:将旧前缀替换为自定义前缀
    // 例如: "Ascend910-0" → "custom910-0"
    return strings.ReplaceAll(oldName, devName.OldDevicePublicNamePre, devName.DevicePublicNamePre)
}

ReplaceDevicePublicType()

func ReplaceDevicePublicType(resourceType string, oldName string) string {
    devName := devNameMap[resourceType]
    if len(devName.DevicePublicType) == 0 {
        // 无自定义:旧设备类型用旧名,新设备类型用 huawei.com/npu
        return getResourceName(oldName)
    }
    // 有自定义:将旧公开类型替换为自定义类型
    // 例如: "huawei.com/Ascend910" → "huawei.com/custom910"
    return strings.ReplaceAll(oldName, devName.OldDevicePublicType, devName.DevicePublicType)
}

ReplacePodAnnotation()

Yes

No

Yes

No

Yes

No

Yes

No

ReplacePodAnnotation(resourceType, annotation)

devName := devNameMap[resourceType]

len(DevicePublicNamePre) == 0?

getPodAnnotationWithPublicName()
使用NPU统一命名

遍历 annotation map

key == OldDevicePublicType?

key = DevicePublicType
替换设备类型名

key == OldPodConfigurationName?

key = PodConfigurationName
替换Pod配置名

保持原key

value = ReplaceAll(value,
OldDevicePublicNamePre,
DevicePublicNamePre)

newAnnotation[key] = value

还有?

return newAnnotation

返回

ReplaceDeviceInfoPublicName()

func ReplaceDeviceInfoPublicName(resourceType string, deviceList map[string]string,
    deviceName string, reasonCm string) (map[string]string, string, string) {
    devName := devNameMap[resourceType]
    if len(devName.DevicePublicNamePre) == 0 {
        // 无自定义名称前缀的分支
        if !oldDeviceTypes[common.ParamOption.RealCardType] {
            // 新设备类型:将所有 "huawei.com/Ascend910" 替换为 "huawei.com/npu"
            // 并将所有名称前缀统一为 "npu-"
            newDeviceList := make(map[string]string, len(deviceList))
            for key, value := range deviceList {
                newKey := strings.ReplaceAll(key, api.HuaweiAscend910, api.HuaweiNPU)
                newValue := getResourceNamePrefix(value)
                newDeviceList[newKey] = newValue
            }
            newDeviceName := getResourceNamePrefix(deviceName)
            newReasonCm := getResourceNamePrefix(reasonCm)
            return newDeviceList, newDeviceName, newReasonCm
        }
        // 旧设备类型:原样返回
        return deviceList, deviceName, reasonCm
    }
    // 有自定义名称前缀的分支:批量替换 key 和 value 中的前缀
    newDeviceList := make(map[string]string, len(deviceList))
    for key, value := range deviceList {
        newKey := strings.ReplaceAll(key, devName.OldDevicePublicType, devName.DevicePublicType)
        newValue := strings.ReplaceAll(value, devName.OldDevicePublicNamePre, devName.DevicePublicNamePre)
        newDeviceList[newKey] = newValue
    }
    newDeviceName := strings.ReplaceAll(deviceName, devName.OldDevicePublicNamePre, devName.DevicePublicNamePre)
    newReasonCm := strings.ReplaceAll(reasonCm, devName.OldDevicePublicNamePre, devName.DevicePublicNamePre)
    return newDeviceList, newDeviceName, newReasonCm
}

Replace 系列设计模式

  • 双分支策略:每个 Replace 函数都有"有自定义配置"和"无自定义配置"两个分支
  • 无自定义配置分支:再分"旧设备类型"和"新设备类型"两个子分支
  • 新设备类型:统一使用 huawei.com/npunpu- 前缀,实现命名归一化
  • 有自定义配置分支:按用户配置进行前缀/类型名替换
5.3.5 loadFaultCodeFromFile() — 配置文件加载
func loadFaultCodeFromFile() ([]DevName, error) {
    // 使用 utils.LoadFile 读取配置文件(最大限制由 utils 内部控制)
    faultCodeBytes, err := utils.LoadFile(nameConfigFilePath)
    if err != nil {
        return nil, fmt.Errorf("load name config json failed: %v", err)
    }
    // JSON 反序列化为 DevName 切片
    var devNames []DevName
    if err := json.Unmarshal(faultCodeBytes, &devNames); err != nil {
        return nil, fmt.Errorf("unmarshal fault code byte failed: %v", err)
    }
    return devNames, nil
}

函数名遗留问题loadFaultCodeFromFile 命名带有 “FaultCode” 是历史遗留,实际加载的是设备名称配置。配置文件路径常量 nameConfigFilePath 是正确的。

5.3.6 命名体系完整决策树

No

Yes

Empty

Yes

No

NotEmpty

Yes

No

输入: resourceType + oldName

devNameMap 中
有此 resourceType?

使用默认命名
getResourceName / getResourceNamePrefix

DevicePublicNamePre
为空?

oldDeviceTypes
包含 RealCardType?

保留旧命名
huawei.com/Ascend910
Ascend910-

使用NPU统一命名
huawei.com/npu
npu-

使用自定义命名
替换为 DevicePublicType
替换为 DevicePublicNamePre

oldDeviceTypes
包含 RealCardType?

保留旧命名

使用NPU统一命名

输出: 替换后的名称


六、topology/topo.go — 拓扑文件读写

6.1 模块定位

维度 说明
业务职责 提供拓扑文件的读取、SHA256 哈希比较和写入功能。确保拓扑文件内容变更时才写入磁盘,避免无意义的 I/O
功能定位 File I/O Utility:纯文件操作,无业务逻辑,被 rack_topology.go 调用
在系统中的位置 拓扑模块的底层工具层,被 RasTopoWriteTask 调用

文件系统

topo.go

rack_topology.go

调用

1. 读取源文件

读取

2. 计算新哈希

3. 检查目标文件

存在

读取

4. 比较哈希

Same

Different

写入

GetTopoFileAndWrite()

ToFile()

topoFileToStr()

getFileHash()

源拓扑文件
(驱动生成)

目标拓扑文件
(RAS路径)

sha256.Sum256(newTopoStr)

os.Stat(filePath)

newHash == originalHash?

跳过写入
日志: file is up to date

写入目标文件
os.OpenFile + WriteString

6.2 模块整体结构

6.2.1 常量定义
const (
    Sha256HashLength       = 32           // SHA256 哈希字节数
    maxRackNumPerSuperPod  = 256          // 每个SuperPod最大Rack数
    maxSuperPodNum         = 64           // 最大SuperPod数
    defaultPerm            = 0644         // 默认文件权限
    rackDirPerm            = 0755         // Rack目录权限
    publishCmNamePrefix    = "super-pod"  // ConfigMap名称前缀
    size50M                = 50 * 1024 * 1024  // 文件最大50MB
)
6.2.2 结构体

TopoInfo

+SuperPodId int // SuperPod ID

+RackId int // Rack ID

+TopoJsonFile string // 拓扑JSON文件目标路径

TopoInfo 用于存储
拓扑文件的定位信息
(本文件中未直接使用
但作为导出结构体供外部使用)

6.2.3 核心方法清单
方法 签名 作用
topoFileToStr func(string) (string, error) 读取拓扑文件并返回字符串,校验JSON合法性
getFileHash func(string) ([32]byte, error) 计算文件内容的 SHA256 哈希
ToFile func(filePath, orgFile string) error 将源文件内容写入目标文件(带哈希比较,内容相同不写入)

6.3 核心业务逻辑深度解析

6.3.1 topoFileToStr() — 读取并校验拓扑文件
func topoFileToStr(orgFile string) (string, error) {
    // 读取源文件,限制最大50MB防止内存溢出
    topoData, err := utils.ReadLimitBytes(orgFile, size50M)
    if err != nil {
        return "", fmt.Errorf("read topo file failed, path:<%v>; err:<%v>", orgFile, err)
    }
    // 校验文件内容是否为合法JSON
    if !json.Valid(topoData) {
        return "", fmt.Errorf("topo file is not json, path:<%v>", orgFile)
    }
    return string(topoData), nil
}

安全设计

  • ReadLimitBytes 限制50MB,防止超大文件导致OOM
  • JSON合法性校验,防止写入无效拓扑数据
6.3.2 getFileHash() — 计算文件哈希
func getFileHash(filePath string) ([Sha256HashLength]byte, error) {
    // 读取文件全部内容
    fileContent, err := os.ReadFile(filePath)
    if err != nil {
        return [Sha256HashLength]byte{}, err
    }
    // 返回 SHA256 哈希值(32字节)
    return sha256.Sum256(fileContent), nil
}
6.3.3 ToFile() — 带哈希比较的文件写入
文件系统 目标文件(filePath) 源文件(orgFile) ToFile() 调用方 文件系统 目标文件(filePath) 源文件(orgFile) ToFile() 调用方 alt [哈希相同] [哈希不同] alt [文件不存在] [文件存在] ToFile(filePath, orgFile) topoFileToStr(orgFile) newTopoStr (读取内容) newHash = sha256(newTopoStr) os.Stat(filePath) IsNotExist err OpenFile(O_RDWR|O_CREATE|O_TRUNC) stat ok getFileHash(filePath) originalHash 比较 newHash == originalHash nil (跳过写入) OpenFile(O_RDWR|O_CREATE|O_TRUNC) WriteString(newTopoStr) Chmod(filePath, 0644) 写入完成 nil
func ToFile(filePath string, orgFile string) error {
    // 步骤1: 读取源文件内容并校验JSON
    newTopoStr, err := topoFileToStr(orgFile)
    if err != nil {
        return err
    }

    // 步骤2: 计算新内容的SHA256哈希
    newHash := sha256.Sum256([]byte(newTopoStr))
    var originalHash [Sha256HashLength]byte

    // 步骤3: 检查目标文件是否存在
    if _, err := os.Stat(filePath); err != nil {
        if !os.IsNotExist(err) {
            return err  // 非文件不存在错误,返回
        }
        // 文件不存在:originalHash 保持零值,必然与 newHash 不同
    } else {
        // 文件存在:读取原文件哈希
        originalHash, err = getFileHash(filePath)
        if err != nil {
            return err
        }
    }

    // 步骤4: 比较哈希,内容相同则跳过写入
    if newHash == originalHash {
        hwlog.RunLog.Infof("The file %s is up to date, no need to write", filePath)
        return nil
    }

    // 步骤5: 内容不同(或文件不存在),写入新内容
    f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultPerm)
    if err != nil {
        return err
    }
    defer f.Close()

    if _, err = f.WriteString(newTopoStr); err != nil {
        return err
    }

    // 步骤6: 确保文件权限正确
    if err = os.Chmod(filePath, defaultPerm); err != nil {
        return err
    }

    return nil
}

设计亮点

  1. 哈希比较避免无效写入:周期性调用时(30秒一次),如果拓扑未变化则零I/O
  2. 原子性保证O_TRUNC 先清空再写入,虽然非原子操作,但对于拓扑配置可接受
  3. 权限显式设置Chmod 确保 umask 不会导致权限异常
  4. JSON校验前置:写入前校验,防止写入损坏数据

七、topology/rack_topology.go — Rack拓扑写入任务

7.1 模块定位

维度 说明
业务职责 作为后台 goroutine 周期性地将 Rack 拓扑信息写入 RAS(Reliability Availability Serviceability)文件路径,供慢网(slownet)模块使用
功能定位 Background Task:长周期循环任务,每30秒检查并更新拓扑文件
在系统中的位置 main.go 以 goroutine 启动,仅对 A5 芯片生效

常量映射

HwDevManager

slownet(外部)

topo.go

rack_topology.go

main.go

go RasTopoWriteTask(ctx, hdm)

初始化时

初始化时

初始化时

初始化时

循环每30秒

1

2

3

成功

查表

调用

ctx.Done()

main()

RasTopoWriteTask()
后台goroutine

checkConfigReady()
检查前置条件

GetTopoFileAndWrite()
写入拓扑文件

ToFile()
文件写入

GetRasNetRootPath()

GetSuperPodInfoFilePath()

GetRackTopologyFilePath()

GetSuperPodID()

GetSuperPodType()

GetRackID()

GetServerIndex()

topoFilePathMap
SuperPodType → 源拓扑路径

优雅退出

7.2 模块整体结构

7.2.1 全局变量
var topoFilePathMap = map[int8]string{
    common.ProductTypeServer:    common.Server8PTopoPath,    // 0 → /usr/local/Ascend/driver/topo/950/atlas_850_1.json
    common.ProductType1D:        common.Pod1DTopoPath,       // 1 → /usr/local/Ascend/driver/topo/950/atlas_950_1.json
    common.ProductType2D:        common.Pod2DTopoPath,       // 2 → /usr/local/Ascend/driver/topo/950/atlas_950_2.json
    common.ProductType16PServer: common.Server16PTopoPath,   // 3 → /usr/local/Ascend/driver/topo/950/atlas_850_2.json
    common.ProductType32PServer: common.Server32PTopoPath,   // 4 → /usr/local/Ascend/driver/topo/950/atlas_850_3.json
}

var (
    superPodId  int32  // SuperPod ID(从 HwDevManager 获取)
    superPodType int8  // SuperPod 类型(决定源拓扑文件路径)
    rackId      int32  // Rack ID(决定目标文件路径)
    serverIndex int32  // Server索引(决定目标文件路径)
)

SuperPod类型与源拓扑文件映射

SuperPod Type → 源拓扑路径

Type 0 (Server)
8P服务器

/usr/local/Ascend/driver/topo/950/
atlas_850_1.json

Type 1 (1D)
1D Pod

/usr/local/Ascend/driver/topo/950/
atlas_950_1.json

Type 2 (2D)
2D Pod

/usr/local/Ascend/driver/topo/950/
atlas_950_2.json

Type 3 (16P Server)
16P服务器

/usr/local/Ascend/driver/topo/950/
atlas_850_2.json

Type 4 (32P Server)
32P服务器

/usr/local/Ascend/driver/topo/950/
atlas_850_3.json

7.2.2 核心方法清单
方法 签名 作用
GetTopoFileAndWrite func(topoJsonFile string) 根据superPodType查找源拓扑路径,调用ToFile写入
RasTopoWriteTask func(ctx, *HwDevManager) 后台goroutine主循环:初始化→每30秒检查并写入拓扑
checkConfigReady func(superPodIdStr string) (string, bool) 检查RAS环境是否就绪,返回目标文件路径

7.3 核心业务逻辑深度解析

7.3.1 RasTopoWriteTask() — 后台任务主循环

No

Yes

Yes

No

Yes

No

ctx.Done()

default

No

Yes

RasTopoWriteTask(ctx, hdm)

RealCardType
== Ascend910A5?

Info: 非A5,无需启动
return

hdm == nil?

Error: hdm为nil
return

slownet.GetRasNetRootPath()

err?

Error: 获取RAS路径失败
return

hdm.ManagerLock.Lock()
加锁读取HwDevManager状态

superPodId = hdm.GetSuperPodID()

superPodType = hdm.GetSuperPodType()

rackId = hdm.GetRackID()

serverIndex = hdm.GetDevManager().GetServerIndex()

hdm.ManagerLock.Unlock()

进入主循环

select

Info: RasTopoWriteTask stop
return

checkConfigReady(superPodId)

就绪?

time.Sleep(30s)

GetTopoFileAndWrite(filePath)

func RasTopoWriteTask(ctx context.Context, hdm *server.HwDevManager) {
    // ★ 前置检查1: 仅A5芯片需要此任务
    if common.ParamOption.RealCardType != common.Ascend910A5 {
        hwlog.RunLog.Infof("current is not %s, no need start RasTopoWriteTask", api.HuaweiNPU)
        return
    }
    // ★ 前置检查2: hdm 不能为nil
    if hdm == nil {
        hwlog.RunLog.Error("illegal input, hdm is nil")
        return
    }
    // ★ 前置检查3: RAS网络根路径必须存在
    _, err := slownet.GetRasNetRootPath()
    if err != nil {
        hwlog.RunLog.Errorf("get ras net root path failed, err: %v", err)
        return
    }

    // ★ 初始化阶段:从 HwDevManager 读取拓扑信息(加锁保护)
    hdm.ManagerLock.Lock()
    superPodId = hdm.GetSuperPodID()       // SuperPod ID
    superPodType = hdm.GetSuperPodType()   // SuperPod 类型(决定源拓扑文件)
    rackId = hdm.GetRackID()               // Rack ID
    serverIndex = hdm.GetDevManager().GetServerIndex()  // Server 索引
    hdm.ManagerLock.Unlock()

    // ★ 主循环:每30秒检查并写入拓扑文件
    for {
        select {
        case _, ok := <-ctx.Done():
            if !ok {
                hwlog.RunLog.Info("catch stop signal channel closed")
            }
            hwlog.RunLog.Info("RasTopoWriteTask stop")
            return
        default:
            // 检查配置是否就绪,就绪则写入拓扑文件
            if filePath, ok := checkConfigReady(fmt.Sprintf("%d", superPodId)); ok {
                hwlog.RunLog.Infof("ready to wirte topology file of rack to %s", filePath)
                GetTopoFileAndWrite(filePath)
            }
            // 30秒后重试
            time.Sleep(common.TopologyRefreshTime * time.Second)  // 30秒
        }
    }
}

设计要点

  1. A5专属功能:仅 Ascend910A5 芯片启动此任务,其他芯片直接返回
  2. 初始化加锁:读取 HwDevManager 状态时使用 ManagerLock,防止并发修改
  3. 全局变量赋值superPodIdsuperPodTyperackIdserverIndex 是包级变量,初始化后只读
  4. ctx 优雅退出:通过 select { case <-ctx.Done(): } 感知上下文取消
  5. default 分支:ctx 未取消时执行 checkConfigReady,然后 sleep 30秒
7.3.2 checkConfigReady() — 前置条件检查

Yes

No

No

Yes

Yes

No

No

Yes

No

Yes

Yes

No

checkConfigReady(superPodIdStr)

1. GetSuperPodInfoFilePath()
获取super-pod-x.json路径

err?

return '', false

2. filepath.Dir(filePath)
获取父目录

3. utils.IsLexist(fileParentDir)
检查父目录是否存在

存在?

Info: 父目录不存在
return '', false

4. GetRackTopologyFilePath()
获取Rack拓扑文件路径

err?

return '', false

5. filepath.Dir(topoFile)
获取Rack目录

6. utils.IsLexist(rackDir)
检查Rack目录是否存在

存在?

os.MkdirAll(rackDir, 0755)
创建目录

err?

Error: 创建目录失败
return '', false

os.Chmod(rackDir, 0755)

err?

Error: chmod失败
return '', false

return topoFile, true
就绪!

func checkConfigReady(superPodIdStr string) (string, bool) {
    // 步骤1: 获取 super-pod-x.json 的文件路径
    // slownet 模块根据 superPodId 和 ConfigMap 前缀构建路径
    filePath, err := slownet.GetSuperPodInfoFilePath(superPodIdStr, publishCmNamePrefix)
    if err != nil {
        hwlog.RunLog.Errorf("get superpod topo file path err: %v", err)
        return "", false
    }

    // 步骤2: 检查 super-pod-x 父目录是否存在
    fileParentDir := filepath.Dir(filePath)
    if !utils.IsLexist(fileParentDir) {
        hwlog.RunLog.Infof("superpod topo file parent dir %s is not exist", fileParentDir)
        return "", false  // 父目录不存在说明super-pod配置尚未就绪
    }
    hwlog.RunLog.Infof("the super-pod-x dir is exist in %s", fileParentDir)

    // 步骤3: 获取 Rack 拓扑文件的目标路径
    // 路径包含 superPodId、rackId、serverIndex 信息
    topoFile, err := slownet.GetRackTopologyFilePath(superPodId, rackId, serverIndex)
    if err != nil {
        hwlog.RunLog.Errorf("get rack topo file path err: %v", err)
        return "", false
    }

    // 步骤4: 确保 Rack 目录存在
    rackDir := filepath.Dir(topoFile)
    if !utils.IsLexist(rackDir) {
        hwlog.RunLog.Infof("%s topo path is not exist and will create it", rackDir)
        if mkErr := os.MkdirAll(rackDir, rackDirPerm); mkErr != nil {
            hwlog.RunLog.Errorf("create dir failed rack dir %s, err: %v", rackDir, mkErr)
            return "", false
        }
        hwlog.RunLog.Infof("create rack dir %s success", rackDir)
    }

    // 步骤5: 确保 Rack 目录权限正确
    if chErr := os.Chmod(rackDir, rackDirPerm); chErr != nil {
        hwlog.RunLog.Errorf("change mod failed rack dir %s, err: %v", rackDir, chErr)
        return "", false
    }

    return topoFile, true
}

设计意图

  1. 两阶段就绪检查:先检查 super-pod 配置就绪,再检查 rack 目录就绪
  2. 自动创建目录:rack 目录不存在时自动创建,适应首次运行场景
  3. 权限显式设置Chmod 确保 0755 权限,即使 MkdirAll 受 umask 影响
  4. 幂等性:每次循环都检查,目录已存在则跳过创建
7.3.3 GetTopoFileAndWrite() — 写入拓扑文件
func GetTopoFileAndWrite(topoJsonFile string) {
    // 根据superPodType查找对应的源拓扑文件路径
    path, exist := topoFilePathMap[superPodType]
    if !exist {
        hwlog.RunLog.Errorf("super pod type:<%d> topo path not exist", superPodType)
        return
    }
    // 调用 topo.go 的 ToFile 函数:从源路径读取,写入目标路径
    if err := ToFile(topoJsonFile, path); err != nil {
        hwlog.RunLog.Errorf("write topology info of RackID=%d SuperPodID=%d failed, err is %s",
            rackId, superPodId, err.Error())
    }
    hwlog.RunLog.Infof("write rack %d topology info to file %s success", rackId, topoJsonFile)
}

注意:此处 path源文件(驱动生成的拓扑文件),topoJsonFile目标文件(RAS路径)。ToFile(目标, 源) 的参数顺序需要注意。

7.3.4 完整数据流

slownet模块

RAS目标路径

topo.go

rack_topology.go

源拓扑文件(按类型)

驱动层

读取

Ascend驱动
生成拓扑JSON

atlas_850_1.json
(Server 8P)

atlas_950_1.json
(Pod 1D)

atlas_950_2.json
(Pod 2D)

atlas_850_2.json
(Server 16P)

atlas_850_3.json
(Server 32P)

topoFilePathMap
类型→路径映射

GetTopoFileAndWrite

ToFile()
读取+哈希比较+写入

RAS_NET_ROOT_PATH/
super-pod-x/rack-y/
topo.json

slownet 消费拓扑
进行慢网容错


八、模块间调用关系总览

8.1 完整调用图

topology/

customname/

driver_init.go

entry.go

main.go

非A5

就绪

flag.Parse()

initLogModule()

checkParam()
13项校验

setParameters()

devicefactory.InitFunction()

hdm.DoSetMultiDiePolicyForA3()

setUseAscendDocker()

go hdm.ListenDevice()

go hdm.ListenDpu()

go topology.RasTopoWriteTask()

duplicatedetector.CheckDuplicateDevices()

hdm.SignCatch()

customname.InitPublicNameConfig()

initDevManager()

server.NewHwDevManager()

配置 SwitchDevManager

devmanager.AutoInit()

A5检查

NewSwitchDevManager()

InitSwitchDev()

loadFaultCodeFromFile()

checkName()

setDefaultName()

devNameMap 填充

RasTopoWriteTask()

checkConfigReady()

GetTopoFileAndWrite()

ToFile()

topoFileToStr()

getFileHash()

8.2 数据流总览

输出

处理

输入

命令行参数

环境变量
ASCEND_DOCKER_RUNTIME

JSON配置文件
deviceNameCustomization.json

驱动拓扑文件
atlas_*.json

Flag 解析

common.ParamOption
全局配置

devNameMap
命名映射表

HwDevManager
设备管理器

拓扑数据

Kubernetes
节点标签/设备注册

RAS拓扑文件

重复检测结果

日志输出

8.3 错误处理策略总览

完全跳过(非目标场景)

非A5芯片
跳过RasTopoWriteTask

A5芯片
跳过交换设备初始化

非致命降级(Warn后继续)

自定义命名配置不存在
(Info, 使用默认命名)

自定义命名配置无效
(Warn, 使用默认命名)

交换设备初始化失败
(Warn, 不处理交换故障)

拓扑写入失败
(Error, 30秒后重试)

RAS路径不存在
(Info, 30秒后重试)

致命错误(return 退出)

日志初始化失败

参数校验失败

驱动初始化失败
(devmanager.AutoInit)

设备管理器创建失败
(NewHwDevManager返回nil)

8.4 并发模型

同步调用

后台goroutine

主goroutine

go

go

go

cancel()

cancel()

cancel()

ctx.Done()

ctx.Done()

ctx.Done()

main()

SignCatch(cancel)
阻塞等待信号

goroutine 1
hdm.ListenDevice(ctx)
设备状态监听

goroutine 2
hdm.ListenDpu(ctx)
DPU监听

goroutine 3
topology.RasTopoWriteTask(ctx, hdm)
拓扑写入(30s循环)

duplicatedetector.CheckDuplicateDevices
(在SignCatch之前同步完成)

优雅退出

优雅退出

优雅退出

并发设计要点

  • 3个后台 goroutine 共享同一个 ctx,通过 cancel() 统一通知退出
  • CheckDuplicateDevices 是同步调用,在 SignCatch 之前完成
  • SignCatch 是阻塞调用,主 goroutine 在此等待信号
  • 收到信号 → cancel() → ctx.Done() → 各 goroutine 退出 → 主 goroutine 返回

附录:文件索引

文件 行数 职责
main.go 343 程序入口、参数解析、参数校验、全局配置注入、生命周期编排
pkg/next/devicefactory/entry.go 47 工厂入口函数,协调命名初始化、驱动初始化、管理器创建
pkg/next/devicefactory/driver_init.go 43 底层驱动自动初始化,交换设备条件初始化
pkg/next/devicefactory/customname/public_name_config.go 274 设备自定义命名映射,Replace系列名称替换函数
pkg/topology/topo.go 116 拓扑文件读取、SHA256哈希比较、文件写入
pkg/topology/rack_topology.go 138 A5芯片Rack拓扑后台写入任务,30秒循环

文档生成时间:2026-07-31
分析基于:mind-cluster-v26.0.1 / ascend-device-plugin

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐