Skip to content

Commit 1d3f785

Browse files
author
houyuxi
committed
refactor(server): split server package into multiple files
1 parent 444d4f5 commit 1d3f785

4 files changed

Lines changed: 458 additions & 421 deletions

File tree

internal/server/allocate.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package server
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strconv"
7+
8+
v1 "k8s.io/api/core/v1"
9+
"k8s.io/klog/v2"
10+
"k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
11+
12+
"github.com/Project-HAMi/HAMi/pkg/device"
13+
"github.com/Project-HAMi/HAMi/pkg/device-plugin/nvidiadevice/nvinternal/plugin"
14+
"github.com/Project-HAMi/HAMi/pkg/util"
15+
)
16+
17+
// buildContainerAllocateResponse builds the allocate response for a single container.
18+
func (ps *PluginServer) buildContainerAllocateResponse(pod *v1.Pod, containerDevs device.ContainerDevices, rtInfoLookup map[string]RuntimeInfo) (*v1beta1.ContainerAllocateResponse, error) {
19+
resp := &v1beta1.ContainerAllocateResponse{}
20+
21+
var (
22+
IDs []int32
23+
memories []*int64
24+
cores []*int32
25+
ascendVNPUSpec string
26+
)
27+
28+
for _, dev := range containerDevs {
29+
d := ps.mgr.GetDeviceByUUID(dev.UUID)
30+
if d == nil {
31+
return nil, fmt.Errorf("unknown uuid: %s", dev.UUID)
32+
}
33+
IDs = append(IDs, d.PhyID)
34+
35+
if info, ok := rtInfoLookup[dev.UUID]; ok {
36+
if ascendVNPUSpec == "" && info.Temp != "" {
37+
ascendVNPUSpec = info.Temp
38+
}
39+
if info.Memory != nil {
40+
memories = append(memories, info.Memory)
41+
}
42+
if info.Core != nil {
43+
cores = append(cores, info.Core)
44+
}
45+
}
46+
}
47+
48+
if len(IDs) == 0 {
49+
return nil, fmt.Errorf("annotation %s value invalid", ps.allocAnno)
50+
}
51+
ascendVisibleDevices := fmt.Sprintf("%d", IDs[0])
52+
for i := 1; i < len(IDs); i++ {
53+
ascendVisibleDevices = fmt.Sprintf("%s,%d", ascendVisibleDevices, IDs[i])
54+
}
55+
resp.Envs = make(map[string]string)
56+
resp.Envs["ASCEND_VISIBLE_DEVICES"] = ascendVisibleDevices
57+
58+
vnpuMode := pod.Annotations[VNPUModeAnnotation]
59+
klog.V(4).Infof("Pod %s vnpu mode: %s", pod.Name, vnpuMode)
60+
if vnpuMode == VNPUModeHamiCore {
61+
// 1. Handle volume mount injection
62+
var mounts []*v1beta1.Mount
63+
// A.Huawei driver and SMI toolchain (Read-Only)
64+
driverPaths := []string{
65+
"/usr/local/bin/npu-smi",
66+
"/etc/ascend_install.info",
67+
"/usr/local/Ascend/driver/lib64/driver",
68+
"/usr/local/Ascend/driver/version.info",
69+
}
70+
for _, p := range driverPaths {
71+
mounts = append(mounts, &v1beta1.Mount{HostPath: p, ContainerPath: p, ReadOnly: true})
72+
}
73+
74+
mounts = append(mounts, &v1beta1.Mount{
75+
HostPath: "/usr/local/hami-vnpu-core",
76+
ContainerPath: "/hami-vnpu-core",
77+
ReadOnly: true,
78+
})
79+
// B. Inject HAMi library path by mounting /etc/ld.so.preload.
80+
mounts = append(mounts, &v1beta1.Mount{
81+
HostPath: "/usr/local/hami-vnpu-core/ld.so.preload", // Template file on host
82+
ContainerPath: "/etc/ld.so.preload", // Overwrites the target file in container
83+
ReadOnly: true,
84+
})
85+
86+
// C. Shared directory for HAMi compute resource partitioning (Read/Write)
87+
mounts = append(mounts, &v1beta1.Mount{
88+
HostPath: "/usr/local/hami-shared-region",
89+
ContainerPath: "/hami-shared-region",
90+
ReadOnly: false,
91+
})
92+
resp.Mounts = mounts
93+
94+
// Set NPU_MEM_QUOTA
95+
if len(memories) > 0 && memories[0] != nil {
96+
resp.Envs["NPU_MEM_QUOTA"] = strconv.FormatInt(*memories[0], 10)
97+
klog.V(4).InfoS("Memory quota set", "value", *memories[0])
98+
}
99+
100+
// Set NPU_PRIORITY
101+
if len(cores) > 0 && cores[0] != nil {
102+
resp.Envs["NPU_PRIORITY"] = strconv.FormatInt(int64(*cores[0]), 10)
103+
klog.V(4).InfoS("Core priority set", "value", *cores[0])
104+
}
105+
106+
// Set GLOBAL_SHM_PATH based on the first device ID.
107+
resp.Envs["NPU_GLOBAL_SHM_PATH"] = fmt.Sprintf("/hami-shared-region/%d_global_registry", IDs[0])
108+
klog.V(5).Infof("Create %d_global_registry", IDs[0])
109+
} else {
110+
if ascendVNPUSpec != "" {
111+
resp.Envs["ASCEND_VNPU_SPECS"] = ascendVNPUSpec
112+
}
113+
}
114+
return resp, nil
115+
}
116+
117+
// popNextContainerDevices finds and erases the first non-empty containerDevices
118+
// from podSingleDev. It mutates podSingleDev in place.
119+
func (ps *PluginServer) popNextContainerDevices(podSingleDev device.PodSingleDevice) (device.ContainerDevices, error) {
120+
for i, ctrDevs := range podSingleDev {
121+
if len(ctrDevs) > 0 {
122+
podSingleDev[i] = device.ContainerDevices{}
123+
return ctrDevs, nil
124+
}
125+
}
126+
return nil, fmt.Errorf("no pending device allocation found")
127+
}
128+
129+
// decodeDeviceAnnotations decodes the pod's device allocation annotation
130+
// (registered as hami.io/<commonword>-devices-to-allocate in InRequestDevices)
131+
// into a PodSingleDevice.
132+
func (ps *PluginServer) decodeDeviceAnnotations(pod *v1.Pod) (device.PodSingleDevice, error) {
133+
pdevices, err := device.DecodePodDevices(device.InRequestDevices, pod.Annotations)
134+
if err != nil {
135+
return nil, err
136+
}
137+
pd, ok := pdevices[ps.commonWord]
138+
if !ok {
139+
return nil, fmt.Errorf("device %s not found in pod annotations", ps.commonWord)
140+
}
141+
return pd, nil
142+
}
143+
144+
// buildRuntimeInfoLookup builds a UUID-to-RuntimeInfo lookup from the pod's allocAnno annotation.
145+
func (ps *PluginServer) buildRuntimeInfoLookup(pod *v1.Pod) (map[string]RuntimeInfo, error) {
146+
anno, ok := pod.Annotations[ps.allocAnno]
147+
if !ok {
148+
return nil, fmt.Errorf("annotation %s not set", ps.allocAnno)
149+
}
150+
var rtInfo []RuntimeInfo
151+
if err := json.Unmarshal([]byte(anno), &rtInfo); err != nil {
152+
return nil, fmt.Errorf("annotation %s value %s invalid: %w", ps.allocAnno, anno, err)
153+
}
154+
lookup := make(map[string]RuntimeInfo, len(rtInfo))
155+
for _, info := range rtInfo {
156+
if info.UUID != "" {
157+
lookup[info.UUID] = info
158+
}
159+
}
160+
return lookup, nil
161+
}
162+
163+
// patchErasedAnnotation patches the pod's device annotation with the given
164+
// podSingleDev. It also updates pod.Annotations in place.
165+
func (ps *PluginServer) patchErasedAnnotation(pod *v1.Pod, podSingleDev device.PodSingleDevice) error {
166+
klog.V(5).Infof("After erase annotation, remaining devices: %v", podSingleDev)
167+
newAnnoValue := device.EncodePodSingleDevice(podSingleDev)
168+
newAnnos := map[string]string{
169+
ps.toAllocDeviceAnno: newAnnoValue,
170+
}
171+
if err := util.PatchPodAnnotations(pod, newAnnos); err != nil {
172+
return err
173+
}
174+
pod.Annotations[ps.toAllocDeviceAnno] = newAnnoValue
175+
return nil
176+
}
177+
178+
// podAllocationTrySuccess checks if all containers of this pod have been
179+
// allocated. If so, it sets bind-phase to "success" and releases the node
180+
// lock; otherwise it returns without setting bind-phase or releasing the lock,
181+
// waiting for the next Allocate call.
182+
func (ps *PluginServer) podAllocationTrySuccess(pod *v1.Pod) {
183+
plugin.PodAllocationTrySuccess(ps.nodeName, ps.commonWord, NodeLockAscend, pod)
184+
}
185+
186+
// podAllocationFailed sets bind-phase to "failed" and releases the node lock.
187+
func (ps *PluginServer) podAllocationFailed(pod *v1.Pod) {
188+
plugin.PodAllocationFailed(ps.nodeName, pod, NodeLockAscend)
189+
}

internal/server/register.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package server
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"path"
8+
"strings"
9+
"time"
10+
11+
"google.golang.org/grpc"
12+
"google.golang.org/grpc/credentials/insecure"
13+
"k8s.io/klog/v2"
14+
"k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
15+
16+
"github.com/Project-HAMi/HAMi/pkg/device"
17+
"github.com/Project-HAMi/HAMi/pkg/util"
18+
)
19+
20+
func (ps *PluginServer) watchAndRegister() {
21+
timer := time.After(1 * time.Second)
22+
for {
23+
select {
24+
case <-ps.stopCh:
25+
klog.Infof("stop watch and register")
26+
return
27+
case <-timer:
28+
}
29+
unhealthy := ps.mgr.GetUnHealthIDs()
30+
if len(unhealthy) > 0 {
31+
if err := ps.mgr.UpdateDevice(); err != nil {
32+
klog.Errorf("update device error: %v", err)
33+
timer = time.After(5 * time.Second)
34+
continue
35+
}
36+
ps.healthCh <- unhealthy[0]
37+
}
38+
err := ps.registerHAMi()
39+
if err != nil {
40+
klog.Errorf("register HAMi error: %v", err)
41+
timer = time.After(5 * time.Second)
42+
} else {
43+
klog.V(3).Infof("register HAMi success")
44+
timer = time.After(30 * time.Second)
45+
}
46+
}
47+
}
48+
49+
func (ps *PluginServer) registerHAMi() error {
50+
devs := ps.mgr.GetDevices()
51+
apiDevices := make([]*device.DeviceInfo, 0, len(devs))
52+
// hami currently believes that the index starts from 0 and is continuous.
53+
for i, dev := range devs {
54+
device := &device.DeviceInfo{
55+
Index: uint(i),
56+
ID: dev.UUID,
57+
Count: int32(ps.mgr.VDeviceCount()),
58+
Devmem: int32(dev.Memory),
59+
Devcore: dev.AICore,
60+
Type: ps.mgr.CommonWord(),
61+
Numa: 0,
62+
Health: dev.Health,
63+
}
64+
if strings.HasPrefix(device.Type, Ascend910Prefix) {
65+
NetworkID, err := ps.getDeviceNetworkID(i, device.Type)
66+
if err != nil {
67+
return fmt.Errorf("get networkID error: %w", err)
68+
}
69+
device.CustomInfo = map[string]any{
70+
"NetworkID": NetworkID,
71+
}
72+
}
73+
apiDevices = append(apiDevices, device)
74+
}
75+
annos := make(map[string]string)
76+
annos[ps.registerAnno] = device.MarshalNodeDevices(apiDevices)
77+
annos[ps.handshakeAnno] = "Reported_" + time.Now().Add(time.Duration(*reportTimeOffset)*time.Second).Format("2006.01.02 15:04:05")
78+
79+
if ps.mgr.IsHamiVnpuCore() {
80+
annos[VNPUNodeSelectorAnnotation] = "true"
81+
klog.V(4).Infof("Node %s has HamiVnpuCore enabled, patching annotation %s: true", ps.nodeName, VNPUNodeSelectorAnnotation)
82+
} else {
83+
annos[VNPUNodeSelectorAnnotation] = "false"
84+
}
85+
86+
node, err := util.GetNode(ps.nodeName)
87+
if err != nil {
88+
return fmt.Errorf("get node %s error: %w", ps.nodeName, err)
89+
}
90+
err = util.PatchNodeAnnotations(node, annos)
91+
if err != nil {
92+
return fmt.Errorf("patch node %s annotations error: %w", ps.nodeName, err)
93+
}
94+
klog.V(5).Infof("patch node %s annotations: %v", ps.nodeName, annos)
95+
return nil
96+
}
97+
98+
func (ps *PluginServer) getDeviceNetworkID(idx int, deviceType string) (int, error) {
99+
// For Ascend910C devices, all modules (dies) are interconnected via HCCS
100+
if deviceType == Ascend910CType {
101+
return 0, nil
102+
}
103+
104+
if idx > 3 {
105+
return 1, nil
106+
}
107+
108+
return 0, nil
109+
}
110+
111+
func (ps *PluginServer) registerKubelet() error {
112+
conn, err := ps.dial(v1beta1.KubeletSocket, 5*time.Second)
113+
if err != nil {
114+
return err
115+
}
116+
defer func(conn *grpc.ClientConn) {
117+
_ = conn.Close()
118+
}(conn)
119+
client := v1beta1.NewRegistrationClient(conn)
120+
reqt := &v1beta1.RegisterRequest{
121+
Version: v1beta1.Version,
122+
Endpoint: path.Base(ps.socket),
123+
ResourceName: ps.mgr.ResourceName(),
124+
Options: &v1beta1.DevicePluginOptions{
125+
GetPreferredAllocationAvailable: false,
126+
},
127+
}
128+
129+
_, err = client.Register(context.Background(), reqt)
130+
if err != nil {
131+
return err
132+
}
133+
return nil
134+
}
135+
136+
func (ps *PluginServer) dial(unixSocketPath string, timeout time.Duration) (*grpc.ClientConn, error) {
137+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
138+
defer cancel()
139+
c, err := grpc.DialContext(ctx, unixSocketPath,
140+
grpc.WithTransportCredentials(insecure.NewCredentials()),
141+
grpc.WithBlock(),
142+
grpc.WithContextDialer(func(ctx2 context.Context, addr string) (net.Conn, error) {
143+
var d net.Dialer
144+
return d.DialContext(ctx2, "unix", addr)
145+
}),
146+
)
147+
148+
if err != nil {
149+
return nil, err
150+
}
151+
return c, nil
152+
}

0 commit comments

Comments
 (0)