github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/daemon/daemon.go (about) 1 package daemon 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "sort" 11 "strconv" 12 "strings" 13 "time" 14 15 "path/filepath" 16 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/emc-advanced-dev/pkg/errors" 19 "github.com/go-martini/martini" 20 "github.com/layer-x/layerx-commons/lxmartini" 21 "github.com/sirupsen/logrus" 22 "github.com/solo-io/unik/pkg/compilers" 23 firecrackercompiler "github.com/solo-io/unik/pkg/compilers/firecracker" 24 "github.com/solo-io/unik/pkg/compilers/includeos" 25 "github.com/solo-io/unik/pkg/compilers/mirage" 26 "github.com/solo-io/unik/pkg/compilers/osv" 27 "github.com/solo-io/unik/pkg/compilers/rump" 28 29 "github.com/solo-io/unik/pkg/config" 30 unikos "github.com/solo-io/unik/pkg/os" 31 "github.com/solo-io/unik/pkg/providers" 32 "github.com/solo-io/unik/pkg/providers/aws" 33 firecrackerprovider "github.com/solo-io/unik/pkg/providers/firecracker" 34 "github.com/solo-io/unik/pkg/providers/gcloud" 35 "github.com/solo-io/unik/pkg/providers/openstack" 36 "github.com/solo-io/unik/pkg/providers/photon" 37 "github.com/solo-io/unik/pkg/providers/qemu" 38 "github.com/solo-io/unik/pkg/providers/ukvm" 39 "github.com/solo-io/unik/pkg/providers/virtualbox" 40 "github.com/solo-io/unik/pkg/providers/vsphere" 41 "github.com/solo-io/unik/pkg/providers/xen" 42 43 "github.com/solo-io/unik/pkg/state" 44 "github.com/solo-io/unik/pkg/types" 45 "github.com/solo-io/unik/pkg/util" 46 ) 47 48 type UnikDaemon struct { 49 server *martini.ClassicMartini 50 providers providers.Providers `json:"providers"` 51 compilers map[compilers.CompilerType]compilers.Compiler 52 } 53 54 const ( 55 //available providers 56 aws_provider = "aws" 57 vsphere_provider = "vsphere" 58 virtualbox_provider = "virtualbox" 59 qemu_provider = "qemu" 60 photon_provider = "photon" 61 xen_provider = "xen" 62 ukvm_provider = "ukvm" 63 gcloud_provider = "gcloud" 64 openstack_provider = "openstack" 65 firecracker_provider = "firecracker" 66 ) 67 68 func NewUnikDaemon(config config.DaemonConfig) (*UnikDaemon, error) { 69 if err := util.InitContainers(); err != nil { 70 return nil, errors.New("initializing containers", err) 71 } 72 73 //set up tmpdrir 74 tmpDir := filepath.Join(os.Getenv("HOME"), ".unik", "tmp") 75 os.Setenv("TMPDIR", tmpDir) 76 os.MkdirAll(tmpDir, 0755) 77 78 _providers := make(providers.Providers) 79 _compilers := make(map[compilers.CompilerType]compilers.Compiler) 80 81 for _, awsConfig := range config.Providers.Aws { 82 logrus.Infof("Bootstrapping provider %s with config %v", aws_provider, awsConfig) 83 p := aws.NewAwsProvier(awsConfig) 84 s, err := state.BasicStateFromFile(aws.AwsStateFile()) 85 if err != nil { 86 logrus.WithError(err).Warnf("failed to read aws state file at %s, creating blank aws state", aws.AwsStateFile()) 87 s = state.NewBasicState(aws.AwsStateFile()) 88 } 89 p = p.WithState(s) 90 _providers[aws_provider] = p 91 break 92 } 93 for _, vsphereConfig := range config.Providers.Vsphere { 94 logrus.Infof("Bootstrapping provider %s with config %v", vsphere_provider, vsphereConfig) 95 p, err := vsphere.NewVsphereProvier(vsphereConfig) 96 if err != nil { 97 return nil, errors.New("initializing vsphere provider", err) 98 } 99 s, err := state.BasicStateFromFile(vsphere.VsphereStateFile()) 100 if err != nil { 101 logrus.WithError(err).Warnf("failed to read vsphere state file at %s, creating blank vsphere state", vsphere.VsphereStateFile()) 102 s = state.NewBasicState(vsphere.VsphereStateFile()) 103 } 104 p = p.WithState(s) 105 _providers[vsphere_provider] = p 106 break 107 } 108 for _, virtualboxConfig := range config.Providers.Virtualbox { 109 logrus.Infof("Bootstrapping provider %s with config %v", virtualbox_provider, virtualboxConfig) 110 p, err := virtualbox.NewVirtualboxProvider(virtualboxConfig) 111 if err != nil { 112 return nil, errors.New("initializing virtualbox provider", err) 113 } 114 s, err := state.BasicStateFromFile(virtualbox.VirtualboxStateFile()) 115 if err != nil { 116 logrus.WithError(err).Warnf("failed to read virtualbox state file at %s, creating blank virtualbox state", virtualbox.VirtualboxStateFile()) 117 s = state.NewBasicState(virtualbox.VirtualboxStateFile()) 118 } 119 p = p.WithState(s) 120 _providers[virtualbox_provider] = p 121 break 122 } 123 124 for _, qemuConfig := range config.Providers.Qemu { 125 logrus.Infof("Bootstrapping provider %s with config %v", qemu_provider, qemuConfig) 126 p, err := qemu.NewQemuProvider(qemuConfig) 127 if err != nil { 128 return nil, errors.New("initializing qemu provider", err) 129 } 130 s, err := state.BasicStateFromFile(qemu.QemuStateFile()) 131 if err != nil { 132 logrus.WithError(err).Warnf("failed to read qemu state file at %s, creating blank qemu state", qemu.QemuStateFile()) 133 s = state.NewBasicState(qemu.QemuStateFile()) 134 } 135 p = p.WithState(s) 136 _providers[qemu_provider] = p 137 break 138 } 139 140 for _, photonConfig := range config.Providers.Photon { 141 logrus.Infof("Bootstrapping provider %s with config %v", photon_provider, photonConfig) 142 p, err := photon.NewPhotonProvider(photonConfig) 143 if err != nil { 144 return nil, errors.New("initializing photon provider", err) 145 } 146 s, err := state.BasicStateFromFile(photon.PhotonStateFile()) 147 if err != nil { 148 logrus.WithError(err).Warnf("failed to read photon state file at %s, creating blank photon state", photon.PhotonStateFile()) 149 s = state.NewBasicState(photon.PhotonStateFile()) 150 } 151 p = p.WithState(s) 152 _providers[photon_provider] = p 153 break 154 } 155 156 for _, openstackConfig := range config.Providers.Openstack { 157 openstack.MergeConfWithEnv(&openstackConfig) 158 159 // Mask password prior logging to console. 160 orig_pass := openstackConfig.Password 161 openstackConfig.Password = "<password>" 162 logrus.Infof("Bootstrapping provider %s with config %v", openstack_provider, openstackConfig) 163 openstackConfig.Password = orig_pass 164 165 p, err := openstack.NewOpenstackProvider(openstackConfig) 166 if err != nil { 167 return nil, errors.New("initializing openstack provider", err) 168 } 169 s, err := state.BasicStateFromFile(openstack.OpenstackStateFile()) 170 if err != nil { 171 logrus.WithError(err).Warnf("failed to read openstack state file at %s, creating blank openstack state", openstack.OpenstackStateFile()) 172 s = state.NewBasicState(openstack.OpenstackStateFile()) 173 } 174 p = p.WithState(s) 175 _providers[openstack_provider] = p 176 break 177 } 178 179 for _, xenConfig := range config.Providers.Xen { 180 logrus.Infof("Bootstrapping provider %s with config %v", xen_provider, xenConfig) 181 p, err := xen.NewXenProvider(xenConfig) 182 if err != nil { 183 return nil, errors.New("initializing xen provider", err) 184 } 185 s, err := state.BasicStateFromFile(xen.XenStateFile()) 186 if err != nil { 187 logrus.WithError(err).Warnf("failed to read xen state file at %s, creating blank state", xen.XenStateFile()) 188 s = state.NewBasicState(xen.XenStateFile()) 189 } 190 p = p.WithState(s) 191 _providers[xen_provider] = p 192 break 193 } 194 195 for _, ukvmConfig := range config.Providers.Ukvm { 196 logrus.Infof("Bootstrapping provider %s with config %v", ukvm_provider, ukvmConfig) 197 p, err := ukvm.NewUkvmProvider(ukvmConfig) 198 if err != nil { 199 return nil, errors.New("initializing ukvm provider", err) 200 } 201 s, err := state.BasicStateFromFile(ukvm.UkvmStateFile()) 202 if err != nil { 203 logrus.WithError(err).Warnf("failed to read ukvm state file at %s, creating blank state", ukvm.UkvmStateFile()) 204 s = state.NewBasicState(ukvm.UkvmStateFile()) 205 } 206 p = p.WithState(s) 207 _providers[ukvm_provider] = p 208 break 209 } 210 211 for _, gcloudConfig := range config.Providers.Gcloud { 212 logrus.Infof("Bootstrapping provider %s with config %v", gcloud_provider, gcloudConfig) 213 p, err := gcloud.NewGcloudProvier(gcloudConfig) 214 if err != nil { 215 return nil, errors.New("initializing gcloud provider", err) 216 } 217 s, err := state.BasicStateFromFile(gcloud.GcloudStateFile()) 218 if err != nil { 219 logrus.WithError(err).Warnf("failed to read gcloud state file at %s, creating blank state", gcloud.GcloudStateFile()) 220 s = state.NewBasicState(gcloud.GcloudStateFile()) 221 } 222 p = p.WithState(s) 223 _providers[gcloud_provider] = p 224 break 225 } 226 227 for _, firecrackerConfig := range config.Providers.Firecracker { 228 logrus.Infof("Bootstrapping provider %s with config %v", firecracker_provider, firecrackerConfig) 229 p, err := firecrackerprovider.NewProvider(firecrackerConfig) 230 if err != nil { 231 return nil, errors.New("initializing firecracker provider", err) 232 } 233 s, err := state.BasicStateFromFile(firecrackerprovider.FirecrackerStateFile()) 234 if err != nil { 235 logrus.WithError(err).Warnf("failed to read firecracker state file at %s, creating blank state", firecrackerprovider.FirecrackerStateFile()) 236 s = state.NewBasicState(firecrackerprovider.FirecrackerStateFile()) 237 } 238 p = p.WithState(s) 239 _providers[firecracker_provider] = p 240 break 241 } 242 243 //rump-go 244 _compilers[compilers.RUMP_GO_PHOTON] = &rump.RumpGoCompiler{ 245 RumCompilerBase: rump.RumCompilerBase{ 246 DockerImage: "compilers-rump-go-hw", 247 CreateImage: rump.CreateImageVmware, 248 }, 249 BootstrapType: rump.BootstrapTypeUDP, 250 } 251 rumpGoXenCompiler := &rump.RumpGoCompiler{ 252 RumCompilerBase: rump.RumCompilerBase{ 253 DockerImage: "compilers-rump-go-xen", 254 CreateImage: rump.CreateImageXen, 255 }, 256 BootstrapType: rump.BootstrapTypeUDP, 257 } 258 _compilers[compilers.RUMP_GO_XEN] = rumpGoXenCompiler 259 _compilers[compilers.RUMP_GO_AWS] = rumpGoXenCompiler 260 _compilers[compilers.RUMP_GO_VSPHERE] = &rump.RumpGoCompiler{ 261 RumCompilerBase: rump.RumCompilerBase{ 262 263 DockerImage: "compilers-rump-go-hw", 264 CreateImage: rump.CreateImageVmware, 265 }, 266 BootstrapType: rump.BootstrapTypeUDP, 267 } 268 _compilers[compilers.RUMP_GO_VIRTUALBOX] = &rump.RumpGoCompiler{ 269 RumCompilerBase: rump.RumCompilerBase{ 270 271 DockerImage: "compilers-rump-go-hw", 272 CreateImage: rump.CreateImageVirtualBox, 273 }, 274 BootstrapType: rump.BootstrapTypeUDP, 275 } 276 _compilers[compilers.RUMP_GO_QEMU] = &rump.RumpGoCompiler{ 277 RumCompilerBase: rump.RumCompilerBase{ 278 DockerImage: "compilers-rump-go-hw", 279 CreateImage: rump.CreateImageQemu, 280 }, 281 BootstrapType: rump.BootstrapTypeNoStub, 282 } 283 _compilers[compilers.RUMP_GO_OPENSTACK] = &rump.RumpGoCompiler{ 284 RumCompilerBase: rump.RumCompilerBase{ 285 DockerImage: "compilers-rump-go-hw", 286 CreateImage: rump.CreateImageQemu, 287 }, 288 BootstrapType: rump.BootstrapTypeNoStub, 289 } 290 _compilers[compilers.RUMP_GO_GCLOUD] = &rump.RumpGoCompiler{ 291 RumCompilerBase: rump.RumCompilerBase{ 292 DockerImage: "compilers-rump-go-hw", 293 CreateImage: rump.CreateImageGCloud, 294 }, 295 BootstrapType: rump.BootstrapTypeGCLOUD, 296 } 297 298 //includeos-cpp 299 _compilers[compilers.INCLUDEOS_CPP_QEMU] = &includeos.IncludeosQemuCompiler{} 300 _compilers[compilers.INCLUDEOS_CPP_VIRTUALBOX] = &includeos.IncludeosVirtualboxCompiler{} 301 _compilers[compilers.INCLUDEOS_CPP_OPENSTACK] = &includeos.IncludeosQemuCompiler{} 302 303 //rump nodejs 304 _compilers[compilers.RUMP_NODEJS_XEN] = &rump.RumpScriptCompiler{ 305 RumCompilerBase: rump.RumCompilerBase{ 306 DockerImage: "compilers-rump-nodejs-xen", 307 CreateImage: rump.CreateImageXen, 308 }, 309 BootstrapType: rump.BootstrapTypeUDP, 310 RunScriptArgs: "/bootpart/node-wrapper.js", 311 } 312 _compilers[compilers.RUMP_NODEJS_AWS] = &rump.RumpScriptCompiler{ 313 RumCompilerBase: rump.RumCompilerBase{ 314 DockerImage: "compilers-rump-nodejs-xen", 315 CreateImage: rump.CreateImageXen, 316 }, 317 BootstrapType: rump.BootstrapTypeEC2, 318 RunScriptArgs: "/bootpart/node-wrapper.js", 319 } 320 _compilers[compilers.RUMP_NODEJS_VIRTUALBOX] = &rump.RumpScriptCompiler{ 321 RumCompilerBase: rump.RumCompilerBase{ 322 DockerImage: "compilers-rump-nodejs-hw", 323 CreateImage: rump.CreateImageVirtualBox, 324 }, 325 BootstrapType: rump.BootstrapTypeUDP, 326 RunScriptArgs: "/bootpart/node-wrapper.js", 327 } 328 _compilers[compilers.RUMP_NODEJS_VSPHERE] = &rump.RumpScriptCompiler{ 329 RumCompilerBase: rump.RumCompilerBase{ 330 DockerImage: "compilers-rump-nodejs-hw", 331 CreateImage: rump.CreateImageVmware, 332 }, 333 BootstrapType: rump.BootstrapTypeUDP, 334 RunScriptArgs: "/bootpart/node-wrapper.js", 335 } 336 _compilers[compilers.RUMP_NODEJS_QEMU] = &rump.RumpScriptCompiler{ 337 RumCompilerBase: rump.RumCompilerBase{ 338 DockerImage: "compilers-rump-nodejs-hw-no-stub", 339 CreateImage: rump.CreateImageQemu, 340 }, 341 RunScriptArgs: "/bootpart/node-wrapper.js", 342 } 343 _compilers[compilers.RUMP_NODEJS_OPENSTACK] = &rump.RumpScriptCompiler{ 344 RumCompilerBase: rump.RumCompilerBase{ 345 DockerImage: "compilers-rump-nodejs-hw-no-stub", 346 CreateImage: rump.CreateImageQemu, 347 }, 348 RunScriptArgs: "/bootpart/node-wrapper.js", 349 } 350 351 //mirage ocaml 352 _compilers[compilers.MIRAGE_OCAML_XEN] = &mirage.MirageCompiler{Type: mirage.XenType} 353 _compilers[compilers.MIRAGE_OCAML_UKVM] = &mirage.MirageCompiler{Type: mirage.UKVMType} 354 // _compilers[compilers.MIRAGE_OCAML_QEMU] = &mirage.MirageCompiler{Type: mirage.VirtioType} 355 356 //rump python 357 _compilers[compilers.RUMP_PYTHON_XEN] = rump.NewRumpPythonCompiler("compilers-rump-python3-xen", rump.CreateImageXenAddStub, rump.BootstrapTypeUDP) 358 _compilers[compilers.RUMP_PYTHON_AWS] = rump.NewRumpPythonCompiler("compilers-rump-python3-xen", rump.CreateImageXenAddStub, rump.BootstrapTypeEC2) 359 _compilers[compilers.RUMP_PYTHON_VIRTUALBOX] = rump.NewRumpPythonCompiler("compilers-rump-python3-hw", rump.CreateImageVirtualBoxAddStub, rump.BootstrapTypeUDP) 360 _compilers[compilers.RUMP_PYTHON_VSPHERE] = rump.NewRumpPythonCompiler("compilers-rump-python3-hw", rump.CreateImageVmwareAddStub, rump.BootstrapTypeUDP) 361 _compilers[compilers.RUMP_PYTHON_QEMU] = rump.NewRumpPythonCompiler("compilers-rump-python3-hw-no-stub", rump.CreateImageQemu, rump.BootstrapTypeNoStub) 362 _compilers[compilers.RUMP_PYTHON_OPENSTACK] = rump.NewRumpPythonCompiler("compilers-rump-python3-hw-no-stub", rump.CreateImageQemu, rump.BootstrapTypeNoStub) 363 364 //rump java 365 _compilers[compilers.RUMP_JAVA_XEN] = rump.NewRumpJavaCompiler("compilers-rump-java-xen", rump.CreateImageXen, rump.BootstrapTypeUDP) 366 _compilers[compilers.RUMP_JAVA_AWS] = rump.NewRumpJavaCompiler("compilers-rump-java-xen", rump.CreateImageXen, rump.BootstrapTypeEC2) 367 _compilers[compilers.RUMP_JAVA_VIRTUALBOX] = rump.NewRumpJavaCompiler("compilers-rump-java-hw", rump.CreateImageVirtualBox, rump.BootstrapTypeUDP) 368 _compilers[compilers.RUMP_JAVA_VSPHERE] = rump.NewRumpJavaCompiler("compilers-rump-java-hw", rump.CreateImageVmware, rump.BootstrapTypeUDP) 369 _compilers[compilers.RUMP_JAVA_QEMU] = rump.NewRumpJavaCompiler("compilers-rump-java-hw", rump.CreateImageQemu, rump.BootstrapTypeNoStub) 370 _compilers[compilers.RUMP_JAVA_OPENSTACK] = rump.NewRumpJavaCompiler("compilers-rump-java-hw", rump.CreateImageQemu, rump.BootstrapTypeNoStub) 371 372 //rump c 373 _compilers[compilers.RUMP_C_XEN] = rump.NewRumpCCompiler("compilers-rump-c-xen", rump.CreateImageXenAddStub) 374 _compilers[compilers.RUMP_C_AWS] = rump.NewRumpCCompiler("compilers-rump-c-xen", rump.CreateImageXenAddStub) 375 _compilers[compilers.RUMP_C_VIRTUALBOX] = rump.NewRumpCCompiler("compilers-rump-c-hw", rump.CreateImageVirtualBoxAddStub) 376 _compilers[compilers.RUMP_C_VSPHERE] = rump.NewRumpCCompiler("compilers-rump-c-hw", rump.CreateImageVmwareAddStub) 377 _compilers[compilers.RUMP_C_QEMU] = rump.NewRumpCCompiler("compilers-rump-c-hw", rump.CreateImageQemu) 378 _compilers[compilers.RUMP_C_OPENSTACK] = rump.NewRumpCCompiler("compilers-rump-c-hw", rump.CreateImageQemu) 379 380 //osv java 381 osvJavaXenCompiler := &osv.OSvJavaCompiler{ 382 ImageFinisher: &osv.AwsImageFinisher{}, 383 } 384 _compilers[compilers.OSV_JAVA_XEN] = osvJavaXenCompiler 385 _compilers[compilers.OSV_JAVA_AWS] = osvJavaXenCompiler 386 _compilers[compilers.OSV_JAVA_VIRTUALBOX] = &osv.OSvJavaCompiler{ 387 ImageFinisher: &osv.VirtualboxImageFinisher{}, 388 } 389 _compilers[compilers.OSV_JAVA_VSPHERE] = &osv.OSvJavaCompiler{ 390 ImageFinisher: &osv.VmwareImageFinisher{}, 391 } 392 // At the moment OpenStack provider borrows Xen's compiler. 393 _compilers[compilers.OSV_JAVA_OPENSTACK] = osvJavaXenCompiler 394 395 // osv nodejs 396 osvNodeQemuCompiler := &osv.OSvNodeCompiler{ 397 ImageFinisher: &osv.QemuImageFinisher{}, 398 } 399 _compilers[compilers.OSV_NODEJS_QEMU] = osvNodeQemuCompiler 400 _compilers[compilers.OSV_NODEJS_OPENSTACK] = osvNodeQemuCompiler 401 _compilers[compilers.OSV_NODEJS_AWS] = &osv.OSvNodeCompiler{ 402 ImageFinisher: &osv.AwsImageFinisher{}, 403 } 404 405 // osv native 406 osvNativeQemuCompiler := &osv.OSvNativeCompiler{ 407 ImageFinisher: &osv.QemuImageFinisher{}, 408 } 409 _compilers[compilers.OSV_NATIVE_QEMU] = osvNativeQemuCompiler 410 _compilers[compilers.OSV_NATIVE_OPENSTACK] = osvNativeQemuCompiler 411 _compilers[compilers.OSV_NATIVE_AWS] = &osv.OSvNativeCompiler{ 412 ImageFinisher: &osv.AwsImageFinisher{}, 413 } 414 _compilers[compilers.FIRECRACKER_GO] = &firecrackercompiler.FirecrackerCompiler{} 415 416 d := &UnikDaemon{ 417 server: lxmartini.QuietMartini(), 418 providers: _providers, 419 compilers: _compilers, 420 } 421 422 d.initialize() 423 424 return d, nil 425 } 426 427 func (d *UnikDaemon) Run(port int) { 428 d.server.RunOnAddr(fmt.Sprintf(":%v", port)) 429 } 430 431 func (d *UnikDaemon) Stop() error { 432 // return d.server.Stop() 433 return nil 434 } 435 436 func (d *UnikDaemon) initialize() { 437 handle := func(res http.ResponseWriter, action func() (interface{}, int, error)) { 438 jsonObject, statusCode, err := action() 439 res.WriteHeader(statusCode) 440 if err != nil { 441 if err := respond(res, err); err != nil { 442 logrus.WithError(err).Errorf("failed to reply to http request") 443 } 444 logrus.WithError(err).Errorf("error handling request") 445 return 446 } 447 if jsonObject != nil { 448 if err := respond(res, jsonObject); err != nil { 449 logrus.WithError(err).Errorf("failed to reply to http request") 450 } 451 logrus.WithField("result", jsonObject).Debugf("request finished") 452 } 453 } 454 455 //images 456 d.server.Get("/images", func(res http.ResponseWriter, req *http.Request) { 457 handle(res, func() (interface{}, int, error) { 458 allImages := []*types.Image{} 459 for _, provider := range d.providers { 460 images, err := provider.ListImages() 461 if err != nil { 462 return nil, http.StatusInternalServerError, errors.New("could not get image list", err) 463 } 464 allImages = append(allImages, images...) 465 } 466 logrus.WithFields(logrus.Fields{ 467 "images": allImages, 468 }).Debugf("Listing all images") 469 return allImages, http.StatusOK, nil 470 }) 471 }) 472 d.server.Get("/images/:image_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 473 handle(res, func() (interface{}, int, error) { 474 imageName := params["image_name"] 475 provider, err := d.providers.ProviderForImage(imageName) 476 if err != nil { 477 return nil, http.StatusInternalServerError, err 478 } 479 image, err := provider.GetImage(imageName) 480 if err != nil { 481 return nil, http.StatusInternalServerError, err 482 } 483 return image, http.StatusOK, nil 484 }) 485 }) 486 d.server.Post("/images/:name/create", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 487 handle(res, func() (interface{}, int, error) { 488 name := params["name"] 489 if name == "" { 490 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 491 } 492 err := req.ParseMultipartForm(0) 493 if err != nil { 494 return nil, http.StatusInternalServerError, err 495 } 496 logrus.WithFields(logrus.Fields{ 497 "req": req, 498 }).Debugf("parsing multipart form") 499 logrus.WithFields(logrus.Fields{ 500 "form": req.Form, 501 }).Debugf("parsing form file marked 'tarfile'") 502 sourceTar, _, err := req.FormFile("tarfile") 503 if err != nil { 504 return nil, http.StatusBadRequest, errors.New("parsing form file marked 'tarfile", err) 505 } 506 defer sourceTar.Close() 507 508 noCleanupStr := req.FormValue("no_cleanup") 509 var noCleanup bool 510 if strings.ToLower(noCleanupStr) == "true" { 511 noCleanup = true 512 } 513 514 sourcesDir, err := ioutil.TempDir("", "unpacked.sources.dir.") 515 if err != nil { 516 return nil, http.StatusInternalServerError, errors.New("creating tmp dir for src files", err) 517 } 518 519 if !noCleanup { 520 defer os.RemoveAll(sourcesDir) 521 } 522 523 logrus.Debugf("extracting uploaded files to " + sourcesDir) 524 if err := unikos.ExtractTar(sourceTar, sourcesDir); err != nil { 525 return nil, http.StatusInternalServerError, errors.New("extracting sources", err) 526 } 527 forceStr := req.FormValue("force") 528 var force bool 529 if strings.ToLower(forceStr) == "true" { 530 force = true 531 } 532 args := req.FormValue("args") 533 providerName := req.FormValue("provider") 534 if _, ok := d.providers[providerName]; !ok { 535 return nil, http.StatusBadRequest, errors.New(providerName+" is not a known provider. Available: "+strings.Join(d.providers.Keys(), "|"), nil) 536 } 537 538 base := req.FormValue("base") 539 if base == "" { 540 return nil, http.StatusBadRequest, errors.New("must provide 'base' parameter", nil) 541 } 542 lang := req.FormValue("lang") 543 if lang == "" { 544 return nil, http.StatusBadRequest, errors.New("must provide 'lang' parameter", nil) 545 } 546 compilerName, err := compilers.ValidateCompiler(base, lang, providerName) 547 if err != nil { 548 return nil, http.StatusBadRequest, errors.New("invalid base - lang - provider match", err) 549 } 550 551 compiler, ok := d.compilers[compilerName] 552 if !ok { 553 return nil, http.StatusBadRequest, errors.New("unikernel type "+compilerName.String()+" not available for "+providerName+"infrastructure", nil) 554 } 555 mntStr := req.FormValue("mounts") 556 557 var mountPoints []string 558 if len(mntStr) > 0 { 559 mountPoints = strings.Split(mntStr, ",") 560 } 561 562 logrus.WithFields(logrus.Fields{ 563 "force": force, 564 "mount-points": mountPoints, 565 "name": name, 566 "args": args, 567 "compiler": compilerName, 568 "provider": providerName, 569 "noCleanup": noCleanup, 570 }).Debugf("compiling raw image") 571 572 compileParams := types.CompileImageParams{ 573 SourcesDir: sourcesDir, 574 Args: args, 575 MntPoints: mountPoints, 576 NoCleanup: noCleanup, 577 } 578 579 rawImage, err := compiler.CompileRawImage(compileParams) 580 if err != nil { 581 return nil, http.StatusInternalServerError, errors.New("failed to compile raw image", err) 582 } 583 logrus.Debugf("raw image compiled and saved to " + rawImage.LocalImagePath) 584 585 if !noCleanup { 586 defer os.Remove(rawImage.LocalImagePath) 587 } 588 589 stageParams := types.StageImageParams{ 590 Name: name, 591 RawImage: rawImage, 592 Force: force, 593 NoCleanup: noCleanup, 594 } 595 596 image, err := d.providers[providerName].Stage(stageParams) 597 if err != nil { 598 return nil, http.StatusInternalServerError, errors.New("failed staging image", err) 599 } 600 return image, http.StatusCreated, nil 601 }) 602 }) 603 d.server.Delete("/images/:image_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 604 handle(res, func() (interface{}, int, error) { 605 imageName := params["image_name"] 606 if imageName == "" { 607 logrus.WithFields(logrus.Fields{ 608 "request": fmt.Sprintf("%v", req), 609 }).Errorf("image must be named") 610 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 611 } 612 logrus.WithFields(logrus.Fields{ 613 "request": req, 614 }).Infof("deleting image " + imageName) 615 forceStr := req.URL.Query().Get("force") 616 force := false 617 if strings.ToLower(forceStr) == "true" { 618 force = true 619 } 620 provider, err := d.providers.ProviderForImage(imageName) 621 if err != nil { 622 return nil, http.StatusInternalServerError, err 623 } 624 err = provider.DeleteImage(imageName, force) 625 if err != nil { 626 return nil, http.StatusInternalServerError, err 627 } 628 return nil, http.StatusNoContent, nil 629 }) 630 }) 631 d.server.Post("/images/push/:image_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 632 handle(res, func() (interface{}, int, error) { 633 imageName := params["image_name"] 634 if imageName == "" { 635 logrus.WithFields(logrus.Fields{ 636 "request": fmt.Sprintf("%v", req), 637 }).Errorf("image must be named") 638 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 639 } 640 var c config.HubConfig 641 body, err := ioutil.ReadAll(req.Body) 642 if err != nil { 643 return nil, http.StatusBadRequest, errors.New("could not read request body", err) 644 } 645 if err := json.Unmarshal(body, &c); err != nil { 646 return nil, http.StatusBadRequest, errors.New("failed to parse request json", err) 647 } 648 logrus.WithFields(logrus.Fields{ 649 "request": req, 650 }).Infof("pushing image " + imageName + " to " + c.URL) 651 provider, err := d.providers.ProviderForImage(imageName) 652 if err != nil { 653 return nil, http.StatusInternalServerError, err 654 } 655 err = provider.PushImage(types.PushImagePararms{ 656 ImageName: imageName, 657 Config: c, 658 }) 659 if err != nil { 660 return nil, http.StatusInternalServerError, err 661 } 662 return nil, http.StatusAccepted, nil 663 }) 664 }) 665 d.server.Post("/images/pull/:image_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 666 handle(res, func() (interface{}, int, error) { 667 imageName := params["image_name"] 668 if imageName == "" { 669 logrus.WithFields(logrus.Fields{ 670 "request": fmt.Sprintf("%v", req), 671 }).Errorf("image must be named") 672 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 673 } 674 var c config.HubConfig 675 body, err := ioutil.ReadAll(req.Body) 676 if err != nil { 677 return nil, http.StatusBadRequest, errors.New("could not read request body", err) 678 } 679 if err := json.Unmarshal(body, &c); err != nil { 680 return nil, http.StatusBadRequest, errors.New("failed to parse request json", err) 681 } 682 logrus.WithFields(logrus.Fields{ 683 "request": req, 684 }).Infof("pushing image " + imageName + " to " + c.URL) 685 providerName := req.URL.Query().Get("provider") 686 provider, ok := d.providers[providerName] 687 if !ok { 688 return nil, http.StatusBadRequest, errors.New(providerName+" is not a known provider. Available: "+strings.Join(d.providers.Keys(), "|"), nil) 689 } 690 forceStr := req.URL.Query().Get("force") 691 force := false 692 if strings.ToLower(forceStr) == "true" { 693 force = true 694 } 695 err = provider.PullImage(types.PullImagePararms{ 696 ImageName: imageName, 697 Config: c, 698 Force: force, 699 }) 700 if err != nil { 701 return nil, http.StatusInternalServerError, err 702 } 703 return nil, http.StatusAccepted, nil 704 }) 705 }) 706 d.server.Post("/images/remote-delete/:image_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 707 handle(res, func() (interface{}, int, error) { 708 imageName := params["image_name"] 709 if imageName == "" { 710 logrus.WithFields(logrus.Fields{ 711 "request": fmt.Sprintf("%v", req), 712 }).Errorf("image must be named") 713 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 714 } 715 var c config.HubConfig 716 body, err := ioutil.ReadAll(req.Body) 717 if err != nil { 718 return nil, http.StatusBadRequest, errors.New("could not read request body", err) 719 } 720 if err := json.Unmarshal(body, &c); err != nil { 721 return nil, http.StatusBadRequest, errors.New("failed to parse request json", err) 722 } 723 logrus.WithFields(logrus.Fields{ 724 "request": req, 725 }).Infof("deleting image " + imageName + " to " + c.URL) 726 provider, err := d.providers.ProviderForImage(imageName) 727 if err != nil { 728 return nil, http.StatusInternalServerError, err 729 } 730 err = provider.PushImage(types.PushImagePararms{ 731 ImageName: imageName, 732 Config: c, 733 }) 734 if err != nil { 735 return nil, http.StatusInternalServerError, err 736 } 737 return nil, http.StatusAccepted, nil 738 }) 739 }) 740 741 //Instances 742 d.server.Get("/instances", func(res http.ResponseWriter, req *http.Request) { 743 handle(res, func() (interface{}, int, error) { 744 allInstances := []*types.Instance{} 745 for _, provider := range d.providers { 746 instances, err := provider.ListInstances() 747 if err != nil { 748 return nil, http.StatusInternalServerError, errors.New("could not get instance list", err) 749 } 750 allInstances = append(allInstances, instances...) 751 } 752 logrus.WithFields(logrus.Fields{ 753 "instances": allInstances, 754 }).Debugf("Listing all instances") 755 return allInstances, http.StatusOK, nil 756 }) 757 }) 758 d.server.Get("/instances/:instance_id", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 759 handle(res, func() (interface{}, int, error) { 760 instanceId := params["instance_id"] 761 provider, err := d.providers.ProviderForInstance(instanceId) 762 if err != nil { 763 return nil, http.StatusInternalServerError, err 764 } 765 instance, err := provider.GetInstance(instanceId) 766 if err != nil { 767 return nil, http.StatusInternalServerError, err 768 } 769 return instance, http.StatusOK, nil 770 }) 771 }) 772 d.server.Delete("/instances/:instance_id", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 773 handle(res, func() (interface{}, int, error) { 774 instanceId := params["instance_id"] 775 logrus.WithFields(logrus.Fields{ 776 "request": req, 777 }).Infof("deleting instance " + instanceId) 778 provider, err := d.providers.ProviderForInstance(instanceId) 779 if err != nil { 780 return nil, http.StatusInternalServerError, err 781 } 782 forceStr := req.URL.Query().Get("force") 783 force := false 784 if strings.ToLower(forceStr) == "true" { 785 force = true 786 } 787 err = provider.DeleteInstance(instanceId, force) 788 if err != nil { 789 return nil, http.StatusInternalServerError, err 790 } 791 return nil, http.StatusNoContent, nil 792 }) 793 }) 794 d.server.Get("/instances/:instance_id/logs", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 795 handle(res, func() (interface{}, int, error) { 796 instanceId := params["instance_id"] 797 follow := req.URL.Query().Get("follow") 798 res.Write([]byte("getting logs for " + instanceId + "...\n")) 799 provider, err := d.providers.ProviderForInstance(instanceId) 800 if err != nil { 801 return nil, http.StatusInternalServerError, err 802 } 803 if strings.ToLower(follow) == "true" { 804 if f, ok := res.(http.Flusher); ok { 805 f.Flush() 806 } else { 807 return nil, http.StatusInternalServerError, errors.New("not a flusher", nil) 808 } 809 810 deleteOnDisconnect := req.URL.Query().Get("delete") 811 if strings.ToLower(deleteOnDisconnect) == "true" { 812 logrus.Warnf("INSTANCE %v WILL BE TERMINTED ON CLIENT DISCONNECT!", instanceId) 813 defer provider.DeleteInstance(instanceId, true) 814 } 815 816 output := ioutils.NewWriteFlusher(res) 817 logFn := func() (string, error) { 818 return provider.GetInstanceLogs(instanceId) 819 } 820 if err := streamOutput(logFn, output); err != nil { 821 logrus.WithError(err).WithFields(logrus.Fields{ 822 "instanceId": instanceId, 823 }).Warnf("streaming logs stopped") 824 return nil, http.StatusInternalServerError, err 825 } 826 return nil, 0, nil 827 } 828 logs, err := provider.GetInstanceLogs(instanceId) 829 if err != nil { 830 return nil, http.StatusInternalServerError, errors.New("failed to perform get logs request", err) 831 } 832 return logs, http.StatusOK, nil 833 }) 834 }) 835 d.server.Post("/instances/run", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 836 handle(res, func() (interface{}, int, error) { 837 body, err := ioutil.ReadAll(req.Body) 838 if err != nil { 839 return nil, http.StatusBadRequest, errors.New("could not read request body", err) 840 } 841 defer req.Body.Close() 842 var runInstanceRequest RunInstanceRequest 843 if err := json.Unmarshal(body, &runInstanceRequest); err != nil { 844 return nil, http.StatusBadRequest, errors.New("failed to parse request json", err) 845 } 846 847 logrus.WithFields(logrus.Fields{ 848 "request": runInstanceRequest, 849 }).Debugf("Received run request") 850 851 if runInstanceRequest.ImageName == "" { 852 return nil, http.StatusBadRequest, errors.New("image must be named", nil) 853 } 854 855 provider, err := d.providers.ProviderForImage(runInstanceRequest.ImageName) 856 if err != nil { 857 return nil, http.StatusBadRequest, err 858 } 859 860 params := types.RunInstanceParams{ 861 Name: runInstanceRequest.InstanceName, 862 ImageId: runInstanceRequest.ImageName, 863 MntPointsToVolumeIds: runInstanceRequest.Mounts, 864 Env: runInstanceRequest.Env, 865 InstanceMemory: runInstanceRequest.MemoryMb, 866 NoCleanup: runInstanceRequest.NoCleanup, 867 DebugMode: runInstanceRequest.DebugMode, 868 } 869 870 instance, err := provider.RunInstance(params) 871 if err != nil { 872 return nil, http.StatusInternalServerError, err 873 } 874 return instance, http.StatusCreated, nil 875 }) 876 }) 877 d.server.Post("/instances/:instance_id/start", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 878 handle(res, func() (interface{}, int, error) { 879 instanceId := params["instance_id"] 880 logrus.WithFields(logrus.Fields{ 881 "request": req, 882 }).Infof("starting instance " + instanceId) 883 provider, err := d.providers.ProviderForInstance(instanceId) 884 if err != nil { 885 return nil, http.StatusInternalServerError, err 886 } 887 err = provider.StartInstance(instanceId) 888 if err != nil { 889 return nil, http.StatusInternalServerError, errors.New("could not start instance "+instanceId, err) 890 } 891 return nil, http.StatusOK, nil 892 }) 893 }) 894 d.server.Post("/instances/:instance_id/stop", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 895 handle(res, func() (interface{}, int, error) { 896 instanceId := params["instance_id"] 897 logrus.WithFields(logrus.Fields{ 898 "request": req, 899 }).Infof("stopping instance " + instanceId) 900 provider, err := d.providers.ProviderForInstance(instanceId) 901 if err != nil { 902 return nil, http.StatusInternalServerError, err 903 } 904 err = provider.StopInstance(instanceId) 905 if err != nil { 906 return nil, http.StatusInternalServerError, errors.New("could not stop instance "+instanceId, err) 907 } 908 return nil, http.StatusOK, nil 909 }) 910 }) 911 912 //Volumes 913 d.server.Get("/volumes", func(res http.ResponseWriter, req *http.Request) { 914 handle(res, func() (interface{}, int, error) { 915 logrus.Debugf("listing volumes started") 916 allVolumes := []*types.Volume{} 917 for _, provider := range d.providers { 918 volumes, err := provider.ListVolumes() 919 if err != nil { 920 return nil, http.StatusInternalServerError, errors.New("could not retrieve volumes", err) 921 } 922 allVolumes = append(allVolumes, volumes...) 923 } 924 logrus.WithFields(logrus.Fields{ 925 "volumes": allVolumes, 926 }).Infof("volumes") 927 return allVolumes, http.StatusOK, nil 928 }) 929 }) 930 d.server.Get("/volumes/:volume_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 931 handle(res, func() (interface{}, int, error) { 932 volumeName := params["volume_name"] 933 provider, err := d.providers.ProviderForVolume(volumeName) 934 if err != nil { 935 return nil, http.StatusInternalServerError, err 936 } 937 volume, err := provider.GetVolume(volumeName) 938 if err != nil { 939 return nil, http.StatusInternalServerError, errors.New("could not get volume", err) 940 } 941 logrus.WithFields(logrus.Fields{ 942 "volume": volume, 943 }).Infof("volume retrieved") 944 return volume, http.StatusOK, nil 945 }) 946 }) 947 d.server.Post("/volumes/:volume_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 948 handle(res, func() (interface{}, int, error) { 949 volumeName := params["volume_name"] 950 var imagePath string 951 var provider providers.Provider 952 var noCleanup bool 953 var raw bool 954 955 logrus.WithField("req", req).Info("received request to create volume") 956 957 typeStr := req.FormValue("type") 958 typeStr = strings.ToLower(typeStr) 959 960 if strings.Contains(req.Header.Get("Content-type"), "multipart/form-data") { 961 962 if strings.ToLower(req.FormValue("raw")) == "true" { 963 raw = true 964 } 965 if strings.ToLower(req.FormValue("no_cleanup")) == "true" { 966 noCleanup = true 967 } 968 969 logrus.Info("received request with form-data") 970 err := req.ParseMultipartForm(0) 971 if err != nil { 972 return nil, http.StatusInternalServerError, err 973 } 974 logrus.WithFields(logrus.Fields{ 975 "req": req, 976 }).Debugf("parsing multipart form") 977 978 providerName := req.FormValue("provider") 979 if _, ok := d.providers[providerName]; !ok { 980 return nil, http.StatusBadRequest, errors.New(providerName+" is not a known provider. Available: "+strings.Join(d.providers.Keys(), "|"), nil) 981 } 982 provider = d.providers[providerName] 983 dataTar, header, err := req.FormFile("tarfile") 984 if err != nil { 985 return nil, http.StatusInternalServerError, errors.New("failed to retrieve form-data for tarfe", err) 986 } 987 defer dataTar.Close() 988 989 if !raw { 990 logrus.WithFields(logrus.Fields{ 991 "form": req.Form, 992 }).Debugf("seeking form file marked 'tarfile'") 993 logrus.WithFields(logrus.Fields{ 994 "tarred-data": header.Filename, 995 "name": volumeName, 996 "provider": providerName, 997 }).Debugf("creating volume started") 998 999 sizeStr := req.URL.Query().Get("size") 1000 if sizeStr == "" { 1001 sizeStr = "0" 1002 } 1003 size, err := strconv.Atoi(sizeStr) 1004 if err != nil { 1005 return nil, http.StatusBadRequest, errors.New("could not parse given size", err) 1006 } 1007 imagePath, err = util.BuildRawDataImageWithType(dataTar, unikos.MegaBytes(size), typeStr, provider.GetConfig().UsePartitionTables) 1008 if err != nil { 1009 return nil, http.StatusInternalServerError, errors.New("creating raw volume image", err) 1010 } 1011 } else { 1012 imagePathFile, err := ioutil.TempFile("", "") 1013 if err != nil { 1014 return nil, http.StatusInternalServerError, errors.New("creating temp file for volume image", err) 1015 } 1016 _, err = io.Copy(imagePathFile, dataTar) 1017 imagePathFile.Close() 1018 if err != nil { 1019 return nil, http.StatusInternalServerError, errors.New("creating temp file for volume image", err) 1020 } 1021 imagePath = imagePathFile.Name() 1022 } 1023 1024 } else { 1025 if strings.ToLower(req.URL.Query().Get("raw")) == "true" { 1026 raw = true 1027 } 1028 if strings.ToLower(req.URL.Query().Get("no_cleanup")) == "true" { 1029 noCleanup = true 1030 } 1031 1032 if raw == true { 1033 return nil, http.StatusBadRequest, errors.New("Raw volume was requested but no data provided", nil) 1034 } 1035 logrus.Info("received request for empty volume") 1036 sizeStr := req.URL.Query().Get("size") 1037 size, err := strconv.Atoi(sizeStr) 1038 if err != nil { 1039 return nil, http.StatusBadRequest, errors.New("could not parse given size", err) 1040 } 1041 logrus.WithFields(logrus.Fields{ 1042 "size": size, 1043 "name": volumeName, 1044 }).Debugf("creating empty volume started") 1045 imagePath, err = util.BuildEmptyDataVolumeWithType(unikos.MegaBytes(size), typeStr) 1046 if err != nil { 1047 return nil, http.StatusInternalServerError, errors.New("failed building raw image", err) 1048 } 1049 providerName := req.URL.Query().Get("provider") 1050 if _, ok := d.providers[providerName]; !ok { 1051 return nil, http.StatusBadRequest, errors.New(providerName+" is not a known provider. Available: "+strings.Join(d.providers.Keys(), "|"), nil) 1052 } 1053 provider = d.providers[providerName] 1054 logrus.WithFields(logrus.Fields{ 1055 "image": imagePath, 1056 }).Infof("raw image created") 1057 1058 } 1059 1060 if !noCleanup { 1061 defer os.RemoveAll(imagePath) 1062 } 1063 1064 params := types.CreateVolumeParams{ 1065 Name: volumeName, 1066 ImagePath: imagePath, 1067 NoCleanup: noCleanup, 1068 } 1069 1070 volume, err := provider.CreateVolume(params) 1071 if err != nil { 1072 return nil, http.StatusInternalServerError, errors.New("could not create volume", err) 1073 } 1074 logrus.WithFields(logrus.Fields{ 1075 "volume": volume, 1076 }).Infof("volume created") 1077 return volume, http.StatusCreated, nil 1078 }) 1079 }) 1080 d.server.Delete("/volumes/:volume_name", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 1081 handle(res, func() (interface{}, int, error) { 1082 volumeName := params["volume_name"] 1083 provider, err := d.providers.ProviderForVolume(volumeName) 1084 if err != nil { 1085 return nil, http.StatusInternalServerError, err 1086 } 1087 forceStr := req.URL.Query().Get("force") 1088 force := false 1089 if strings.ToLower(forceStr) == "true" { 1090 force = true 1091 } 1092 1093 logrus.WithFields(logrus.Fields{ 1094 "force": force, "name": volumeName, 1095 }).Debugf("deleting volume started") 1096 err = provider.DeleteVolume(volumeName, force) 1097 if err != nil { 1098 return nil, http.StatusInternalServerError, errors.New("could not delete volume", err) 1099 } 1100 logrus.WithFields(logrus.Fields{ 1101 "volume": volumeName, 1102 }).Infof("volume deleted") 1103 return nil, http.StatusNoContent, nil 1104 }) 1105 }) 1106 d.server.Post("/volumes/:volume_name/attach/:instance_id", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 1107 handle(res, func() (interface{}, int, error) { 1108 volumeName := params["volume_name"] 1109 provider, err := d.providers.ProviderForVolume(volumeName) 1110 if err != nil { 1111 return nil, http.StatusInternalServerError, err 1112 } 1113 instanceId := params["instance_id"] 1114 mount := req.URL.Query().Get("mount") 1115 if mount == "" { 1116 return nil, http.StatusBadRequest, errors.New("must provide a mount point in URL query", nil) 1117 } 1118 logrus.WithFields(logrus.Fields{ 1119 "instance": instanceId, 1120 "volume": volumeName, 1121 "mount": mount, 1122 }).Debugf("attaching volume to instance") 1123 err = provider.AttachVolume(volumeName, instanceId, mount) 1124 if err != nil { 1125 return nil, http.StatusInternalServerError, errors.New("could not attach volume to instance", err) 1126 } 1127 logrus.WithFields(logrus.Fields{ 1128 "instance": instanceId, 1129 "volume": volumeName, 1130 "mount": mount, 1131 }).Infof("volume attached") 1132 return volumeName, http.StatusAccepted, nil 1133 }) 1134 }) 1135 d.server.Post("/volumes/:volume_name/detach", func(res http.ResponseWriter, req *http.Request, params martini.Params) { 1136 handle(res, func() (interface{}, int, error) { 1137 volumeName := params["volume_name"] 1138 provider, err := d.providers.ProviderForVolume(volumeName) 1139 if err != nil { 1140 return nil, http.StatusInternalServerError, err 1141 } 1142 logrus.WithFields(logrus.Fields{ 1143 "volume": volumeName, 1144 }).Debugf("detaching volume from any instance") 1145 err = provider.DetachVolume(volumeName) 1146 if err != nil { 1147 return nil, http.StatusInternalServerError, errors.New("could not detach volume from instance", err) 1148 } 1149 logrus.WithFields(logrus.Fields{ 1150 "volume": volumeName, 1151 }).Infof("volume detached") 1152 return volumeName, http.StatusAccepted, nil 1153 }) 1154 }) 1155 1156 //info 1157 d.server.Get("/available_compilers", func(res http.ResponseWriter, req *http.Request) { 1158 handle(res, func() (interface{}, int, error) { 1159 logrus.Debugf("listing available compilers") 1160 availableCompilers := sort.StringSlice{} 1161 for compilerName := range d.compilers { 1162 availableCompilers = append(availableCompilers, compilerName.String()) 1163 } 1164 availableCompilers.Sort() 1165 logrus.WithFields(logrus.Fields{ 1166 "compilers": availableCompilers, 1167 }).Infof("compilers") 1168 return []string(availableCompilers), http.StatusOK, nil 1169 }) 1170 }) 1171 d.server.Get("/available_providers", func(res http.ResponseWriter, req *http.Request) { 1172 handle(res, func() (interface{}, int, error) { 1173 logrus.Debugf("listing available providers") 1174 availableProviders := sort.StringSlice{} 1175 for compilerName := range d.providers { 1176 availableProviders = append(availableProviders, compilerName) 1177 } 1178 availableProviders.Sort() 1179 logrus.WithFields(logrus.Fields{ 1180 "providers": availableProviders, 1181 }).Infof("providers") 1182 return []string(availableProviders), http.StatusOK, nil 1183 }) 1184 }) 1185 d.server.Get("/describe_compiler", func(res http.ResponseWriter, req *http.Request) { 1186 handle(res, func() (interface{}, int, error) { 1187 logrus.Debugf("describing compiler") 1188 1189 // Find compiler. 1190 provider := req.FormValue("provider") 1191 if _, ok := d.providers[provider]; !ok { 1192 return nil, http.StatusBadRequest, errors.New(provider+" is not a known provider. Available: "+ 1193 strings.Join(d.providers.Keys(), "|"), nil) 1194 } 1195 base := req.FormValue("base") 1196 if base == "" { 1197 return nil, http.StatusBadRequest, errors.New("must provide 'base' parameter", nil) 1198 } 1199 lang := req.FormValue("lang") 1200 if lang == "" { 1201 return nil, http.StatusBadRequest, errors.New("must provide 'lang' parameter", nil) 1202 } 1203 compilerName, err := compilers.ValidateCompiler(base, lang, provider) 1204 if err != nil { 1205 return nil, http.StatusBadRequest, errors.New("invalid base - lang - provider match", err) 1206 } 1207 compiler, ok := d.compilers[compilerName] 1208 if !ok { 1209 return nil, http.StatusBadRequest, errors.New("failed to access compiler", nil) 1210 } 1211 1212 logrus.WithFields(logrus.Fields{ 1213 "compiler": compiler, 1214 }).Infof("describe compiler") 1215 1216 // Let compiler describe itself. 1217 compilerUsage := compiler.Usage() 1218 description := "<missing compiler description>" 1219 if compilerUsage != nil { 1220 description = compilerUsage.ToString() 1221 } 1222 1223 return description, http.StatusOK, nil 1224 }) 1225 }) 1226 } 1227 1228 func streamOutput(outputFunc func() (string, error), w io.Writer) (err error) { 1229 defer func() { 1230 if err != nil { 1231 w.Write([]byte(err.Error())) 1232 } 1233 }() 1234 1235 //give instance some time to start logs server 1236 if err := util.Retry(30, time.Millisecond*500, func() error { 1237 _, err := outputFunc() 1238 return err 1239 }); err != nil { 1240 return err 1241 } 1242 linesCounted := -1 1243 for { 1244 time.Sleep(100 * time.Millisecond) 1245 output, err := outputFunc() 1246 if err != nil { 1247 return errors.New("could not read output", err) 1248 } 1249 logLines := strings.Split(output, "\n") 1250 for i, _ := range logLines { 1251 if linesCounted < len(logLines) && linesCounted < i { 1252 linesCounted = i 1253 1254 if f, ok := w.(http.Flusher); ok { 1255 f.Flush() 1256 } else { 1257 return errors.New("w is not a flusher", nil) 1258 } 1259 1260 _, err = w.Write([]byte(logLines[linesCounted] + "\n")) 1261 if err != nil { 1262 return errors.New("writing to connection", err) 1263 } 1264 } 1265 } 1266 _, err = w.Write([]byte{0}) //ignore errors; close comes from external 1267 if err != nil { 1268 return nil 1269 } 1270 if len(logLines)-1 == linesCounted { 1271 time.Sleep(2500 * time.Millisecond) 1272 continue 1273 } 1274 } 1275 } 1276 1277 func respond(res http.ResponseWriter, message interface{}) error { 1278 switch message.(type) { 1279 case string: 1280 messageString := message.(string) 1281 data := []byte(messageString) 1282 _, err := res.Write(data) 1283 if err != nil { 1284 return errors.New("writing data", err) 1285 } 1286 return nil 1287 case error: 1288 responseError := message.(error) 1289 _, err := res.Write([]byte(responseError.Error())) 1290 if err != nil { 1291 return errors.New("writing data", err) 1292 } 1293 return nil 1294 } 1295 data, err := json.Marshal(message) 1296 if err != nil { 1297 return errors.New("marshalling message to json", err) 1298 } 1299 _, err = res.Write(data) 1300 if err != nil { 1301 return errors.New("writing data", err) 1302 } 1303 return nil 1304 }