github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/engine/virt/virt.go (about) 1 package virt 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/mitchellh/mapstructure" 15 "github.com/projecteru2/core/cluster" 16 "github.com/projecteru2/core/engine" 17 enginetypes "github.com/projecteru2/core/engine/types" 18 "github.com/projecteru2/core/log" 19 resourcetypes "github.com/projecteru2/core/resource/types" 20 coresource "github.com/projecteru2/core/source" 21 "github.com/projecteru2/core/types" 22 coretypes "github.com/projecteru2/core/types" 23 virtapi "github.com/projecteru2/libyavirt/client" 24 virttypes "github.com/projecteru2/libyavirt/types" 25 ) 26 27 const ( 28 // GRPCPrefixKey indicates grpc yavirtd 29 GRPCPrefixKey = "virt-grpc://" 30 // ImageUserKey indicates the image's owner 31 ImageUserKey = "ImageUser" 32 // DmiUUIDKey indicates the key within deploy info. 33 DmiUUIDKey = "DMIUUID" 34 // Type indicate type 35 Type = "virt" 36 ) 37 38 // Virt implements the core engine.API interface. 39 type Virt struct { 40 client virtapi.Client 41 config coretypes.Config 42 } 43 44 // MakeClient makes a virt. client which wraps yavirt API client. 45 func MakeClient(_ context.Context, config coretypes.Config, nodename, endpoint, ca, _, _ string) (engine.API, error) { 46 var uri string 47 switch { 48 case strings.HasPrefix(endpoint, GRPCPrefixKey): 49 uri = "grpc://" + strings.TrimPrefix(endpoint, GRPCPrefixKey) 50 default: 51 return nil, coretypes.ErrInvaildEngineEndpoint 52 } 53 54 yCfg := &virttypes.Config{ 55 URI: uri, 56 } 57 if ca != "" { 58 caFile, err := os.CreateTemp(config.CertPath, fmt.Sprintf("ca-%s", nodename)) 59 if err != nil { 60 return nil, err 61 } 62 if _, err := caFile.WriteString(ca); err != nil { 63 return nil, err 64 } 65 defer os.Remove(caFile.Name()) 66 yCfg.CA = caFile.Name() 67 } 68 cli, err := virtapi.New(yCfg) 69 if err != nil { 70 return nil, err 71 } 72 return &Virt{cli, config}, nil 73 } 74 75 // Info shows a connected node's information. 76 func (v *Virt) Info(ctx context.Context) (*enginetypes.Info, error) { 77 resp, err := v.client.Info(ctx) 78 if err != nil { 79 return nil, err 80 } 81 82 return &enginetypes.Info{ 83 Type: Type, 84 ID: resp.ID, 85 NCPU: resp.CPU, 86 MemTotal: resp.Mem, 87 StorageTotal: resp.Storage, 88 Resources: resp.Resources, 89 }, nil 90 } 91 92 // Ping tests connection. 93 func (v *Virt) Ping(ctx context.Context) error { 94 _, err := v.client.Info(ctx) 95 return err 96 } 97 98 // CloseConn closes the connection. 99 func (v *Virt) CloseConn() error { 100 return v.client.Close() 101 } 102 103 // Execute executes a command in vm 104 // in tty mode, 'execID' return value indicates the execID which has the pattern '%s_%s', at other times it indicates the pid 105 func (v *Virt) Execute(ctx context.Context, ID string, config *enginetypes.ExecConfig) (execID string, stdout, stderr io.ReadCloser, stdin io.WriteCloser, err error) { 106 if config.Tty { 107 flags := virttypes.AttachGuestFlags{Safe: true, Force: true} 108 execID, stream, err := v.client.AttachGuest(ctx, ID, config.Cmd, flags) 109 if err != nil { 110 return "", nil, nil, nil, err 111 } 112 return execID, io.NopCloser(stream), nil, stream, nil 113 } 114 msg, err := v.client.ExecuteGuest(ctx, ID, config.Cmd) 115 return strconv.Itoa(msg.Pid), io.NopCloser(bytes.NewReader(msg.Data)), nil, nil, err 116 } 117 118 // ExecExitCode get return code of a specific execution. 119 func (v *Virt) ExecExitCode(ctx context.Context, ID, execID string) (code int, err error) { 120 if strings.HasPrefix(execID, virttypes.MagicPrefix) { 121 return 0, nil 122 } 123 124 intPid, err := strconv.Atoi(execID) 125 if err != nil { 126 return -1, err 127 } 128 code, err = v.client.ExecExitCode(ctx, ID, intPid) 129 if err != nil { 130 return -1, err 131 } 132 return 133 } 134 135 // ExecResize resize exec tty 136 func (v *Virt) ExecResize(ctx context.Context, execID string, height, width uint) (err error) { 137 return v.client.ResizeConsoleWindow(ctx, execID, height, width) 138 } 139 140 // NetworkConnect connects to a network. 141 func (v *Virt) NetworkConnect(ctx context.Context, network, target, ipv4, _ string) (cidrs []string, err error) { 142 req := virttypes.ConnectNetworkReq{ 143 Network: network, 144 IPv4: ipv4, 145 } 146 req.ID = target 147 148 var cidr string 149 if cidr, err = v.client.ConnectNetwork(ctx, req); err != nil { 150 return 151 } 152 153 cidrs = append(cidrs, cidr) 154 155 return 156 } 157 158 // NetworkDisconnect disconnects from one network. 159 func (v *Virt) NetworkDisconnect(ctx context.Context, network, target string, _ bool) (err error) { 160 var req virttypes.DisconnectNetworkReq 161 req.Network = network 162 req.ID = target 163 164 _, err = v.client.DisconnectNetwork(ctx, req) 165 166 return 167 } 168 169 // NetworkList lists all networks. 170 func (v *Virt) NetworkList(ctx context.Context, drivers []string) (nets []*enginetypes.Network, err error) { 171 networks, err := v.client.NetworkList(ctx, drivers) 172 if err != nil { 173 return nil, err 174 } 175 176 for _, network := range networks { 177 nets = append(nets, &enginetypes.Network{ 178 Name: network.Name, 179 Subnets: network.Subnets, 180 }) 181 } 182 return 183 } 184 185 // BuildRefs builds references. 186 func (v *Virt) BuildRefs(_ context.Context, opts *enginetypes.BuildRefOptions) (refs []string) { 187 return []string{combineUserImage(opts.User, opts.Name)} 188 } 189 190 // BuildContent builds content, the use of it is similar to BuildRefs. 191 func (v *Virt) BuildContent(_ context.Context, _ coresource.Source, _ *enginetypes.BuildContentOptions) (string, io.Reader, error) { 192 return "", nil, coretypes.ErrEngineNotImplemented 193 } 194 195 // VirtualizationCreate creates a guest. 196 func (v *Virt) VirtualizationCreate(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions) (guest *enginetypes.VirtualizationCreated, err error) { 197 // parse engine args to resource options 198 resourceOpts := &engine.VirtualizationResource{} 199 if err = engine.MakeVirtualizationResource(opts.EngineParams, resourceOpts, func(p resourcetypes.Resources, d *engine.VirtualizationResource) error { 200 for _, v := range p { 201 if err := mapstructure.Decode(v, d); err != nil { 202 return err 203 } 204 } 205 return nil 206 }); err != nil { 207 log.WithFunc("engine.virt.VirtualizationCreate").Errorf(ctx, err, "failed to parse engine args %+v", opts.EngineParams) 208 return nil, coretypes.ErrInvalidEngineArgs 209 } 210 211 vols, err := v.parseVolumes(resourceOpts.Volumes) 212 if err != nil { 213 return nil, err 214 } 215 216 req := virttypes.CreateGuestReq{ 217 CPU: int(resourceOpts.Quota), 218 Mem: resourceOpts.Memory, 219 ImageName: opts.Image, 220 ImageUser: opts.Labels[ImageUserKey], 221 Volumes: vols, 222 Labels: opts.Labels, 223 DmiUUID: opts.Labels[DmiUUIDKey], 224 AncestorID: opts.AncestorWorkloadID, 225 Cmd: opts.Cmd, 226 Lambda: opts.Lambda, 227 Stdin: opts.Stdin, 228 Resources: convertEngineParamsToResources(opts.EngineParams), 229 } 230 231 var resp virttypes.Guest 232 if resp, err = v.client.CreateGuest(ctx, req); err != nil { 233 return nil, err 234 } 235 236 return &enginetypes.VirtualizationCreated{ 237 ID: resp.ID, 238 Name: opts.Name, 239 Labels: resp.Labels, 240 }, nil 241 } 242 243 // VirtualizationCopyTo copies one. 244 func (v *Virt) VirtualizationCopyTo(ctx context.Context, ID, dest string, content []byte, _, _ int, _ int64) error { 245 return v.client.CopyToGuest(ctx, ID, dest, bytes.NewReader(content), true, true) 246 } 247 248 // VirtualizationCopyChunkTo copies one. 249 func (v *Virt) VirtualizationCopyChunkTo(ctx context.Context, ID, dest string, _ int64, content io.Reader, _, _ int, _ int64) error { 250 return v.client.CopyToGuest(ctx, ID, dest, content, true, true) 251 } 252 253 // VirtualizationStart boots a guest. 254 func (v *Virt) VirtualizationStart(ctx context.Context, ID string) (err error) { 255 _, err = v.client.StartGuest(ctx, ID) 256 return 257 } 258 259 // VirtualizationStop stops it. 260 func (v *Virt) VirtualizationStop(ctx context.Context, ID string, gracefulTimeout time.Duration) (err error) { 261 _, err = v.client.StopGuest(ctx, ID, gracefulTimeout == 0) 262 return 263 } 264 265 // VirtualizationRemove removes a guest. 266 func (v *Virt) VirtualizationRemove(ctx context.Context, ID string, _, force bool) (err error) { 267 if _, err = v.client.DestroyGuest(ctx, ID, force); err == nil { 268 return nil 269 } 270 if strings.Contains(err.Error(), "key not exists") { 271 return types.ErrWorkloadNotExists 272 } 273 return 274 } 275 276 // VirtualizationSuspend suspends a guest. 277 func (v *Virt) VirtualizationSuspend(ctx context.Context, ID string) (err error) { 278 _, err = v.client.SuspendGuest(ctx, ID) 279 return 280 } 281 282 // VirtualizationResume resumes a guest. 283 func (v *Virt) VirtualizationResume(ctx context.Context, ID string) (err error) { 284 _, err = v.client.ResumeGuest(ctx, ID) 285 return 286 } 287 288 func (v *Virt) RawEngine(ctx context.Context, opts *enginetypes.RawEngineOptions) (res *enginetypes.RawEngineResult, err error) { 289 req := virttypes.RawEngineReq{ 290 ID: opts.ID, 291 Op: opts.Op, 292 Params: opts.Params, 293 } 294 resp, err := v.client.RawEngine(ctx, req) 295 if err != nil { 296 return 297 } 298 res = &enginetypes.RawEngineResult{ 299 ID: resp.ID, 300 Data: resp.Data, 301 } 302 return 303 } 304 305 // VirtualizationInspect gets a guest. 306 func (v *Virt) VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error) { 307 guest, err := v.client.GetGuest(ctx, ID) 308 if err != nil { 309 return nil, err 310 } 311 312 info := &enginetypes.VirtualizationInfo{ 313 ID: guest.ID, 314 Image: guest.ImageName, 315 Running: guest.Status == "running", 316 Networks: guest.Networks, 317 Labels: guest.Labels, 318 } 319 320 if info.Labels == nil { 321 info.Labels = make(map[string]string) 322 } 323 324 content, err := json.Marshal(coretypes.LabelMeta{Publish: []string{"PORT"}}) 325 if err != nil { 326 return nil, err 327 } 328 329 info.Labels[cluster.LabelMeta] = string(content) 330 info.Labels[cluster.ERUMark] = "1" 331 332 return info, nil 333 } 334 335 // VirtualizationLogs streams a specific guest's log 336 func (v *Virt) VirtualizationLogs(ctx context.Context, opts *enginetypes.VirtualizationLogStreamOptions) (stdout io.ReadCloser, stderr io.ReadCloser, err error) { 337 n := -1 338 if opts.Tail != "all" && opts.Tail != "" { 339 if n, err = strconv.Atoi(opts.Tail); err != nil { 340 return nil, nil, err 341 } 342 } 343 344 stream, err := v.client.Log(ctx, n, opts.ID) 345 return stream, nil, err 346 } 347 348 // VirtualizationAttach attaches something to a guest. 349 func (v *Virt) VirtualizationAttach(ctx context.Context, ID string, _, _ bool) (stdout, stderr io.ReadCloser, stdin io.WriteCloser, err error) { 350 flags := virttypes.AttachGuestFlags{Safe: true, Force: true} 351 _, attachGuest, err := v.client.AttachGuest(ctx, ID, []string{}, flags) 352 if err != nil { 353 return nil, nil, nil, err 354 } 355 return io.NopCloser(attachGuest), nil, attachGuest, nil 356 } 357 358 // VirtualizationResize resized window size 359 func (v *Virt) VirtualizationResize(ctx context.Context, ID string, height, width uint) error { 360 return v.client.ResizeConsoleWindow(ctx, ID, height, width) 361 } 362 363 // VirtualizationWait is waiting for a shut-off 364 func (v *Virt) VirtualizationWait(ctx context.Context, ID, _ string) (*enginetypes.VirtualizationWaitResult, error) { 365 r := &enginetypes.VirtualizationWaitResult{} 366 msg, err := v.client.WaitGuest(ctx, ID, true) 367 if err != nil { 368 r.Message = err.Error() 369 r.Code = -1 370 return r, err 371 } 372 373 r.Message = msg.Msg 374 r.Code = msg.Code 375 return r, nil 376 } 377 378 // VirtualizationUpdateResource updates resource. 379 func (v *Virt) VirtualizationUpdateResource(ctx context.Context, ID string, engineParams resourcetypes.Resources) error { 380 // parse engine args to resource options 381 resourceOpts := &engine.VirtualizationResource{} 382 if err := engine.MakeVirtualizationResource(engineParams, resourceOpts, func(p resourcetypes.Resources, d *engine.VirtualizationResource) error { 383 for _, v := range p { 384 if err := mapstructure.Decode(v, d); err != nil { 385 return err 386 } 387 } 388 return nil 389 }); err != nil { 390 log.WithFunc("engine.virt.VirtualizationUpdateResource").Errorf(ctx, err, "failed to parse engine args %+v", engineParams) 391 return err 392 } 393 394 vols, err := v.parseVolumes(resourceOpts.Volumes) 395 if err != nil { 396 return err 397 } 398 399 args := virttypes.ResizeGuestReq{ 400 CPU: int(resourceOpts.Quota), 401 Mem: resourceOpts.Memory, 402 Volumes: vols, 403 Resources: convertEngineParamsToResources(engineParams), 404 } 405 args.ID = ID 406 407 _, err = v.client.ResizeGuest(ctx, args) 408 return err 409 } 410 411 // VirtualizationCopyFrom copies file content from the container. 412 func (v *Virt) VirtualizationCopyFrom(ctx context.Context, ID, path string) (content []byte, uid, gid int, mode int64, err error) { 413 // TODO@zc: virt shall return the properties too 414 rd, err := v.client.Cat(ctx, ID, path) 415 if err != nil { 416 return 417 } 418 content, err = io.ReadAll(rd) 419 return 420 }