github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/compilers/mirage/mirage.go (about) 1 package mirage 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 "strings" 12 13 log "github.com/sirupsen/logrus" 14 "github.com/emc-advanced-dev/pkg/errors" 15 16 "github.com/solo-io/unik/pkg/compilers" 17 unikos "github.com/solo-io/unik/pkg/os" 18 "github.com/solo-io/unik/pkg/types" 19 unikutil "github.com/solo-io/unik/pkg/util" 20 "gopkg.in/yaml.v2" 21 ) 22 23 type Type int 24 25 const ( 26 XenType Type = iota 27 UKVMType 28 VirtioType 29 ) 30 31 type MirageCompiler struct { 32 Type Type 33 } 34 35 func (c *MirageCompiler) CompileRawImage(params types.CompileImageParams) (*types.RawImage, error) { 36 37 sourcesDir := params.SourcesDir 38 39 if err := grantOpamPermissions(sourcesDir); err != nil { 40 return nil, err 41 } 42 var containerToUse string 43 var args []string 44 switch c.Type { 45 case XenType: 46 var err error 47 args, err = parseMirageManifest(sourcesDir) 48 if err != nil { 49 args, err = introspectArguments(sourcesDir) 50 if err != nil { 51 return nil, err 52 } 53 } 54 containerToUse = "compilers-mirage-ocaml-xen" 55 args = append([]string{"configure", "-t", "xen"}, args...) 56 57 case VirtioType: 58 containerToUse = "compilers-mirage-ocaml-ukvm" 59 args = append([]string{"configure", "-t", "virtio"}, args...) 60 61 case UKVMType: 62 containerToUse = "compilers-mirage-ocaml-ukvm" 63 args = append([]string{"configure", "-t", "ukvm"}, args...) 64 default: 65 return nil, errors.New("unknown type", nil) 66 } 67 68 if err := unikutil.NewContainer(containerToUse).WithEntrypoint("mirage").WithVolume(sourcesDir, "/opt/code").Run(args...); err != nil { 69 return nil, err 70 } 71 72 if err := unikutil.NewContainer(containerToUse).WithEntrypoint("/usr/bin/make").WithVolume(sourcesDir, "/opt/code").Run(); err != nil { 73 return nil, err 74 } 75 76 // Extract volume info 77 78 // read xl template file, and see what disks are needed 79 matches, err := filepath.Glob(filepath.Join(params.SourcesDir, "*.xl.in")) 80 if err != nil { 81 return nil, err 82 } 83 84 if len(matches) != 1 { 85 return nil, errors.New("XL file count is wrong", nil) 86 } 87 88 xlFile := matches[0] 89 90 disks, err := getDisks(sourcesDir, xlFile) 91 if err != nil { 92 return nil, err 93 } 94 switch c.Type { 95 case XenType: 96 return c.packageForXen(sourcesDir, disks, params.NoCleanup) 97 case UKVMType: 98 return c.packageForUkvm(sourcesDir, disks, params.NoCleanup) 99 case VirtioType: 100 return c.packageForVirtio(sourcesDir, disks, params.NoCleanup) 101 default: 102 return nil, errors.New("unknown type", nil) 103 } 104 } 105 106 func (c *MirageCompiler) packageForXen(sourcesDir string, disks []string, cleanup bool) (*types.RawImage, error) { 107 // TODO: ukvm package zipfile for ukvm 108 var res types.RawImage 109 res.RunSpec.Compiler = compilers.MIRAGE_OCAML_XEN.String() 110 111 unikernelfile, err := getUnikernelFile(sourcesDir) 112 if err != nil { 113 return nil, err 114 } 115 116 res.RunSpec.DeviceMappings = append(res.RunSpec.DeviceMappings, types.DeviceMapping{MountPoint: "/", DeviceName: "/dev/sda1"}) 117 for _, disk := range disks { 118 res.RunSpec.DeviceMappings = append(res.RunSpec.DeviceMappings, types.DeviceMapping{MountPoint: "xen:" + disk, DeviceName: disk}) 119 } 120 121 // TODO: ukvm package zipfile for ukvm 122 imgFile, err := compilers.BuildBootableImage(unikernelfile, "", false, cleanup) 123 124 if err != nil { 125 return nil, err 126 } 127 128 res.LocalImagePath = imgFile 129 res.StageSpec = types.StageSpec{ 130 ImageFormat: types.ImageFormat_RAW, 131 XenVirtualizationType: types.XenVirtualizationType_Paravirtual, 132 } 133 res.RunSpec.DefaultInstanceMemory = 256 134 135 return &res, nil 136 } 137 138 func (c *MirageCompiler) packageForUkvm(sourcesDir string, disks []string, cleanup bool) (*types.RawImage, error) { 139 return c.packageUnikernel(sourcesDir, disks, cleanup, "ukvm") 140 } 141 func (c *MirageCompiler) packageForVirtio(sourcesDir string, disks []string, cleanup bool) (*types.RawImage, error) { 142 // for qemu we create an empty cmdline file 143 r, err := c.packageUnikernel(sourcesDir, disks, cleanup, "virtio") 144 if err != nil { 145 return r, err 146 } 147 return r, err 148 } 149 150 func (c *MirageCompiler) packageUnikernel(sourcesDir string, disks []string, cleanup bool, unikernel string) (*types.RawImage, error) { 151 // find ukvm-bin -> the monitor 152 // find *.ukvm -> the unikernel 153 154 matches, err := filepath.Glob(filepath.Join(sourcesDir, "*."+unikernel)) 155 if err != nil { 156 return nil, err 157 } 158 159 // filter non relevant Makefile.ukvm 160 var potentialMatches []string 161 for _, m := range matches { 162 if !strings.HasSuffix(m, "Makefile."+unikernel) { 163 potentialMatches = append(potentialMatches, m) 164 } 165 } 166 167 if len(potentialMatches) != 1 { 168 return nil, errors.New(fmt.Sprintf("Ukvm kernel file count is wrong: %v", potentialMatches), nil) 169 } 170 171 kernel := potentialMatches[0] 172 173 // place them in the image directory 174 175 tmpImageDir, err := ioutil.TempDir("", "") 176 if err != nil { 177 return nil, err 178 } 179 180 if err := unikos.CopyFile(kernel, filepath.Join(tmpImageDir, "program.bin")); err != nil { 181 return nil, errors.New("copying bootable image to image dir", err) 182 } 183 184 if unikernel == "ukvm" { 185 monitor := filepath.Join(sourcesDir, "ukvm-bin") 186 if err := unikos.CopyFile(monitor, filepath.Join(tmpImageDir, "ukvm-bin")); err != nil { 187 return nil, errors.New("copying bootable image to image dir", err) 188 } 189 } else { 190 // it is virtio for qemu 191 // mirage doesn't have command line, so create an empty one 192 // this is the hint for qemu to use PV mode 193 f, err := os.Create(filepath.Join(tmpImageDir, "cmdline")) 194 if err != nil { 195 return nil, errors.New("creating empty cmdline for image", err) 196 } 197 f.Close() 198 } 199 200 res := &types.RawImage{} 201 for _, disk := range disks { 202 res.RunSpec.DeviceMappings = append(res.RunSpec.DeviceMappings, types.DeviceMapping{MountPoint: unikernel + ":" + disk, DeviceName: disk}) 203 } 204 if unikernel == "ukvm" { 205 res.RunSpec.Compiler = compilers.MIRAGE_OCAML_UKVM.String() 206 } else { 207 res.RunSpec.Compiler = compilers.MIRAGE_OCAML_QEMU.String() 208 } 209 res.LocalImagePath = tmpImageDir 210 res.StageSpec = types.StageSpec{ 211 ImageFormat: types.ImageFormat_Folder, 212 } 213 res.RunSpec.DefaultInstanceMemory = 256 214 215 return res, nil 216 } 217 218 func (r *MirageCompiler) Usage() *compilers.CompilerUsage { 219 return nil 220 } 221 222 var parseRegEx = regexp.MustCompile(`vdev=(\S+),\s.+?target=@\S+?:(\S+?)@`) 223 224 func getUnikernelFile(sourcesDir string) (string, error) { 225 226 matches, err := filepath.Glob(filepath.Join(sourcesDir, "*.xen")) 227 if err != nil { 228 return "", err 229 } 230 231 if len(matches) != 1 { 232 return "", errors.New("Xen kernel file count is wrong", nil) 233 } 234 235 return matches[0], nil 236 } 237 238 func getDisks(sourcesDir string, xlFile string) ([]string, error) { 239 240 xlFileFile, err := os.Open(xlFile) 241 if err != nil { 242 return nil, err 243 } 244 defer xlFileFile.Close() 245 return getDisksFromReader(sourcesDir, xlFileFile) 246 } 247 248 func getDiskMatchesFromReader(xlFile io.Reader) ([][]string, error) { 249 250 scanner := bufio.NewScanner(xlFile) 251 const diskPrefix = "disk = " 252 var matches [][]string 253 for scanner.Scan() { 254 line := scanner.Text() 255 if strings.HasPrefix(line, diskPrefix) { 256 restOfLine := line[len(diskPrefix):] 257 matches = parseRegEx.FindAllStringSubmatch(restOfLine, -1) 258 break 259 } 260 } 261 if err := scanner.Err(); err != nil { 262 return nil, err 263 } 264 265 return matches, nil 266 267 } 268 269 func getDisksFromReader(sourcesDir string, xlFile io.Reader) ([]string, error) { 270 matches, err := getDiskMatchesFromReader(xlFile) 271 272 if err != nil { 273 return nil, err 274 } 275 276 var disks []string 277 for _, match := range matches { 278 // match must have length of two! 279 if len(match) != 3 { 280 return nil, errors.New("Matches mismatch - please update code", nil) 281 } 282 disk := match[1] 283 volFile := match[2] 284 285 _, err := os.Stat(filepath.Join(sourcesDir, volFile)) 286 if err != nil { 287 if os.IsNotExist(err) { 288 continue 289 } else { 290 return nil, errors.New("unexpected error statting disk file", err) 291 } 292 } 293 disks = append(disks, disk) 294 } 295 296 // only return disks that have an image file with them... 297 298 return disks, nil 299 } 300 301 func introspectArguments(sourcesDir string) ([]string, error) { 302 303 output, err := unikutil.NewContainer("compilers-mirage-ocaml-xen").WithVolume(sourcesDir, "/opt/code").WithEntrypoint("/home/opam/.opam/system/bin/mirage").CombinedOutput("describe", "--color=never") 304 if err != nil { 305 log.WithError(err).WithFields(log.Fields{"output": string(output)}).Error("Error getting data on mirage code") 306 return nil, err 307 } 308 309 keys := getKeys(getKeyStringFromDescribe(string(output))) 310 311 var args []string 312 313 if _, ok := keys["kv_ro"]; ok { 314 args = append(args, "--kv_ro", "fat") 315 } 316 317 if _, ok := keys["network"]; ok { 318 args = append(args, "--network", "0") 319 args = append(args, "--net", "direct") 320 args = append(args, "--dhcp", "true") 321 } 322 323 // find kv_ro, net, network arguments 324 return args, nil 325 } 326 327 func getKeyStringFromDescribe(input string) string { 328 const keys = "Keys " 329 index := strings.Index(input, keys) 330 331 if index < 0 { 332 return "" 333 } 334 return input[index+len(keys):] 335 } 336 337 var pairs = regexp.MustCompile(`\s*(\S+?)=(.+?)(,|\s*$)`) 338 339 func getKeys(input string) map[string]string { 340 res := make(map[string]string) 341 matches := pairs.FindAllStringSubmatch(input, -1) 342 for _, match := range matches { 343 res[match[1]] = match[2] 344 } 345 return res 346 } 347 348 type mirageProjectConfig struct { 349 Args string `yaml:"arguments"` 350 } 351 352 func parseMirageManifest(sourcesDir string) ([]string, error) { 353 data, err := ioutil.ReadFile(filepath.Join(sourcesDir, "manifest.yaml")) 354 if err != nil { 355 return nil, errors.New("failed to read manifest.yaml file", err) 356 } 357 358 var config mirageProjectConfig 359 if err := yaml.Unmarshal(data, &config); err != nil { 360 return nil, errors.New("failed to parse yaml manifest.yaml file", err) 361 } 362 363 return strings.Split(config.Args, " "), nil 364 } 365 366 func grantOpamPermissions(sourcesDir string) error { 367 err := unikutil.NewContainer("compilers-mirage-ocaml-xen").WithVolume(sourcesDir, "/opt/code").WithEntrypoint("sudo").Run("chown", "-R", "opam", ".") 368 if err != nil { 369 log.WithError(err).Error("Error granting permissions to opam") 370 return err 371 } 372 return nil 373 }