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 }