2013-06-25 02:30:48 -04:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
2013-11-14 01:10:20 -05:00
|
|
|
"bytes"
|
2013-11-18 14:25:13 -05:00
|
|
|
"fmt"
|
2013-06-25 02:30:48 -04:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2013-11-15 19:05:57 -05:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2013-06-25 02:30:48 -04:00
|
|
|
"os"
|
|
|
|
"path"
|
2014-11-17 14:39:55 -05:00
|
|
|
"path/filepath"
|
2013-06-25 02:30:48 -04:00
|
|
|
"strings"
|
|
|
|
"testing"
|
2013-11-15 19:05:57 -05:00
|
|
|
"time"
|
2014-02-03 18:36:39 -05:00
|
|
|
|
2014-07-24 18:19:50 -04:00
|
|
|
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
2014-05-09 14:09:59 -04:00
|
|
|
|
2015-04-07 21:57:54 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2014-07-24 18:19:50 -04:00
|
|
|
"github.com/docker/docker/daemon"
|
2015-04-04 00:06:48 -04:00
|
|
|
"github.com/docker/docker/daemon/networkdriver/bridge"
|
2014-07-24 18:19:50 -04:00
|
|
|
"github.com/docker/docker/engine"
|
2015-04-07 21:57:54 -04:00
|
|
|
"github.com/docker/docker/graph"
|
2014-09-04 02:21:51 -04:00
|
|
|
flag "github.com/docker/docker/pkg/mflag"
|
2014-10-06 21:54:52 -04:00
|
|
|
"github.com/docker/docker/registry"
|
2014-07-24 18:19:50 -04:00
|
|
|
"github.com/docker/docker/runconfig"
|
|
|
|
"github.com/docker/docker/utils"
|
2013-06-25 02:30:48 -04:00
|
|
|
)
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
type Fataler interface {
|
|
|
|
Fatal(...interface{})
|
|
|
|
}
|
|
|
|
|
2013-06-25 02:30:48 -04:00
|
|
|
// This file contains utility functions for docker's unit test suite.
|
|
|
|
// It has to be named XXX_test.go, apparently, in other to access private functions
|
|
|
|
// from other XXX_test.go functions.
|
|
|
|
|
2014-04-17 17:43:01 -04:00
|
|
|
// Create a temporary daemon suitable for unit testing.
|
2013-06-25 02:30:48 -04:00
|
|
|
// Call t.Fatal() at the first error.
|
2014-10-24 13:12:35 -04:00
|
|
|
func mkDaemon(f Fataler) *daemon.Daemon {
|
2014-02-24 14:22:48 -05:00
|
|
|
eng := newTestEngine(f, false, "")
|
2014-04-17 17:43:01 -04:00
|
|
|
return mkDaemonFromEngine(eng, f)
|
2013-06-25 02:30:48 -04:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) {
|
2015-04-10 20:05:21 -04:00
|
|
|
containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{})
|
2015-04-09 19:29:31 -04:00
|
|
|
if err != nil {
|
2013-10-27 22:20:00 -04:00
|
|
|
f.Fatal(err)
|
|
|
|
}
|
2015-04-09 19:29:31 -04:00
|
|
|
return containerId
|
2013-07-11 20:59:25 -04:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) {
|
2013-10-27 22:20:00 -04:00
|
|
|
return createNamedTestContainer(eng, config, f, "")
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func startContainer(eng *engine.Engine, id string, t Fataler) {
|
2015-04-10 20:05:21 -04:00
|
|
|
if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil {
|
2013-11-15 19:05:57 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerRun(eng *engine.Engine, id string, t Fataler) {
|
2013-11-15 19:05:57 -05:00
|
|
|
startContainer(eng, id, t)
|
|
|
|
containerWait(eng, id, t)
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool {
|
2013-11-15 19:05:57 -05:00
|
|
|
c := getContainer(eng, id, t)
|
2013-12-06 06:15:14 -05:00
|
|
|
if err := c.Mount(); err != nil {
|
2013-11-15 19:05:57 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2013-12-05 16:18:02 -05:00
|
|
|
defer c.Unmount()
|
2014-03-04 04:16:09 -05:00
|
|
|
if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil {
|
2013-11-15 19:05:57 -05:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) {
|
2013-11-15 19:05:57 -05:00
|
|
|
c := getContainer(eng, id, t)
|
2014-12-04 16:12:29 -05:00
|
|
|
i := c.StdinPipe()
|
|
|
|
o := c.StdoutPipe()
|
2013-11-15 19:05:57 -05:00
|
|
|
return i, o
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerWait(eng *engine.Engine, id string, t Fataler) int {
|
2014-08-31 11:20:35 -04:00
|
|
|
ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second)
|
2014-06-06 07:30:04 -04:00
|
|
|
return ex
|
2013-11-15 19:05:57 -05:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error {
|
2014-08-31 11:20:35 -04:00
|
|
|
_, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond)
|
2014-06-06 07:30:04 -04:00
|
|
|
return err
|
2013-11-15 19:05:57 -05:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerKill(eng *engine.Engine, id string, t Fataler) {
|
2015-04-09 14:56:47 -04:00
|
|
|
if err := getDaemon(eng).ContainerKill(id, 0); err != nil {
|
2013-11-15 19:05:57 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerRunning(eng *engine.Engine, id string, t Fataler) bool {
|
2014-08-31 11:20:35 -04:00
|
|
|
return getContainer(eng, id, t).IsRunning()
|
2013-11-15 19:05:57 -05:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerAssertExists(eng *engine.Engine, id string, t Fataler) {
|
2013-11-15 19:05:57 -05:00
|
|
|
getContainer(eng, id, t)
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) {
|
2014-04-17 17:43:01 -04:00
|
|
|
daemon := mkDaemonFromEngine(eng, t)
|
2014-12-16 18:06:35 -05:00
|
|
|
if c, _ := daemon.Get(id); c != nil {
|
2013-11-15 19:05:57 -05:00
|
|
|
t.Fatal(fmt.Errorf("Container %s should not exist", id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// assertHttpNotError expect the given response to not have an error.
|
|
|
|
// Otherwise the it causes the test to fail.
|
2014-10-24 13:12:35 -04:00
|
|
|
func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) {
|
2013-11-15 19:05:57 -05:00
|
|
|
// Non-error http status are [200, 400)
|
|
|
|
if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
|
|
|
|
t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// assertHttpError expect the given response to have an error.
|
|
|
|
// Otherwise the it causes the test to fail.
|
2014-10-24 13:12:35 -04:00
|
|
|
func assertHttpError(r *httptest.ResponseRecorder, t Fataler) {
|
2013-11-15 19:05:57 -05:00
|
|
|
// Non-error http status are [200, 400)
|
|
|
|
if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) {
|
|
|
|
t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container {
|
2014-04-17 17:43:01 -04:00
|
|
|
daemon := mkDaemonFromEngine(eng, t)
|
2014-12-16 18:06:35 -05:00
|
|
|
c, err := daemon.Get(id)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2013-11-15 19:05:57 -05:00
|
|
|
}
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon {
|
2015-03-25 21:40:23 -04:00
|
|
|
iDaemon := eng.HackGetGlobalVar("httpapi.daemon")
|
2014-04-17 17:43:01 -04:00
|
|
|
if iDaemon == nil {
|
|
|
|
panic("Legacy daemon field not set in engine")
|
2013-11-14 01:10:20 -05:00
|
|
|
}
|
2014-04-17 17:43:01 -04:00
|
|
|
daemon, ok := iDaemon.(*daemon.Daemon)
|
2013-11-14 01:10:20 -05:00
|
|
|
if !ok {
|
2014-04-17 17:43:01 -04:00
|
|
|
panic("Legacy daemon field in engine does not cast to *daemon.Daemon")
|
2013-11-14 01:10:20 -05:00
|
|
|
}
|
2014-04-17 17:43:01 -04:00
|
|
|
return daemon
|
2013-11-14 01:10:20 -05:00
|
|
|
}
|
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
|
2014-02-24 14:22:48 -05:00
|
|
|
if root == "" {
|
|
|
|
if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
root = dir
|
|
|
|
}
|
2013-07-11 20:59:25 -04:00
|
|
|
}
|
2014-04-22 22:24:47 -04:00
|
|
|
os.MkdirAll(root, 0700)
|
2014-04-23 14:54:35 -04:00
|
|
|
|
|
|
|
eng := engine.New()
|
2014-09-19 20:30:37 -04:00
|
|
|
eng.Logging = false
|
2014-10-06 21:54:52 -04:00
|
|
|
|
2013-10-27 03:13:45 -04:00
|
|
|
// (This is manually copied and modified from main() until we have a more generic plugin system)
|
2014-08-09 18:30:27 -04:00
|
|
|
cfg := &daemon.Config{
|
2014-08-08 05:12:39 -04:00
|
|
|
Root: root,
|
|
|
|
AutoRestart: autorestart,
|
|
|
|
ExecDriver: "native",
|
2014-08-09 18:30:27 -04:00
|
|
|
// Either InterContainerCommunication or EnableIptables must be set,
|
|
|
|
// otherwise NewDaemon will fail because of conflicting settings.
|
2015-04-04 00:06:48 -04:00
|
|
|
Bridge: bridge.Config{
|
|
|
|
InterContainerCommunication: true,
|
|
|
|
},
|
|
|
|
TrustKeyPath: filepath.Join(root, "key.json"),
|
|
|
|
LogConfig: runconfig.LogConfig{Type: "json-file"},
|
2014-08-08 05:12:39 -04:00
|
|
|
}
|
2015-03-31 19:21:37 -04:00
|
|
|
d, err := daemon.NewDaemon(cfg, eng, registry.NewService(nil))
|
2014-08-08 05:12:39 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := d.Install(eng); err != nil {
|
2013-10-27 03:13:45 -04:00
|
|
|
t.Fatal(err)
|
2013-07-11 20:59:25 -04:00
|
|
|
}
|
2013-10-27 03:13:45 -04:00
|
|
|
return eng
|
|
|
|
}
|
2013-07-11 20:59:25 -04:00
|
|
|
|
2014-10-24 13:12:35 -04:00
|
|
|
func NewTestEngine(t Fataler) *engine.Engine {
|
2014-02-24 14:22:48 -05:00
|
|
|
return newTestEngine(t, false, "")
|
|
|
|
}
|
|
|
|
|
2013-10-27 03:13:45 -04:00
|
|
|
func newTestDirectory(templateDir string) (dir string, err error) {
|
2013-11-14 01:10:20 -05:00
|
|
|
return utils.TestDirectory(templateDir)
|
2013-10-27 03:13:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func getCallerName(depth int) string {
|
2013-11-14 01:10:20 -05:00
|
|
|
return utils.GetCallerName(depth)
|
2013-07-11 20:59:25 -04:00
|
|
|
}
|
|
|
|
|
2013-06-25 02:30:48 -04:00
|
|
|
// Write `content` to the file at path `dst`, creating it if necessary,
|
|
|
|
// as well as any missing directories.
|
|
|
|
// The file is truncated if it already exists.
|
|
|
|
// Call t.Fatal() at the first error.
|
|
|
|
func writeFile(dst, content string, t *testing.T) {
|
|
|
|
// Create subdirectories if necessary
|
|
|
|
if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2013-06-26 15:04:55 -04:00
|
|
|
f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
|
2013-06-25 02:30:48 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// Write content (truncate if it exists)
|
|
|
|
if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the contents of file at path `src`.
|
|
|
|
// Call t.Fatal() at the first error (including if the file doesn't exist)
|
|
|
|
func readFile(src string, t *testing.T) (content string) {
|
|
|
|
f, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return string(data)
|
|
|
|
}
|
|
|
|
|
2014-04-17 17:43:01 -04:00
|
|
|
// Create a test container from the given daemon `r` and run arguments `args`.
|
2013-07-12 20:56:55 -04:00
|
|
|
// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
|
|
|
|
// dynamically replaced by the current test image.
|
2013-06-25 02:30:48 -04:00
|
|
|
// The caller is responsible for destroying the container.
|
|
|
|
// Call t.Fatal() at the first error.
|
2014-04-17 17:43:01 -04:00
|
|
|
func mkContainer(r *daemon.Daemon, args []string, t *testing.T) (*daemon.Container, *runconfig.HostConfig, error) {
|
2014-10-30 12:35:49 -04:00
|
|
|
config, hc, _, err := parseRun(args)
|
2013-07-12 20:56:55 -04:00
|
|
|
defer func() {
|
|
|
|
if err != nil && t != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
2013-06-25 02:30:48 -04:00
|
|
|
if err != nil {
|
2013-11-14 15:33:15 -05:00
|
|
|
return nil, nil, err
|
2013-07-12 20:56:55 -04:00
|
|
|
}
|
|
|
|
if config.Image == "_" {
|
|
|
|
config.Image = GetTestImage(r).ID
|
2013-06-25 02:30:48 -04:00
|
|
|
}
|
2014-09-25 17:23:59 -04:00
|
|
|
c, _, err := r.Create(config, nil, "")
|
2013-06-25 02:30:48 -04:00
|
|
|
if err != nil {
|
2013-11-14 15:33:15 -05:00
|
|
|
return nil, nil, err
|
2013-06-25 02:30:48 -04:00
|
|
|
}
|
2013-11-14 01:10:20 -05:00
|
|
|
// NOTE: hostConfig is ignored.
|
|
|
|
// If `args` specify privileged mode, custom lxc conf, external mount binds,
|
|
|
|
// port redirects etc. they will be ignored.
|
|
|
|
// This is because the correct way to set these things is to pass environment
|
|
|
|
// to the `start` job.
|
|
|
|
// FIXME: this helper function should be deprecated in favor of calling
|
|
|
|
// `create` and `start` jobs directly.
|
2013-11-14 15:33:15 -05:00
|
|
|
return c, hc, nil
|
2013-06-25 02:30:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a test container, start it, wait for it to complete, destroy it,
|
|
|
|
// and return its standard output as a string.
|
2013-06-26 15:04:55 -04:00
|
|
|
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
|
2013-06-25 02:30:48 -04:00
|
|
|
// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally.
|
2014-04-17 17:43:01 -04:00
|
|
|
func runContainer(eng *engine.Engine, r *daemon.Daemon, args []string, t *testing.T) (output string, err error) {
|
2013-06-25 02:30:48 -04:00
|
|
|
defer func() {
|
|
|
|
if err != nil && t != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
2013-11-14 15:33:15 -05:00
|
|
|
container, hc, err := mkContainer(r, args, t)
|
2013-07-12 20:56:55 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2015-02-23 10:15:56 -05:00
|
|
|
defer r.Rm(container)
|
2014-12-04 16:12:29 -05:00
|
|
|
stdout := container.StdoutPipe()
|
2013-06-25 02:30:48 -04:00
|
|
|
defer stdout.Close()
|
2013-11-14 15:33:15 -05:00
|
|
|
|
|
|
|
job := eng.Job("start", container.ID)
|
|
|
|
if err := job.ImportEnv(hc); err != nil {
|
2013-06-25 02:30:48 -04:00
|
|
|
return "", err
|
|
|
|
}
|
2013-11-14 15:33:15 -05:00
|
|
|
if err := job.Run(); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2014-08-31 11:20:35 -04:00
|
|
|
container.WaitStop(-1 * time.Second)
|
2013-06-25 02:30:48 -04:00
|
|
|
data, err := ioutil.ReadAll(stdout)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
output = string(data)
|
|
|
|
return
|
|
|
|
}
|
2013-07-15 09:12:33 -04:00
|
|
|
|
2013-11-14 01:10:20 -05:00
|
|
|
// FIXME: this is duplicated from graph_test.go in the docker package.
|
2014-02-14 06:41:46 -05:00
|
|
|
func fakeTar() (io.ReadCloser, error) {
|
2013-11-14 01:10:20 -05:00
|
|
|
content := []byte("Hello world!\n")
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
tw := tar.NewWriter(buf)
|
|
|
|
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
|
|
|
|
hdr := new(tar.Header)
|
|
|
|
hdr.Size = int64(len(content))
|
|
|
|
hdr.Name = name
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
|
|
return nil, err
|
2013-08-15 19:35:03 -04:00
|
|
|
}
|
2013-11-14 01:10:20 -05:00
|
|
|
tw.Write([]byte(content))
|
2013-08-15 19:35:03 -04:00
|
|
|
}
|
2013-11-14 01:10:20 -05:00
|
|
|
tw.Close()
|
2014-02-14 06:41:46 -05:00
|
|
|
return ioutil.NopCloser(buf), nil
|
2013-08-15 19:35:03 -04:00
|
|
|
}
|
2013-12-12 17:39:35 -05:00
|
|
|
|
2015-04-07 21:57:54 -04:00
|
|
|
func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) []*types.Image {
|
|
|
|
config := graph.ImagesConfig{
|
|
|
|
Filter: filter,
|
|
|
|
All: all,
|
2013-12-12 17:39:35 -05:00
|
|
|
}
|
2015-04-07 21:57:54 -04:00
|
|
|
images, err := getDaemon(eng).Repositories().Images(&config)
|
|
|
|
if err != nil {
|
2013-12-12 17:39:35 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2015-04-07 21:57:54 -04:00
|
|
|
return images
|
2013-12-12 17:39:35 -05:00
|
|
|
}
|
2014-09-04 02:21:51 -04:00
|
|
|
|
2014-10-30 12:35:49 -04:00
|
|
|
func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) {
|
2014-09-04 02:21:51 -04:00
|
|
|
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
|
|
|
cmd.SetOutput(ioutil.Discard)
|
|
|
|
cmd.Usage = nil
|
2014-10-30 12:35:49 -04:00
|
|
|
return runconfig.Parse(cmd, args)
|
2014-09-04 02:21:51 -04:00
|
|
|
}
|
2015-04-07 21:57:54 -04:00
|
|
|
|
|
|
|
func getDaemon(eng *engine.Engine) *daemon.Daemon {
|
|
|
|
return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon)
|
|
|
|
}
|