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  }