func getRemoteCertificate(address string) (*x509.Certificate, error) { // Setup a permissive TLS config tlsConfig, err := shared.GetTLSConfig("", "", nil) if err != nil { return nil, err } tlsConfig.InsecureSkipVerify = true tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: shared.RFC3493Dialer, Proxy: shared.ProxyFromEnvironment, } // Connect client := &http.Client{Transport: tr} resp, err := client.Get(address) if err != nil { return nil, err } // Retrieve the certificate if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 { return nil, fmt.Errorf("Unable to read remote TLS certificate") } return resp.TLS.PeerCertificates[0], nil }
func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) { var err error var cert *x509.Certificate if certificate != "" { certBlock, _ := pem.Decode([]byte(certificate)) if certBlock == nil { return nil, fmt.Errorf("Invalid certificate") } cert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, err } } tlsConfig, err := shared.GetTLSConfig("", "", "", cert) if err != nil { return nil, err } tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: shared.RFC3493Dialer, Proxy: d.proxy, DisableKeepAlives: true, } myhttp := http.Client{ Transport: tr, } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", shared.UserAgent) r, err := myhttp.Do(req) if err != nil { return nil, err } resp, err := lxd.ParseResponse(r) if err != nil { return nil, err } if resp.Type != lxd.Sync { return nil, fmt.Errorf("unexpected non-sync response") } return resp, nil }
func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, error) { var err error var cert *x509.Certificate if certificate != "" { certBlock, _ := pem.Decode([]byte(certificate)) if certBlock == nil { return nil, fmt.Errorf("Invalid certificate") } cert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, err } } tlsConfig, err := shared.GetTLSConfig("", "", "", cert) if err != nil { return nil, err } tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: shared.RFC3493Dialer, Proxy: d.proxy, DisableKeepAlives: true, } myhttp := http.Client{ Transport: tr, } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", shared.UserAgent) raw, err := myhttp.Do(req) if err != nil { return nil, err } if raw.StatusCode != 200 { _, err := lxd.HoistResponse(raw, lxd.Error) if err != nil { return nil, err } return nil, fmt.Errorf("non-200 status with no error response?") } return raw, nil }
func (d *Daemon) UpdateHTTPsPort(oldAddress string, newAddress string) error { var sockets []net.Listener if oldAddress != "" { _, _, err := net.SplitHostPort(oldAddress) if err != nil { oldAddress = fmt.Sprintf("%s:%s", oldAddress, shared.DefaultPort) } for _, socket := range d.Sockets { if socket.Addr().String() == oldAddress { socket.Close() } else { sockets = append(sockets, socket) } } } else { sockets = d.Sockets } if newAddress != "" { _, _, err := net.SplitHostPort(newAddress) if err != nil { newAddress = fmt.Sprintf("%s:%s", newAddress, shared.DefaultPort) } tlsConfig, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } tcpl, err := tls.Listen("tcp", newAddress, tlsConfig) if err != nil { return fmt.Errorf("cannot listen on https socket: %v", err) } d.tomb.Go(func() error { return http.Serve(tcpl, d.mux) }) sockets = append(sockets, tcpl) } d.Sockets = sockets return nil }
func (d *Daemon) httpGetSync(url string) (*lxd.Response, error) { var err error if d.tlsconfig == nil { d.tlsconfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return nil, err } } tr := &http.Transport{ TLSClientConfig: d.tlsconfig, Dial: shared.RFC3493Dialer, Proxy: http.ProxyFromEnvironment, } myhttp := http.Client{ Transport: tr, } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", shared.UserAgent) r, err := myhttp.Do(req) if err != nil { return nil, err } resp, err := lxd.ParseResponse(r) if err != nil { return nil, err } if resp.Type != lxd.Sync { return nil, fmt.Errorf("unexpected non-sync response") } return resp, nil }
func (d *Daemon) httpGetFile(url string) (*http.Response, error) { var err error if d.tlsconfig == nil { d.tlsconfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return nil, err } } tr := &http.Transport{ TLSClientConfig: d.tlsconfig, Dial: shared.RFC3493Dialer, Proxy: http.ProxyFromEnvironment, } myhttp := http.Client{ Transport: tr, } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", shared.UserAgent) raw, err := myhttp.Do(req) if err != nil { return nil, err } if raw.StatusCode != 200 { _, err := lxd.HoistResponse(raw, lxd.Error) if err != nil { return nil, err } return nil, fmt.Errorf("non-200 status with no error response?") } return raw, nil }
// NewClient returns a new LXD client. func NewClient(config *Config, remote string) (*Client, error) { c := Client{ Config: *config, Http: http.Client{}, } c.Name = remote if remote == "" { return nil, fmt.Errorf(i18n.G("A remote name must be provided.")) } if r, ok := config.Remotes[remote]; ok { if r.Addr[0:5] == "unix:" { if r.Addr == "unix://" { r.Addr = fmt.Sprintf("unix:%s", shared.VarPath("unix.socket")) } c.BaseURL = "http://unix.socket" c.BaseWSURL = "ws://unix.socket" c.Transport = "unix" uDial := func(networ, addr string) (net.Conn, error) { var err error var raddr *net.UnixAddr if r.Addr[7:] == "unix://" { raddr, err = net.ResolveUnixAddr("unix", r.Addr[7:]) } else { raddr, err = net.ResolveUnixAddr("unix", r.Addr[5:]) } if err != nil { return nil, err } return net.DialUnix("unix", nil, raddr) } c.Http.Transport = &http.Transport{Dial: uDial} c.websocketDialer.NetDial = uDial c.Remote = &r } else { certf, keyf, err := readMyCert(config.ConfigDir) if err != nil { return nil, err } tlsconfig, err := shared.GetTLSConfig(certf, keyf) if err != nil { return nil, err } tr := &http.Transport{ TLSClientConfig: tlsconfig, Dial: shared.RFC3493Dialer, Proxy: http.ProxyFromEnvironment, } c.websocketDialer = websocket.Dialer{ NetDial: shared.RFC3493Dialer, TLSClientConfig: tlsconfig, } c.certf = certf c.keyf = keyf if r.Addr[0:8] == "https://" { c.BaseURL = "https://" + r.Addr[8:] c.BaseWSURL = "wss://" + r.Addr[8:] } else { c.BaseURL = "https://" + r.Addr c.BaseWSURL = "wss://" + r.Addr } c.Transport = "https" c.Http.Transport = tr c.loadServerCert() c.Remote = &r } } else { return nil, fmt.Errorf(i18n.G("unknown remote name: %q"), remote) } return &c, nil }
func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error { var err error if req.Source["url"] == "" { return fmt.Errorf("Missing URL") } // Resolve the image URL if d.tlsconfig == nil { d.tlsconfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } tr := &http.Transport{ TLSClientConfig: d.tlsconfig, Dial: shared.RFC3493Dialer, Proxy: http.ProxyFromEnvironment, } myhttp := http.Client{ Transport: tr, } head, err := http.NewRequest("HEAD", req.Source["url"], nil) if err != nil { return err } architecturesStr := []string{} for _, arch := range d.architectures { architecturesStr = append(architecturesStr, fmt.Sprintf("%d", arch)) } head.Header.Set("User-Agent", shared.UserAgent) head.Header.Set("LXD-Server-Architectures", strings.Join(architecturesStr, ", ")) head.Header.Set("LXD-Server-Version", shared.Version) raw, err := myhttp.Do(head) if err != nil { return err } hash := raw.Header.Get("LXD-Image-Hash") if hash == "" { return fmt.Errorf("Missing LXD-Image-Hash header") } url := raw.Header.Get("LXD-Image-URL") if url == "" { return fmt.Errorf("Missing LXD-Image-URL header") } // Import the image err = d.ImageDownload(op, url, hash, "", false, true) if err != nil { return err } info, err := dbImageGet(d.db, hash, false, false) if err != nil { return err } if req.Public { err = dbImageSetPublic(d.db, info.Id, req.Public) if err != nil { return err } } metadata := make(map[string]string) metadata["fingerprint"] = info.Fingerprint metadata["size"] = strconv.FormatInt(info.Size, 10) op.UpdateMetadata(metadata) return nil }
func createFromMigration(d *Daemon, req *containerPostReq) Response { if req.Source.Mode != "pull" { return NotImplemented } run := func(op *operation) error { args := containerArgs{ Architecture: req.Architecture, BaseImage: req.Source.BaseImage, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } var c container _, err := dbImageGet(d.db, req.Source.BaseImage, false, true) /* Only create a container from an image if we're going to * rsync over the top of it. In the case of a better file * transfer mechanism, let's just use that. * * TODO: we could invent some negotiation here, where if the * source and sink both have the same image, we can clone from * it, but we have to know before sending the snapshot that * we're sending the whole thing or just a delta from the * image, so one extra negotiation round trip is needed. An * alternative is to move actual container object to a later * point and just negotiate it over the migration control * socket. Anyway, it'll happen later :) */ if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC { c, err = containerCreateFromImage(d, args, req.Source.BaseImage) if err != nil { return err } } else { c, err = containerCreateAsEmpty(d, args) if err != nil { return err } } config, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { c.Delete() return err } migrationArgs := MigrationSinkArgs{ Url: req.Source.Operation, Dialer: websocket.Dialer{ TLSClientConfig: config, NetDial: shared.RFC3493Dialer}, Container: c, Secrets: req.Source.Websockets, } sink, err := NewMigrationSink(&migrationArgs) if err != nil { c.Delete() return err } // Start the storage for this container (LVM mount/umount) c.StorageStart() // And finaly run the migration. err = sink() if err != nil { c.StorageStop() shared.Log.Error("Error during migration sink", "err", err) c.Delete() return fmt.Errorf("Error transferring container data: %s", err) } defer c.StorageStop() err = c.TemplateApply("copy") if err != nil { return err } return nil } resources := map[string][]string{} resources["containers"] = []string{req.Name} op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } return OperationResponse(op) }
func createFromMigration(d *Daemon, req *containerPostReq) Response { if req.Source.Mode != "pull" { return NotImplemented } run := func() shared.OperationResult { createArgs := containerLXDArgs{ Ctype: cTypeRegular, Config: req.Config, Profiles: req.Profiles, Ephemeral: req.Ephemeral, BaseImage: req.Source.BaseImage, } var c container if _, err := dbImageGet(d.db, req.Source.BaseImage, false, true); err == nil { c, err = containerLXDCreateFromImage( d, req.Name, createArgs, req.Source.BaseImage) if err != nil { return shared.OperationError(err) } } else { c, err = containerLXDCreateAsEmpty(d, req.Name, createArgs) if err != nil { return shared.OperationError(err) } } config, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { c.Delete() return shared.OperationError(err) } args := MigrationSinkArgs{ Url: req.Source.Operation, Dialer: websocket.Dialer{ TLSClientConfig: config, NetDial: shared.RFC3493Dialer}, Container: c, Secrets: req.Source.Websockets, } sink, err := NewMigrationSink(&args) if err != nil { c.Delete() return shared.OperationError(err) } // Start the storage for this container (LVM mount/umount) c.StorageStart() // And finaly run the migration. err = sink() if err != nil { c.StorageStop() c.Delete() return shared.OperationError(fmt.Errorf("Error transferring container data: %s", err)) } defer c.StorageStop() err = c.TemplateApply("copy") if err != nil { return shared.OperationError(err) } return shared.OperationError(nil) } resources := make(map[string][]string) resources["containers"] = []string{req.Name} return &asyncResponse{run: run, resources: resources} }
func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error { var err error if req.Source["url"] == "" { return fmt.Errorf("Missing URL") } // Resolve the image URL tlsConfig, err := shared.GetTLSConfig("", "", "", nil) if err != nil { return err } tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: shared.RFC3493Dialer, Proxy: d.proxy, } myhttp := http.Client{ Transport: tr, } head, err := http.NewRequest("HEAD", req.Source["url"], nil) if err != nil { return err } architecturesStr := []string{} for _, arch := range d.architectures { architecturesStr = append(architecturesStr, fmt.Sprintf("%d", arch)) } head.Header.Set("User-Agent", shared.UserAgent) head.Header.Set("LXD-Server-Architectures", strings.Join(architecturesStr, ", ")) head.Header.Set("LXD-Server-Version", shared.Version) raw, err := myhttp.Do(head) if err != nil { return err } hash := raw.Header.Get("LXD-Image-Hash") if hash == "" { return fmt.Errorf("Missing LXD-Image-Hash header") } url := raw.Header.Get("LXD-Image-URL") if url == "" { return fmt.Errorf("Missing LXD-Image-URL header") } // Import the image hash, err = d.ImageDownload(op, url, "direct", "", "", hash, false, req.AutoUpdate) if err != nil { return err } id, info, err := dbImageGet(d.db, hash, false, false) if err != nil { return err } // Allow overriding or adding properties for k, v := range req.Properties { info.Properties[k] = v } if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 { err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties) if err != nil { return err } } metadata := make(map[string]string) metadata["fingerprint"] = info.Fingerprint metadata["size"] = strconv.FormatInt(info.Size, 10) op.UpdateMetadata(metadata) return nil }
func (d *Daemon) UpdateHTTPsPort(oldAddress string, newAddress string) error { var sockets []Socket if oldAddress == newAddress { return nil } if oldAddress != "" { oldHost, oldPort, err := net.SplitHostPort(oldAddress) if err != nil { oldHost = oldAddress oldPort = shared.DefaultPort } // Strip brackets around IPv6 once we've gotten rid of the port oldHost = strings.TrimLeft(oldHost, "[") oldHost = strings.TrimRight(oldHost, "]") for _, socket := range d.Sockets { host, port, err := net.SplitHostPort(socket.Socket.Addr().String()) if err != nil { host = socket.Socket.Addr().String() port = shared.DefaultPort } // Strip brackets around IPv6 once we've gotten rid of the port host = strings.TrimLeft(host, "[") host = strings.TrimRight(host, "]") if !shared.PathExists(host) && IpsEqual(net.ParseIP(host), net.ParseIP(oldHost)) && port == oldPort { socket.Socket.Close() } else { sockets = append(sockets, socket) } } } else { sockets = d.Sockets } if newAddress != "" { _, _, err := net.SplitHostPort(newAddress) if err != nil { ip := net.ParseIP(newAddress) if ip != nil && ip.To4() == nil { newAddress = fmt.Sprintf("[%s]:%s", newAddress, shared.DefaultPort) } else { newAddress = fmt.Sprintf("%s:%s", newAddress, shared.DefaultPort) } } tlsConfig, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } tcpl, err := tls.Listen("tcp", newAddress, tlsConfig) if err != nil { return fmt.Errorf("cannot listen on https socket: %v", err) } d.tomb.Go(func() error { return http.Serve(tcpl, d.mux) }) sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } d.Sockets = sockets return nil }
// NewClient returns a new LXD client. func NewClient(config *Config, remote string) (*Client, error) { c := Client{ config: *config, http: http.Client{}, } c.name = remote // TODO: Here, we don't support configurable local remotes, we only // support the default local LXD at /var/lib/lxd/unix.socket. if remote == "" { c.BaseURL = "http://unix.socket" c.BaseWSURL = "ws://unix.socket" c.Transport = "unix" c.http.Transport = &unixTransport c.websocketDialer.NetDial = unixDial } else if r, ok := config.Remotes[remote]; ok { if r.Addr[0:5] == "unix:" { c.BaseURL = "http://unix.socket" c.BaseWSURL = "ws://unix.socket" c.Transport = "unix" uDial := func(networ, addr string) (net.Conn, error) { var err error var raddr *net.UnixAddr if r.Addr[7:] == "unix://" { raddr, err = net.ResolveUnixAddr("unix", r.Addr[7:]) } else { raddr, err = net.ResolveUnixAddr("unix", r.Addr[5:]) } if err != nil { return nil, err } return net.DialUnix("unix", nil, raddr) } c.http.Transport = &http.Transport{Dial: uDial} c.websocketDialer.NetDial = uDial c.Remote = &r } else { certf, keyf, err := readMyCert() if err != nil { return nil, err } tlsconfig, err := shared.GetTLSConfig(certf, keyf) if err != nil { return nil, err } tr := &http.Transport{ TLSClientConfig: tlsconfig, Dial: shared.RFC3493Dialer, Proxy: http.ProxyFromEnvironment, } c.websocketDialer = websocket.Dialer{ NetDial: shared.RFC3493Dialer, TLSClientConfig: tlsconfig, } c.certf = certf c.keyf = keyf if r.Addr[0:8] == "https://" { c.BaseURL = "https://" + r.Addr[8:] c.BaseWSURL = "wss://" + r.Addr[8:] } else { c.BaseURL = "https://" + r.Addr c.BaseWSURL = "wss://" + r.Addr } c.Transport = "https" c.http.Transport = tr c.loadServerCert() c.Remote = &r } } else { return nil, fmt.Errorf(gettext.Gettext("unknown remote name: %q"), remote) } if err := c.Finger(); err != nil { return nil, err } return &c, nil }
func (d *Daemon) Init() error { /* Setup logging */ if shared.Log == nil { shared.SetLogger("", "", true, true) } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect apparmor support */ if aaEnabled && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles have been manually disabled") } if aaEnabled && !shared.IsDir("/sys/kernel/security/apparmor") { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because of lack of kernel support") } if aaEnabled && !haveMacAdmin() { shared.Log.Warn("Per-container AppArmor profiles are disabled because mac_admin capability is missing.") aaEnabled = false } _, err := exec.LookPath("apparmor_parser") if aaEnabled && err != nil { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because 'apparmor_parser' couldn't be found") } if aaEnabled && runningInUserns { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because LXD is running inside a user namespace") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Create required paths */ d.lxcpath = shared.VarPath("containers") err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return err } // Create default directories if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { for { expiryStr, err := dbImageExpiryGet(d.db) var expiry int if err != nil { expiry = 10 } else { expiry, err = strconv.Atoi(expiryStr) if err != nil { expiry = 10 } if expiry <= 0 { expiry = 1 } } timer := time.NewTimer(time.Duration(expiry) * 24 * time.Hour) timeChan := timer.C select { case <-timeChan: d.pruneExpiredImages() case <-d.pruneChan: d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ containersRestart(d) containersWatch(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Log.Debug("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } gid, err := shared.GroupId(*group) if err != nil { return err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
// StartDaemon starts the shared daemon with the provided configuration. func StartDaemon(listenAddr string) (*Daemon, error) { d := &Daemon{} d.lxcpath = shared.VarPath("lxc") err := os.MkdirAll(shared.VarPath("/"), 0755) if err != nil { return nil, err } err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return nil, err } d.BackingFs, err = shared.GetFilesystem(d.lxcpath) if err != nil { shared.Debugf("Error detecting backing fs: %s\n", err) } certf, keyf, err := readMyCert() if err != nil { return nil, err } d.certf = certf d.keyf = keyf err = initDb(d) if err != nil { return nil, err } readSavedClientCAList(d) d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Debugf("sending top level 404: %s", r.URL) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Logf("error reading idmap: %s", err.Error()) shared.Logf("operations requiring idmap will not be available") } else { shared.Debugf("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Debugf(" - " + lxcmap) } } tlsConfig, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return nil, err } listeners, err := activation.Listeners(false) if err != nil { return nil, err } var localSockets []net.Listener var remoteSockets []net.Listener if len(listeners) > 0 { shared.Debugf("LXD is socket activated.\n") for _, listener := range listeners { if _, err := os.Stat(listener.Addr().String()); err == nil { localSockets = append(localSockets, listener) } else { tlsListener := tls.NewListener(listener, tlsConfig) remoteSockets = append(remoteSockets, tlsListener) } } } else { shared.Debugf("LXD isn't socket activated.\n") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if _, err := os.Stat(localSocketPath); err == nil { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Debugf("Detected old but dead unix socket, deleting it...") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return nil, err } } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return nil, fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return nil, fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return nil, err } gid, err := shared.GroupId(*group) if err != nil { return nil, err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return nil, err } localSockets = append(localSockets, unixl) if listenAddr != "" { tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { return nil, fmt.Errorf("cannot listen on unix socket: %v", err) } remoteSockets = append(remoteSockets, tcpl) } } d.localSockets = localSockets d.remoteSockets = remoteSockets d.devlxd, err = createAndBindDevLxd() if err != nil { return nil, err } containersRestart(d) containersWatch(d) d.tomb.Go(func() error { for _, socket := range d.localSockets { shared.Debugf(" - binding local socket: %s\n", socket.Addr()) d.tomb.Go(func() error { return http.Serve(socket, d.mux) }) } for _, socket := range d.remoteSockets { shared.Debugf(" - binding remote socket: %s\n", socket.Addr()) d.tomb.Go(func() error { return http.Serve(socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return d, nil }
// StartDaemon starts the shared daemon with the provided configuration. func StartDaemon() (*Daemon, error) { d := &Daemon{} /* Setup logging */ if shared.Log == nil { shared.SetLogger("", "", true, true) } shared.Log.Info("LXD is starting.") /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return nil, err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return nil, err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return nil, err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Create required paths */ d.lxcpath = shared.VarPath("containers") err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return nil, err } // Create default directories if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return nil, err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return nil, err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return nil, err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("operations requiring idmap will not be available") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initDb(d) if err != nil { return nil, err } /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return nil, err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return nil, err } /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return nil, err } if err := setupSharedMounts(); err != nil { return nil, err } /* Restart containers */ containersRestart(d) containersWatch(d) err = d.SetupStorageDriver() if err != nil { return nil, fmt.Errorf("Failed to setup storage: %s", err) } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return nil, err } var sockets []net.Listener if len(listeners) > 0 { shared.Log.Info("LXD is socket activated.") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, listener) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, tlsListener) } } } else { shared.Log.Info("LXD isn't socket activated.") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Log.Debug("Detected old but dead unix socket, deleting it...") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return nil, err } } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return nil, fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return nil, fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return nil, err } gid, err := shared.GroupId(*group) if err != nil { return nil, err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return nil, err } sockets = append(sockets, unixl) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return nil, err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { return nil, fmt.Errorf("cannot listen on https socket: %v", err) } sockets = append(sockets, tcpl) } d.Sockets = sockets d.pruneChan = make(chan bool) go func() { for { expiryStr, err := dbGetImageExpiry(d) var expiry int if err != nil { expiry = 10 } else { expiry, err = strconv.Atoi(expiryStr) if err != nil { expiry = 10 } if expiry <= 0 { expiry = 1 } } timer := time.NewTimer(time.Duration(expiry) * 24 * time.Hour) timeChan := timer.C select { case <-timeChan: d.pruneExpiredImages() case <-d.pruneChan: d.pruneExpiredImages() timer.Stop() } } }() d.tomb.Go(func() error { for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return d, nil }
func createFromMigration(d *Daemon, req *containerPostReq) Response { if req.Source.Mode != "pull" { return NotImplemented } createArgs := DbCreateContainerArgs{ d: d, name: req.Name, ctype: cTypeRegular, config: req.Config, profiles: req.Profiles, ephem: req.Ephemeral, baseImage: req.Source.BaseImage, } _, err := dbCreateContainer(createArgs) if err != nil { return SmartError(err) } c, err := newLxdContainer(req.Name, d) if err != nil { removeContainer(d, req.Name) return SmartError(err) } // rsync complaisn if the parent directory for the rootfs sync doesn't // exist dpath := shared.VarPath("lxc", req.Name) if err := os.MkdirAll(dpath, 0700); err != nil { removeContainer(d, req.Name) return InternalError(err) } if err := extractShiftIfExists(d, c, req.Source.BaseImage, req.Name); err != nil { removeContainer(d, req.Name) return InternalError(err) } config, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { removeContainer(d, req.Name) return InternalError(err) } args := migration.MigrationSinkArgs{ Url: req.Source.Operation, Dialer: websocket.Dialer{ TLSClientConfig: config, NetDial: shared.RFC3493Dialer}, Container: c.c, Secrets: req.Source.Websockets, IdMapSet: c.idmapset, } sink, err := migration.NewMigrationSink(&args) if err != nil { removeContainer(d, req.Name) return BadRequest(err) } run := func() shared.OperationResult { err := sink() if err != nil { removeContainer(d, req.Name) return shared.OperationError(err) } c, err := newLxdContainer(req.Name, d) if err != nil { return shared.OperationError(err) } err = templateApply(c, "copy") if err != nil { return shared.OperationError(err) } return shared.OperationError(nil) } resources := make(map[string][]string) resources["containers"] = []string{req.Name} return &asyncResponse{run: run, resources: resources} }
func (d *Daemon) Init() error { d.shutdownChan = make(chan bool) /* Set the executable path */ absPath, err := os.Readlink("/proc/self/exe") if err != nil { return err } d.execPath = absPath /* Set the LVM environment */ err = os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1") if err != nil { return err } /* Setup logging if that wasn't done before */ if shared.Log == nil { shared.Log, err = logging.GetLogger("", "", true, true, nil) if err != nil { return err } } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect AppArmor support */ if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because of lack of kernel support") } _, err = exec.LookPath("apparmor_parser") if aaAvailable && err != nil { aaAvailable = false aaAdmin = false shared.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.") } /* Detect AppArmor confinment */ if !aaConfined { profile := aaProfile() if profile != "unconfined" && profile != "" { aaConfined = true shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } /* Detect CGroup support */ cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") if !cgSwapAccounting { shared.Log.Warn("CGroup memory swap accounting is disabled, swap limits will be ignored.") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Set container path */ d.lxcpath = shared.VarPath("containers") /* Make sure all our directories are available */ if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devices"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("security"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("shmounts"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { d.pruneExpiredImages() for { timer := time.NewTimer(24 * time.Hour) timeChan := timer.C select { case <-timeChan: /* run once per day */ d.pruneExpiredImages() case <-d.pruneChan: /* run when image.remote_cache_expiry is changed */ d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ go func() { containersRestart(d) }() /* Start the scheduler */ go deviceTaskScheduler(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } for _, c := range apiInternal { d.createCmd("internal", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { c, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { return err } err = c.Finger() if err != nil { shared.Log.Debug("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } else { return fmt.Errorf("LXD is already running.") } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } var gid int if d.group != "" { gid, err = shared.GroupId(d.group) if err != nil { return err } } else { gid = os.Getgid() } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func createFromMigration(d *Daemon, req *containerPostReq) Response { if req.Source.Mode != "pull" && req.Source.Mode != "push" { return NotImplemented } architecture, err := shared.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } args := containerArgs{ Architecture: architecture, BaseImage: req.Source.BaseImage, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } var c container _, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true) /* Only create a container from an image if we're going to * rsync over the top of it. In the case of a better file * transfer mechanism, let's just use that. * * TODO: we could invent some negotiation here, where if the * source and sink both have the same image, we can clone from * it, but we have to know before sending the snapshot that * we're sending the whole thing or just a delta from the * image, so one extra negotiation round trip is needed. An * alternative is to move actual container object to a later * point and just negotiate it over the migration control * socket. Anyway, it'll happen later :) */ if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC { c, err = containerCreateFromImage(d, args, req.Source.BaseImage) if err != nil { return InternalError(err) } } else { c, err = containerCreateAsEmpty(d, args) if err != nil { return InternalError(err) } } var cert *x509.Certificate if req.Source.Certificate != "" { certBlock, _ := pem.Decode([]byte(req.Source.Certificate)) if certBlock == nil { return InternalError(fmt.Errorf("Invalid certificate")) } cert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return InternalError(err) } } config, err := shared.GetTLSConfig("", "", "", cert) if err != nil { c.Delete() return InternalError(err) } push := false if req.Source.Mode == "push" { push = true } migrationArgs := MigrationSinkArgs{ Url: req.Source.Operation, Dialer: websocket.Dialer{ TLSClientConfig: config, NetDial: shared.RFC3493Dialer}, Container: c, Secrets: req.Source.Websockets, Push: push, Live: req.Source.Live, } sink, err := NewMigrationSink(&migrationArgs) if err != nil { c.Delete() return InternalError(err) } run := func(op *operation) error { // And finaly run the migration. err = sink.Do(op) if err != nil { shared.LogError("Error during migration sink", log.Ctx{"err": err}) c.Delete() return fmt.Errorf("Error transferring container data: %s", err) } err = c.TemplateApply("copy") if err != nil { return err } return nil } resources := map[string][]string{} resources["containers"] = []string{req.Name} var op *operation if push { op, err = operationCreate(operationClassWebsocket, resources, sink.Metadata(), run, nil, sink.Connect) if err != nil { return InternalError(err) } } else { op, err = operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } } return OperationResponse(op) }