github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/providers/firecracker/run_instance.go (about)

     1  package firecracker
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"time"
    10  
    11  	firecrackersdk "github.com/firecracker-microvm/firecracker-go-sdk"
    12  
    13  	"github.com/emc-advanced-dev/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  	"github.com/solo-io/unik/pkg/providers/common"
    16  	"github.com/solo-io/unik/pkg/types"
    17  )
    18  
    19  func toblk(s string) firecrackersdk.BlockDevice {
    20  	return firecrackersdk.BlockDevice{
    21  		HostPath: s,
    22  		Mode:     "rw",
    23  	}
    24  }
    25  
    26  func (p *FirecrackerProvider) RunInstance(params types.RunInstanceParams) (_ *types.Instance, err error) {
    27  
    28  	logrus.WithFields(logrus.Fields{
    29  		"image-id": params.ImageId,
    30  		"mounts":   params.MntPointsToVolumeIds,
    31  		"env":      params.Env,
    32  	}).Infof("running instance %s", params.Name)
    33  
    34  	if _, err := p.GetInstance(params.Name); err == nil {
    35  		return nil, errors.New("instance with name "+params.Name+" already exists. firecracker provider requires unique names for instances", nil)
    36  	}
    37  
    38  	image, err := p.GetImage(params.ImageId)
    39  	if err != nil {
    40  		return nil, errors.New("getting image", err)
    41  	}
    42  
    43  	if err := common.VerifyMntsInput(p, image, params.MntPointsToVolumeIds); err != nil {
    44  		return nil, errors.New("invalid mapping for volume", err)
    45  	}
    46  
    47  	instanceId := params.Name
    48  	instanceDir := getInstanceDir(instanceId)
    49  
    50  	if _, err := os.Stat(instanceDir); os.IsNotExist(err) {
    51  		err = os.Mkdir(instanceDir, 0755)
    52  		if err != nil {
    53  			return nil, errors.New("can't create instance dir", err)
    54  		}
    55  	}
    56  
    57  	logs := filepath.Join(instanceDir, "logs.fifo")
    58  	metrics := filepath.Join(instanceDir, "metrics.fifo")
    59  	sock := filepath.Join(instanceDir, "firecracker.sock")
    60  
    61  	if params.InstanceMemory == 0 {
    62  		params.InstanceMemory = image.RunSpec.DefaultInstanceMemory
    63  	}
    64  
    65  	rootDrive := getImagePath(image.Name)
    66  
    67  	volumeIdInOrder := make([]string, len(params.MntPointsToVolumeIds))
    68  
    69  	for mntPoint, volumeId := range params.MntPointsToVolumeIds {
    70  
    71  		controllerPort, err := common.GetControllerPortForMnt(image, mntPoint)
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  		volumeIdInOrder[controllerPort] = volumeId
    76  	}
    77  
    78  	volImagesInOrder, err := p.getVolumeImages(volumeIdInOrder)
    79  	if err != nil {
    80  		return nil, errors.New("can't get volumes", err)
    81  	}
    82  
    83  	fcCfg := firecrackersdk.Config{
    84  		BinPath:          p.config.Binary,
    85  		SocketPath:       sock,
    86  		LogFifo:          logs,
    87  		LogLevel:         "Debug",
    88  		MetricsFifo:      metrics,
    89  		KernelImagePath:  p.config.Kernel,
    90  		KernelArgs:       "console=ttyS0 reboot=k panic=1 pci=off",
    91  		RootDrive:        toblk(rootDrive),
    92  		AdditionalDrives: volPathToBlockDevices(volImagesInOrder),
    93  		// TODO: add these later. NetworkInterfaces: NICs,
    94  		CPUCount:    1,
    95  		CPUTemplate: firecrackersdk.CPUTemplate("C3"),
    96  		HtEnabled:   false,
    97  		MemInMiB:    int64(params.InstanceMemory),
    98  		Debug:       true,
    99  		Console:     p.config.Console,
   100  	}
   101  
   102  	logrus.Debugf("creating firecracker vm")
   103  
   104  	m, err := firecrackersdk.NewMachine(fcCfg, firecrackersdk.WithLogger(logrus.NewEntry(logrus.New())))
   105  	if err != nil {
   106  		logrus.Errorf("Failed creating machine: %s", err)
   107  		return nil, err
   108  	}
   109  
   110  	ctx := context.Background()
   111  	vmmCtx, vmmCancel := context.WithCancel(ctx)
   112  	exitchan, err := m.Init(ctx)
   113  	if err != nil {
   114  		logrus.Errorf("Firecracker Init returned error %s", err)
   115  		return nil, err
   116  	}
   117  
   118  	go func() {
   119  		<-exitchan
   120  		vmmCancel()
   121  	}()
   122  
   123  	err = m.StartInstance(vmmCtx)
   124  	if err != nil {
   125  		return nil, errors.New("can't start firecracker - make sure it's in your path.", nil)
   126  	}
   127  
   128  	// todo: once we have network support we can set this up.
   129  	var instanceIp string
   130  
   131  	instance := &types.Instance{
   132  		Id:             instanceId,
   133  		Name:           params.Name,
   134  		State:          types.InstanceState_Running,
   135  		IpAddress:      instanceIp,
   136  		Infrastructure: types.Infrastructure_FIRECRACKER,
   137  		ImageId:        image.Id,
   138  		Created:        time.Now(),
   139  	}
   140  
   141  	go func() {
   142  		<-vmmCtx.Done()
   143  		p.state.RemoveInstance(instance)
   144  		os.RemoveAll(instanceDir)
   145  	}()
   146  
   147  	if err := p.state.ModifyInstances(func(instances map[string]*types.Instance) error {
   148  		instances[instance.Id] = instance
   149  		return nil
   150  	}); err != nil {
   151  		return nil, errors.New("modifying instance map in state", err)
   152  	}
   153  
   154  	logrus.WithField("instance", instance).Infof("instance created successfully")
   155  
   156  	p.mapLock.Lock()
   157  	p.runningMachines[instanceId] = m
   158  	p.mapLock.Unlock()
   159  
   160  	return instance, nil
   161  }
   162  
   163  func (p *FirecrackerProvider) getVolumeImages(volumeIdInOrder []string) ([]string, error) {
   164  
   165  	var volPath []string
   166  	for _, v := range volumeIdInOrder {
   167  		v, err := p.GetVolume(v)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		volPath = append(volPath, getVolumePath(v.Name))
   172  	}
   173  	return volPath, nil
   174  }
   175  
   176  func volPathToBlockDevices(volPaths []string) []firecrackersdk.BlockDevice {
   177  	var res []firecrackersdk.BlockDevice
   178  	for _, v := range volPaths {
   179  		res = append(res, toblk(v))
   180  	}
   181  	return res
   182  }
   183  
   184  func injectEnv(cmdline string, env map[string]string) string {
   185  	// rump json is not really json so we can't parse it
   186  	var envRumpJson []string
   187  	for key, value := range env {
   188  		envRumpJson = append(envRumpJson, fmt.Sprintf("\"env\": \"%s=%s\"", key, value))
   189  	}
   190  
   191  	cmdline = cmdline[:len(cmdline)-2] + "," + strings.Join(envRumpJson, ",") + "}}"
   192  	return cmdline
   193  }