... | ... |
@@ -33,15 +33,13 @@ run apt-get update |
33 | 33 |
run apt-get install -y -q curl |
34 | 34 |
run apt-get install -y -q git |
35 | 35 |
run apt-get install -y -q mercurial |
36 |
-run apt-get install -y -q build-essential |
|
36 |
+run apt-get install -y -q build-essential libsqlite3-dev |
|
37 | 37 |
|
38 |
-# Install Go from source (for eventual cross-compiling) |
|
39 |
-env CGO_ENABLED 0 |
|
40 |
-run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot |
|
41 |
-run cd /goroot/src && ./make.bash |
|
42 |
-env GOROOT /goroot |
|
43 |
-env PATH $PATH:/goroot/bin |
|
38 |
+# Install Go |
|
39 |
+run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz |
|
40 |
+env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin |
|
44 | 41 |
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor |
42 |
+run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std |
|
45 | 43 |
|
46 | 44 |
# Ubuntu stuff |
47 | 45 |
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev |
... | ... |
@@ -6,6 +6,7 @@ import ( |
6 | 6 |
"encoding/json" |
7 | 7 |
"fmt" |
8 | 8 |
"github.com/dotcloud/docker/auth" |
9 |
+ "github.com/dotcloud/docker/gograph" |
|
9 | 10 |
"github.com/dotcloud/docker/utils" |
10 | 11 |
"github.com/gorilla/mux" |
11 | 12 |
"io" |
... | ... |
@@ -14,6 +15,7 @@ import ( |
14 | 14 |
"mime" |
15 | 15 |
"net" |
16 | 16 |
"net/http" |
17 |
+ "net/url" |
|
17 | 18 |
"os" |
18 | 19 |
"os/exec" |
19 | 20 |
"regexp" |
... | ... |
@@ -154,7 +156,7 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r * |
154 | 154 |
} |
155 | 155 |
} |
156 | 156 |
} |
157 |
- |
|
157 |
+ name = decodeName(name) |
|
158 | 158 |
if err := srv.ContainerKill(name, signal); err != nil { |
159 | 159 |
return err |
160 | 160 |
} |
... | ... |
@@ -167,6 +169,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r |
167 | 167 |
return fmt.Errorf("Missing parameter") |
168 | 168 |
} |
169 | 169 |
name := vars["name"] |
170 |
+ name = decodeName(name) |
|
170 | 171 |
|
171 | 172 |
if err := srv.ContainerExport(name, w); err != nil { |
172 | 173 |
utils.Errorf("%s", err) |
... | ... |
@@ -534,16 +537,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r |
534 | 534 |
return err |
535 | 535 |
} |
536 | 536 |
|
537 |
- if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { |
|
537 |
+ if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { |
|
538 | 538 |
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns)) |
539 | 539 |
config.Dns = defaultDns |
540 | 540 |
} |
541 | 541 |
|
542 |
- id, err := srv.ContainerCreate(config) |
|
542 |
+ id, warnings, err := srv.ContainerCreate(config) |
|
543 | 543 |
if err != nil { |
544 | 544 |
return err |
545 | 545 |
} |
546 | 546 |
out.ID = id |
547 |
+ for _, warning := range warnings { |
|
548 |
+ out.Warnings = append(out.Warnings, warning) |
|
549 |
+ } |
|
547 | 550 |
|
548 | 551 |
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { |
549 | 552 |
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") |
... | ... |
@@ -574,6 +580,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, |
574 | 574 |
return fmt.Errorf("Missing parameter") |
575 | 575 |
} |
576 | 576 |
name := vars["name"] |
577 |
+ name = decodeName(name) |
|
577 | 578 |
if err := srv.ContainerRestart(name, t); err != nil { |
578 | 579 |
return err |
579 | 580 |
} |
... | ... |
@@ -589,12 +596,18 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht |
589 | 589 |
return fmt.Errorf("Missing parameter") |
590 | 590 |
} |
591 | 591 |
name := vars["name"] |
592 |
+ name = decodeName(name) |
|
593 |
+ |
|
592 | 594 |
removeVolume, err := getBoolParam(r.Form.Get("v")) |
593 | 595 |
if err != nil { |
594 | 596 |
return err |
595 | 597 |
} |
598 |
+ removeLink, err := getBoolParam(r.Form.Get("link")) |
|
599 |
+ if err != nil { |
|
600 |
+ return err |
|
601 |
+ } |
|
596 | 602 |
|
597 |
- if err := srv.ContainerDestroy(name, removeVolume); err != nil { |
|
603 |
+ if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil { |
|
598 | 604 |
return err |
599 | 605 |
} |
600 | 606 |
w.WriteHeader(http.StatusNoContent) |
... | ... |
@@ -640,7 +653,12 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r |
640 | 640 |
if vars == nil { |
641 | 641 |
return fmt.Errorf("Missing parameter") |
642 | 642 |
} |
643 |
+ var err error |
|
643 | 644 |
name := vars["name"] |
645 |
+ name = decodeName(name) |
|
646 |
+ if err != nil { |
|
647 |
+ return err |
|
648 |
+ } |
|
644 | 649 |
if err := srv.ContainerStart(name, hostConfig); err != nil { |
645 | 650 |
return err |
646 | 651 |
} |
... | ... |
@@ -661,6 +679,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * |
661 | 661 |
return fmt.Errorf("Missing parameter") |
662 | 662 |
} |
663 | 663 |
name := vars["name"] |
664 |
+ name = decodeName(name) |
|
664 | 665 |
|
665 | 666 |
if err := srv.ContainerStop(name, t); err != nil { |
666 | 667 |
return err |
... | ... |
@@ -674,6 +693,8 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * |
674 | 674 |
return fmt.Errorf("Missing parameter") |
675 | 675 |
} |
676 | 676 |
name := vars["name"] |
677 |
+ name = decodeName(name) |
|
678 |
+ |
|
677 | 679 |
status, err := srv.ContainerWait(name) |
678 | 680 |
if err != nil { |
679 | 681 |
return err |
... | ... |
@@ -733,6 +754,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r |
733 | 733 |
return fmt.Errorf("Missing parameter") |
734 | 734 |
} |
735 | 735 |
name := vars["name"] |
736 |
+ name = decodeName(name) |
|
736 | 737 |
|
737 | 738 |
c, err := srv.ContainerInspect(name) |
738 | 739 |
if err != nil { |
... | ... |
@@ -805,6 +827,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * |
805 | 805 |
return fmt.Errorf("Missing parameter") |
806 | 806 |
} |
807 | 807 |
name := vars["name"] |
808 |
+ name = decodeName(name) |
|
808 | 809 |
|
809 | 810 |
if _, err := srv.ContainerInspect(name); err != nil { |
810 | 811 |
return err |
... | ... |
@@ -827,6 +850,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r |
827 | 827 |
return fmt.Errorf("Missing parameter") |
828 | 828 |
} |
829 | 829 |
name := vars["name"] |
830 |
+ name = decodeName(name) |
|
830 | 831 |
|
831 | 832 |
container, err := srv.ContainerInspect(name) |
832 | 833 |
if err != nil { |
... | ... |
@@ -994,7 +1018,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s |
994 | 994 |
if err != nil { |
995 | 995 |
version = APIVERSION |
996 | 996 |
} |
997 |
- if srv.enableCors { |
|
997 |
+ if srv.runtime.config.EnableCors { |
|
998 | 998 |
writeCorsHeaders(w, r) |
999 | 999 |
} |
1000 | 1000 |
|
... | ... |
@@ -1010,6 +1034,75 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s |
1010 | 1010 |
} |
1011 | 1011 |
} |
1012 | 1012 |
|
1013 |
+func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
1014 |
+ if err := parseForm(r); err != nil { |
|
1015 |
+ return err |
|
1016 |
+ } |
|
1017 |
+ |
|
1018 |
+ runtime := srv.runtime |
|
1019 |
+ all, err := getBoolParam(r.Form.Get("all")) |
|
1020 |
+ if err != nil { |
|
1021 |
+ return err |
|
1022 |
+ } |
|
1023 |
+ |
|
1024 |
+ out := []APILink{} |
|
1025 |
+ err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { |
|
1026 |
+ if container := runtime.Get(e.ID()); container != nil { |
|
1027 |
+ if !all && strings.Contains(p, container.ID) { |
|
1028 |
+ return nil |
|
1029 |
+ } |
|
1030 |
+ out = append(out, APILink{ |
|
1031 |
+ Path: p, |
|
1032 |
+ ContainerID: container.ID, |
|
1033 |
+ Image: runtime.repositories.ImageName(container.Image), |
|
1034 |
+ }) |
|
1035 |
+ } |
|
1036 |
+ return nil |
|
1037 |
+ }, -1) |
|
1038 |
+ |
|
1039 |
+ if err != nil { |
|
1040 |
+ return err |
|
1041 |
+ } |
|
1042 |
+ return writeJSON(w, http.StatusOK, out) |
|
1043 |
+} |
|
1044 |
+ |
|
1045 |
+func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
1046 |
+ if vars == nil { |
|
1047 |
+ return fmt.Errorf("Missing parameter") |
|
1048 |
+ } |
|
1049 |
+ values := make(map[string]string) |
|
1050 |
+ if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil { |
|
1051 |
+ defer r.Body.Close() |
|
1052 |
+ |
|
1053 |
+ dec := json.NewDecoder(r.Body) |
|
1054 |
+ if err := dec.Decode(&values); err != nil { |
|
1055 |
+ return err |
|
1056 |
+ } |
|
1057 |
+ } else { |
|
1058 |
+ return fmt.Errorf("Invalid json body") |
|
1059 |
+ } |
|
1060 |
+ currentName := values["currentName"] |
|
1061 |
+ newName := values["newName"] |
|
1062 |
+ |
|
1063 |
+ if currentName == "" { |
|
1064 |
+ return fmt.Errorf("currentName cannot be empty") |
|
1065 |
+ } |
|
1066 |
+ if newName == "" { |
|
1067 |
+ return fmt.Errorf("newName cannot be empty") |
|
1068 |
+ } |
|
1069 |
+ |
|
1070 |
+ if err := srv.runtime.RenameLink(currentName, newName); err != nil { |
|
1071 |
+ return err |
|
1072 |
+ } |
|
1073 |
+ |
|
1074 |
+ return nil |
|
1075 |
+} |
|
1076 |
+ |
|
1077 |
+func decodeName(name string) string { |
|
1078 |
+ s, _ := url.QueryUnescape(name) |
|
1079 |
+ return s |
|
1080 |
+} |
|
1081 |
+ |
|
1013 | 1082 |
func createRouter(srv *Server, logging bool) (*mux.Router, error) { |
1014 | 1083 |
r := mux.NewRouter() |
1015 | 1084 |
|
... | ... |
@@ -1030,6 +1123,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { |
1030 | 1030 |
"/containers/{name:.*}/json": getContainersByName, |
1031 | 1031 |
"/containers/{name:.*}/top": getContainersTop, |
1032 | 1032 |
"/containers/{name:.*}/attach/ws": wsContainersAttach, |
1033 |
+ "/containers/links": getContainersLinks, |
|
1033 | 1034 |
}, |
1034 | 1035 |
"POST": { |
1035 | 1036 |
"/auth": postAuth, |
... | ... |
@@ -1048,6 +1142,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { |
1048 | 1048 |
"/containers/{name:.*}/resize": postContainersResize, |
1049 | 1049 |
"/containers/{name:.*}/attach": postContainersAttach, |
1050 | 1050 |
"/containers/{name:.*}/copy": postContainersCopy, |
1051 |
+ "/containers/link": postContainerLink, |
|
1051 | 1052 |
}, |
1052 | 1053 |
"DELETE": { |
1053 | 1054 |
"/containers/{name:.*}": deleteContainers, |
... | ... |
@@ -1,7 +1,5 @@ |
1 | 1 |
package docker |
2 | 2 |
|
3 |
-import "encoding/json" |
|
4 |
- |
|
5 | 3 |
type APIHistory struct { |
6 | 4 |
ID string `json:"Id"` |
7 | 5 |
Tags []string `json:",omitempty"` |
... | ... |
@@ -52,6 +50,7 @@ type APIContainers struct { |
52 | 52 |
Ports []APIPort |
53 | 53 |
SizeRw int64 |
54 | 54 |
SizeRootFs int64 |
55 |
+ Names []string |
|
55 | 56 |
} |
56 | 57 |
|
57 | 58 |
func (self *APIContainers) ToLegacy() APIContainersOld { |
... | ... |
@@ -96,14 +95,7 @@ type APIPort struct { |
96 | 96 |
PrivatePort int64 |
97 | 97 |
PublicPort int64 |
98 | 98 |
Type string |
99 |
-} |
|
100 |
- |
|
101 |
-func (port *APIPort) MarshalJSON() ([]byte, error) { |
|
102 |
- return json.Marshal(map[string]interface{}{ |
|
103 |
- "PrivatePort": port.PrivatePort, |
|
104 |
- "PublicPort": port.PublicPort, |
|
105 |
- "Type": port.Type, |
|
106 |
- }) |
|
99 |
+ IP string |
|
107 | 100 |
} |
108 | 101 |
|
109 | 102 |
type APIVersion struct { |
... | ... |
@@ -129,3 +121,9 @@ type APICopy struct { |
129 | 129 |
Resource string |
130 | 130 |
HostPath string |
131 | 131 |
} |
132 |
+ |
|
133 |
+type APILink struct { |
|
134 |
+ Path string |
|
135 |
+ ContainerID string |
|
136 |
+ Image string |
|
137 |
+} |
... | ... |
@@ -349,7 +349,7 @@ func TestGetContainersJSON(t *testing.T) { |
349 | 349 |
|
350 | 350 |
beginLen := runtime.containers.Len() |
351 | 351 |
|
352 |
- container, err := runtime.Create(&Config{ |
|
352 |
+ container, _, err := runtime.Create(&Config{ |
|
353 | 353 |
Image: GetTestImage(runtime).ID, |
354 | 354 |
Cmd: []string{"echo", "test"}, |
355 | 355 |
}) |
... | ... |
@@ -386,7 +386,7 @@ func TestGetContainersExport(t *testing.T) { |
386 | 386 |
srv := &Server{runtime: runtime} |
387 | 387 |
|
388 | 388 |
// Create a container and remove a file |
389 |
- container, err := runtime.Create( |
|
389 |
+ container, _, err := runtime.Create( |
|
390 | 390 |
&Config{ |
391 | 391 |
Image: GetTestImage(runtime).ID, |
392 | 392 |
Cmd: []string{"touch", "/test"}, |
... | ... |
@@ -436,7 +436,7 @@ func TestGetContainersChanges(t *testing.T) { |
436 | 436 |
srv := &Server{runtime: runtime} |
437 | 437 |
|
438 | 438 |
// Create a container and remove a file |
439 |
- container, err := runtime.Create( |
|
439 |
+ container, _, err := runtime.Create( |
|
440 | 440 |
&Config{ |
441 | 441 |
Image: GetTestImage(runtime).ID, |
442 | 442 |
Cmd: []string{"/bin/rm", "/etc/passwd"}, |
... | ... |
@@ -479,7 +479,7 @@ func TestGetContainersTop(t *testing.T) { |
479 | 479 |
|
480 | 480 |
srv := &Server{runtime: runtime} |
481 | 481 |
|
482 |
- container, err := runtime.Create( |
|
482 |
+ container, _, err := runtime.Create( |
|
483 | 483 |
&Config{ |
484 | 484 |
Image: GetTestImage(runtime).ID, |
485 | 485 |
Cmd: []string{"/bin/sh", "-c", "cat"}, |
... | ... |
@@ -561,7 +561,7 @@ func TestGetContainersByName(t *testing.T) { |
561 | 561 |
srv := &Server{runtime: runtime} |
562 | 562 |
|
563 | 563 |
// Create a container and remove a file |
564 |
- container, err := runtime.Create( |
|
564 |
+ container, _, err := runtime.Create( |
|
565 | 565 |
&Config{ |
566 | 566 |
Image: GetTestImage(runtime).ID, |
567 | 567 |
Cmd: []string{"echo", "test"}, |
... | ... |
@@ -592,7 +592,7 @@ func TestPostCommit(t *testing.T) { |
592 | 592 |
srv := &Server{runtime: runtime} |
593 | 593 |
|
594 | 594 |
// Create a container and remove a file |
595 |
- container, err := runtime.Create( |
|
595 |
+ container, _, err := runtime.Create( |
|
596 | 596 |
&Config{ |
597 | 597 |
Image: GetTestImage(runtime).ID, |
598 | 598 |
Cmd: []string{"touch", "/test"}, |
... | ... |
@@ -686,7 +686,7 @@ func TestPostContainersKill(t *testing.T) { |
686 | 686 |
|
687 | 687 |
srv := &Server{runtime: runtime} |
688 | 688 |
|
689 |
- container, err := runtime.Create( |
|
689 |
+ container, _, err := runtime.Create( |
|
690 | 690 |
&Config{ |
691 | 691 |
Image: GetTestImage(runtime).ID, |
692 | 692 |
Cmd: []string{"/bin/cat"}, |
... | ... |
@@ -728,7 +728,7 @@ func TestPostContainersRestart(t *testing.T) { |
728 | 728 |
|
729 | 729 |
srv := &Server{runtime: runtime} |
730 | 730 |
|
731 |
- container, err := runtime.Create( |
|
731 |
+ container, _, err := runtime.Create( |
|
732 | 732 |
&Config{ |
733 | 733 |
Image: GetTestImage(runtime).ID, |
734 | 734 |
Cmd: []string{"/bin/top"}, |
... | ... |
@@ -782,7 +782,7 @@ func TestPostContainersStart(t *testing.T) { |
782 | 782 |
|
783 | 783 |
srv := &Server{runtime: runtime} |
784 | 784 |
|
785 |
- container, err := runtime.Create( |
|
785 |
+ container, _, err := runtime.Create( |
|
786 | 786 |
&Config{ |
787 | 787 |
Image: GetTestImage(runtime).ID, |
788 | 788 |
Cmd: []string{"/bin/cat"}, |
... | ... |
@@ -834,7 +834,7 @@ func TestPostContainersStop(t *testing.T) { |
834 | 834 |
|
835 | 835 |
srv := &Server{runtime: runtime} |
836 | 836 |
|
837 |
- container, err := runtime.Create( |
|
837 |
+ container, _, err := runtime.Create( |
|
838 | 838 |
&Config{ |
839 | 839 |
Image: GetTestImage(runtime).ID, |
840 | 840 |
Cmd: []string{"/bin/top"}, |
... | ... |
@@ -881,7 +881,7 @@ func TestPostContainersWait(t *testing.T) { |
881 | 881 |
|
882 | 882 |
srv := &Server{runtime: runtime} |
883 | 883 |
|
884 |
- container, err := runtime.Create( |
|
884 |
+ container, _, err := runtime.Create( |
|
885 | 885 |
&Config{ |
886 | 886 |
Image: GetTestImage(runtime).ID, |
887 | 887 |
Cmd: []string{"/bin/sleep", "1"}, |
... | ... |
@@ -923,7 +923,7 @@ func TestPostContainersAttach(t *testing.T) { |
923 | 923 |
|
924 | 924 |
srv := &Server{runtime: runtime} |
925 | 925 |
|
926 |
- container, err := runtime.Create( |
|
926 |
+ container, _, err := runtime.Create( |
|
927 | 927 |
&Config{ |
928 | 928 |
Image: GetTestImage(runtime).ID, |
929 | 929 |
Cmd: []string{"/bin/cat"}, |
... | ... |
@@ -1012,7 +1012,7 @@ func TestPostContainersAttachStderr(t *testing.T) { |
1012 | 1012 |
|
1013 | 1013 |
srv := &Server{runtime: runtime} |
1014 | 1014 |
|
1015 |
- container, err := runtime.Create( |
|
1015 |
+ container, _, err := runtime.Create( |
|
1016 | 1016 |
&Config{ |
1017 | 1017 |
Image: GetTestImage(runtime).ID, |
1018 | 1018 |
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, |
... | ... |
@@ -1104,7 +1104,7 @@ func TestDeleteContainers(t *testing.T) { |
1104 | 1104 |
|
1105 | 1105 |
srv := &Server{runtime: runtime} |
1106 | 1106 |
|
1107 |
- container, err := runtime.Create(&Config{ |
|
1107 |
+ container, _, err := runtime.Create(&Config{ |
|
1108 | 1108 |
Image: GetTestImage(runtime).ID, |
1109 | 1109 |
Cmd: []string{"touch", "/test"}, |
1110 | 1110 |
}) |
... | ... |
@@ -1142,7 +1142,8 @@ func TestOptionsRoute(t *testing.T) { |
1142 | 1142 |
runtime := mkRuntime(t) |
1143 | 1143 |
defer nuke(runtime) |
1144 | 1144 |
|
1145 |
- srv := &Server{runtime: runtime, enableCors: true} |
|
1145 |
+ runtime.config.EnableCors = true |
|
1146 |
+ srv := &Server{runtime: runtime} |
|
1146 | 1147 |
|
1147 | 1148 |
r := httptest.NewRecorder() |
1148 | 1149 |
router, err := createRouter(srv, false) |
... | ... |
@@ -1165,7 +1166,8 @@ func TestGetEnabledCors(t *testing.T) { |
1165 | 1165 |
runtime := mkRuntime(t) |
1166 | 1166 |
defer nuke(runtime) |
1167 | 1167 |
|
1168 |
- srv := &Server{runtime: runtime, enableCors: true} |
|
1168 |
+ runtime.config.EnableCors = true |
|
1169 |
+ srv := &Server{runtime: runtime} |
|
1169 | 1170 |
|
1170 | 1171 |
r := httptest.NewRecorder() |
1171 | 1172 |
|
... | ... |
@@ -1292,7 +1294,7 @@ func TestPostContainersCopy(t *testing.T) { |
1292 | 1292 |
srv := &Server{runtime: runtime} |
1293 | 1293 |
|
1294 | 1294 |
// Create a container and remove a file |
1295 |
- container, err := runtime.Create( |
|
1295 |
+ container, _, err := runtime.Create( |
|
1296 | 1296 |
&Config{ |
1297 | 1297 |
Image: GetTestImage(runtime).ID, |
1298 | 1298 |
Cmd: []string{"touch", "/test.txt"}, |
... | ... |
@@ -187,6 +187,9 @@ func (b *buildFile) CmdCmd(args string) error { |
187 | 187 |
} |
188 | 188 |
|
189 | 189 |
func (b *buildFile) CmdExpose(args string) error { |
190 |
+ if strings.Contains(args, ":") { |
|
191 |
+ return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port") |
|
192 |
+ } |
|
190 | 193 |
ports := strings.Split(args, " ") |
191 | 194 |
b.config.PortSpecs = append(ports, b.config.PortSpecs...) |
192 | 195 |
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) |
... | ... |
@@ -332,7 +335,7 @@ func (b *buildFile) CmdAdd(args string) error { |
332 | 332 |
|
333 | 333 |
b.config.Image = b.image |
334 | 334 |
// Create the container and start it |
335 |
- container, err := b.runtime.Create(b.config) |
|
335 |
+ container, _, err := b.runtime.Create(b.config) |
|
336 | 336 |
if err != nil { |
337 | 337 |
return err |
338 | 338 |
} |
... | ... |
@@ -367,7 +370,7 @@ func (b *buildFile) run() (string, error) { |
367 | 367 |
b.config.Image = b.image |
368 | 368 |
|
369 | 369 |
// Create the container and start it |
370 |
- c, err := b.runtime.Create(b.config) |
|
370 |
+ c, _, err := b.runtime.Create(b.config) |
|
371 | 371 |
if err != nil { |
372 | 372 |
return "", err |
373 | 373 |
} |
... | ... |
@@ -430,7 +433,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { |
430 | 430 |
} |
431 | 431 |
} |
432 | 432 |
|
433 |
- container, err := b.runtime.Create(b.config) |
|
433 |
+ container, _, err := b.runtime.Create(b.config) |
|
434 | 434 |
if err != nil { |
435 | 435 |
return err |
436 | 436 |
} |
... | ... |
@@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { |
92 | 92 |
{"kill", "Kill a running container"}, |
93 | 93 |
{"login", "Register or Login to the docker registry server"}, |
94 | 94 |
{"logs", "Fetch the logs of a container"}, |
95 |
+ {"ls", "List links for containers"}, |
|
95 | 96 |
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, |
96 | 97 |
{"ps", "List containers"}, |
97 | 98 |
{"pull", "Pull an image or a repository from the docker registry server"}, |
... | ... |
@@ -504,7 +505,8 @@ func (cli *DockerCli) CmdStop(args ...string) error { |
504 | 504 |
v.Set("t", strconv.Itoa(*nSeconds)) |
505 | 505 |
|
506 | 506 |
for _, name := range cmd.Args() { |
507 |
- _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) |
|
507 |
+ encName := cleanName(name) |
|
508 |
+ _, _, err := cli.call("POST", "/containers/"+encName+"/stop?"+v.Encode(), nil) |
|
508 | 509 |
if err != nil { |
509 | 510 |
fmt.Fprintf(cli.err, "%s\n", err) |
510 | 511 |
} else { |
... | ... |
@@ -529,7 +531,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error { |
529 | 529 |
v.Set("t", strconv.Itoa(*nSeconds)) |
530 | 530 |
|
531 | 531 |
for _, name := range cmd.Args() { |
532 |
- _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) |
|
532 |
+ encName := cleanName(name) |
|
533 |
+ _, _, err := cli.call("POST", "/containers/"+encName+"/restart?"+v.Encode(), nil) |
|
533 | 534 |
if err != nil { |
534 | 535 |
fmt.Fprintf(cli.err, "%s\n", err) |
535 | 536 |
} else { |
... | ... |
@@ -605,7 +608,8 @@ func (cli *DockerCli) CmdStart(args ...string) error { |
605 | 605 |
|
606 | 606 |
var encounteredError error |
607 | 607 |
for _, name := range cmd.Args() { |
608 |
- _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) |
|
608 |
+ encName := cleanName(name) |
|
609 |
+ _, _, err := cli.call("POST", "/containers/"+encName+"/start", nil) |
|
609 | 610 |
if err != nil { |
610 | 611 |
if !*attach || !*openStdin { |
611 | 612 |
fmt.Fprintf(cli.err, "%s\n", err) |
... | ... |
@@ -811,6 +815,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { |
811 | 811 |
func (cli *DockerCli) CmdRm(args ...string) error { |
812 | 812 |
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers") |
813 | 813 |
v := cmd.Bool("v", false, "Remove the volumes associated to the container") |
814 |
+ link := cmd.Bool("link", false, "Remove the specified link and not the underlying container") |
|
815 |
+ |
|
814 | 816 |
if err := cmd.Parse(args); err != nil { |
815 | 817 |
return nil |
816 | 818 |
} |
... | ... |
@@ -822,8 +828,12 @@ func (cli *DockerCli) CmdRm(args ...string) error { |
822 | 822 |
if *v { |
823 | 823 |
val.Set("v", "1") |
824 | 824 |
} |
825 |
+ if *link { |
|
826 |
+ val.Set("link", "1") |
|
827 |
+ } |
|
825 | 828 |
for _, name := range cmd.Args() { |
826 |
- _, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) |
|
829 |
+ encName := cleanName(name) |
|
830 |
+ _, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil) |
|
827 | 831 |
if err != nil { |
828 | 832 |
fmt.Fprintf(cli.err, "%s\n", err) |
829 | 833 |
} else { |
... | ... |
@@ -845,7 +855,8 @@ func (cli *DockerCli) CmdKill(args ...string) error { |
845 | 845 |
} |
846 | 846 |
|
847 | 847 |
for _, name := range args { |
848 |
- _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil) |
|
848 |
+ encName := cleanName(name) |
|
849 |
+ _, _, err := cli.call("POST", "/containers/"+encName+"/kill", nil) |
|
849 | 850 |
if err != nil { |
850 | 851 |
fmt.Fprintf(cli.err, "%s\n", err) |
851 | 852 |
} else { |
... | ... |
@@ -1088,10 +1099,10 @@ func (cli *DockerCli) CmdImages(args ...string) error { |
1088 | 1088 |
func displayablePorts(ports []APIPort) string { |
1089 | 1089 |
result := []string{} |
1090 | 1090 |
for _, port := range ports { |
1091 |
- if port.Type == "tcp" { |
|
1092 |
- result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort)) |
|
1091 |
+ if port.IP == "" { |
|
1092 |
+ result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type)) |
|
1093 | 1093 |
} else { |
1094 |
- result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type)) |
|
1094 |
+ result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)) |
|
1095 | 1095 |
} |
1096 | 1096 |
} |
1097 | 1097 |
sort.Strings(result) |
... | ... |
@@ -1144,7 +1155,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { |
1144 | 1144 |
} |
1145 | 1145 |
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
1146 | 1146 |
if !*quiet { |
1147 |
- fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") |
|
1147 |
+ fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") |
|
1148 | 1148 |
if *size { |
1149 | 1149 |
fmt.Fprintln(w, "\tSIZE") |
1150 | 1150 |
} else { |
... | ... |
@@ -1153,11 +1164,16 @@ func (cli *DockerCli) CmdPs(args ...string) error { |
1153 | 1153 |
} |
1154 | 1154 |
|
1155 | 1155 |
for _, out := range outs { |
1156 |
+ for i := 0; i < len(out.Names); i++ { |
|
1157 |
+ out.Names[i] = utils.Trunc(out.Names[i], 10) |
|
1158 |
+ } |
|
1159 |
+ |
|
1160 |
+ names := strings.Join(out.Names, ",") |
|
1156 | 1161 |
if !*quiet { |
1157 | 1162 |
if *noTrunc { |
1158 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) |
|
1163 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) |
|
1159 | 1164 |
} else { |
1160 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) |
|
1165 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) |
|
1161 | 1166 |
} |
1162 | 1167 |
if *size { |
1163 | 1168 |
if out.SizeRootFs > 0 { |
... | ... |
@@ -1183,6 +1199,64 @@ func (cli *DockerCli) CmdPs(args ...string) error { |
1183 | 1183 |
return nil |
1184 | 1184 |
} |
1185 | 1185 |
|
1186 |
+func (cli *DockerCli) CmdLs(args ...string) error { |
|
1187 |
+ cmd := Subcmd("ls", "", "List links for containers") |
|
1188 |
+ flAll := cmd.Bool("a", false, "Show all links") |
|
1189 |
+ |
|
1190 |
+ if err := cmd.Parse(args); err != nil { |
|
1191 |
+ return nil |
|
1192 |
+ } |
|
1193 |
+ v := url.Values{} |
|
1194 |
+ if *flAll { |
|
1195 |
+ v.Set("all", "1") |
|
1196 |
+ } |
|
1197 |
+ |
|
1198 |
+ body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil) |
|
1199 |
+ if err != nil { |
|
1200 |
+ return err |
|
1201 |
+ } |
|
1202 |
+ var links []APILink |
|
1203 |
+ if err := json.Unmarshal(body, &links); err != nil { |
|
1204 |
+ return err |
|
1205 |
+ } |
|
1206 |
+ |
|
1207 |
+ w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
1208 |
+ fmt.Fprintf(w, "NAME\tID\tIMAGE") |
|
1209 |
+ fmt.Fprintf(w, "\n") |
|
1210 |
+ |
|
1211 |
+ sortLinks(links, func(i, j APILink) bool { |
|
1212 |
+ return len(i.Path) < len(j.Path) |
|
1213 |
+ }) |
|
1214 |
+ for _, link := range links { |
|
1215 |
+ fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image) |
|
1216 |
+ fmt.Fprintf(w, "\n") |
|
1217 |
+ } |
|
1218 |
+ w.Flush() |
|
1219 |
+ |
|
1220 |
+ return nil |
|
1221 |
+} |
|
1222 |
+ |
|
1223 |
+func (cli *DockerCli) CmdLink(args ...string) error { |
|
1224 |
+ cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name") |
|
1225 |
+ if err := cmd.Parse(args); err != nil { |
|
1226 |
+ return nil |
|
1227 |
+ } |
|
1228 |
+ if cmd.NArg() != 2 { |
|
1229 |
+ cmd.Usage() |
|
1230 |
+ return nil |
|
1231 |
+ } |
|
1232 |
+ body := map[string]string{ |
|
1233 |
+ "currentName": cmd.Arg(0), |
|
1234 |
+ "newName": cmd.Arg(1), |
|
1235 |
+ } |
|
1236 |
+ |
|
1237 |
+ _, _, err := cli.call("POST", "/containers/link", body) |
|
1238 |
+ if err != nil { |
|
1239 |
+ return err |
|
1240 |
+ } |
|
1241 |
+ return nil |
|
1242 |
+} |
|
1243 |
+ |
|
1186 | 1244 |
func (cli *DockerCli) CmdCommit(args ...string) error { |
1187 | 1245 |
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") |
1188 | 1246 |
flComment := cmd.String("m", "", "Commit message") |
... | ... |
@@ -1300,8 +1374,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error { |
1300 | 1300 |
cmd.Usage() |
1301 | 1301 |
return nil |
1302 | 1302 |
} |
1303 |
+ name := cleanName(cmd.Arg(0)) |
|
1303 | 1304 |
|
1304 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil { |
|
1305 |
+ if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil { |
|
1305 | 1306 |
return err |
1306 | 1307 |
} |
1307 | 1308 |
return nil |
... | ... |
@@ -1318,8 +1393,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error { |
1318 | 1318 |
cmd.Usage() |
1319 | 1319 |
return nil |
1320 | 1320 |
} |
1321 |
- |
|
1322 |
- body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) |
|
1321 |
+ name := cmd.Arg(0) |
|
1322 |
+ name = cleanName(name) |
|
1323 |
+ body, _, err := cli.call("GET", "/containers/"+name+"/json", nil) |
|
1323 | 1324 |
if err != nil { |
1324 | 1325 |
return err |
1325 | 1326 |
} |
... | ... |
@@ -2028,6 +2104,10 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { |
2028 | 2028 |
return c.State.Running, c.State.ExitCode, nil |
2029 | 2029 |
} |
2030 | 2030 |
|
2031 |
+func cleanName(name string) string { |
|
2032 |
+ return strings.Replace(name, "/", "%252F", -1) |
|
2033 |
+} |
|
2034 |
+ |
|
2031 | 2035 |
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { |
2032 | 2036 |
var ( |
2033 | 2037 |
isTerminal = false |
2034 | 2038 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
0 |
+package docker |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "net" |
|
4 |
+) |
|
5 |
+ |
|
6 |
+type DaemonConfig struct { |
|
7 |
+ Pidfile string |
|
8 |
+ GraphPath string |
|
9 |
+ ProtoAddresses []string |
|
10 |
+ AutoRestart bool |
|
11 |
+ EnableCors bool |
|
12 |
+ Dns []string |
|
13 |
+ EnableIptables bool |
|
14 |
+ BridgeIface string |
|
15 |
+ DefaultIp net.IP |
|
16 |
+} |
... | ... |
@@ -59,6 +59,8 @@ type Container struct { |
59 | 59 |
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. |
60 | 60 |
// Easier than migrating older container configs :) |
61 | 61 |
VolumesRW map[string]bool |
62 |
+ |
|
63 |
+ activeLinks map[string]*Link |
|
62 | 64 |
} |
63 | 65 |
|
64 | 66 |
type Config struct { |
... | ... |
@@ -71,7 +73,8 @@ type Config struct { |
71 | 71 |
AttachStdin bool |
72 | 72 |
AttachStdout bool |
73 | 73 |
AttachStderr bool |
74 |
- PortSpecs []string |
|
74 |
+ PortSpecs []string // Deprecated - Can be in the format of 8080/tcp |
|
75 |
+ ExposedPorts map[Port]struct{} |
|
75 | 76 |
Tty bool // Attach standard streams to a tty, including stdin if it is not closed. |
76 | 77 |
OpenStdin bool // Open stdin |
77 | 78 |
StdinOnce bool // If true, close stdin after the 1 attached client disconnects. |
... | ... |
@@ -91,6 +94,8 @@ type HostConfig struct { |
91 | 91 |
Binds []string |
92 | 92 |
ContainerIDFile string |
93 | 93 |
LxcConf []KeyValuePair |
94 |
+ PortBindings map[Port][]PortBinding |
|
95 |
+ Links []string |
|
94 | 96 |
} |
95 | 97 |
|
96 | 98 |
type BindMap struct { |
... | ... |
@@ -113,6 +118,34 @@ type KeyValuePair struct { |
113 | 113 |
Value string |
114 | 114 |
} |
115 | 115 |
|
116 |
+type PortBinding struct { |
|
117 |
+ HostIp string |
|
118 |
+ HostPort string |
|
119 |
+} |
|
120 |
+ |
|
121 |
+// 80/tcp |
|
122 |
+type Port string |
|
123 |
+ |
|
124 |
+func (p Port) Proto() string { |
|
125 |
+ return strings.Split(string(p), "/")[1] |
|
126 |
+} |
|
127 |
+ |
|
128 |
+func (p Port) Port() string { |
|
129 |
+ return strings.Split(string(p), "/")[0] |
|
130 |
+} |
|
131 |
+ |
|
132 |
+func (p Port) Int() int { |
|
133 |
+ i, err := parsePort(p.Port()) |
|
134 |
+ if err != nil { |
|
135 |
+ panic(err) |
|
136 |
+ } |
|
137 |
+ return i |
|
138 |
+} |
|
139 |
+ |
|
140 |
+func NewPort(proto, port string) Port { |
|
141 |
+ return Port(fmt.Sprintf("%s/%s", port, proto)) |
|
142 |
+} |
|
143 |
+ |
|
116 | 144 |
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { |
117 | 145 |
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") |
118 | 146 |
if os.Getenv("TEST") != "" { |
... | ... |
@@ -142,8 +175,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
142 | 142 |
|
143 | 143 |
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") |
144 | 144 |
|
145 |
- var flPorts ListOpts |
|
146 |
- cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") |
|
145 |
+ var flPublish ListOpts |
|
146 |
+ cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") |
|
147 |
+ |
|
148 |
+ var flExpose ListOpts |
|
149 |
+ cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") |
|
147 | 150 |
|
148 | 151 |
var flEnv ListOpts |
149 | 152 |
cmd.Var(&flEnv, "e", "Set environment variables") |
... | ... |
@@ -162,6 +198,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
162 | 162 |
var flLxcOpts ListOpts |
163 | 163 |
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") |
164 | 164 |
|
165 |
+ var flLinks ListOpts |
|
166 |
+ cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)") |
|
167 |
+ |
|
165 | 168 |
if err := cmd.Parse(args); err != nil { |
166 | 169 |
return nil, nil, cmd, err |
167 | 170 |
} |
... | ... |
@@ -230,10 +269,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
230 | 230 |
hostname = parts[0] |
231 | 231 |
domainname = parts[1] |
232 | 232 |
} |
233 |
+ |
|
234 |
+ ports, portBindings, err := parsePortSpecs(flPublish) |
|
235 |
+ if err != nil { |
|
236 |
+ return nil, nil, cmd, err |
|
237 |
+ } |
|
238 |
+ |
|
239 |
+ // Merge in exposed ports to the map of published ports |
|
240 |
+ for _, e := range flExpose { |
|
241 |
+ if strings.Contains(e, ":") { |
|
242 |
+ return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e) |
|
243 |
+ } |
|
244 |
+ p := NewPort(splitProtoPort(e)) |
|
245 |
+ if _, exists := ports[p]; !exists { |
|
246 |
+ ports[p] = struct{}{} |
|
247 |
+ } |
|
248 |
+ } |
|
249 |
+ |
|
233 | 250 |
config := &Config{ |
234 |
- Hostname: hostname, |
|
251 |
+ Hostname: *flHostname, |
|
235 | 252 |
Domainname: domainname, |
236 |
- PortSpecs: flPorts, |
|
253 |
+ PortSpecs: nil, // Deprecated |
|
254 |
+ ExposedPorts: ports, |
|
237 | 255 |
User: *flUser, |
238 | 256 |
Tty: *flTty, |
239 | 257 |
NetworkDisabled: !*flNetwork, |
... | ... |
@@ -253,10 +310,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
253 | 253 |
Privileged: *flPrivileged, |
254 | 254 |
WorkingDir: *flWorkingDir, |
255 | 255 |
} |
256 |
+ |
|
256 | 257 |
hostConfig := &HostConfig{ |
257 | 258 |
Binds: binds, |
258 | 259 |
ContainerIDFile: *flContainerIDFile, |
259 | 260 |
LxcConf: lxcConf, |
261 |
+ PortBindings: portBindings, |
|
262 |
+ Links: flLinks, |
|
260 | 263 |
} |
261 | 264 |
|
262 | 265 |
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { |
... | ... |
@@ -271,36 +331,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
271 | 271 |
return config, hostConfig, cmd, nil |
272 | 272 |
} |
273 | 273 |
|
274 |
-type PortMapping map[string]string |
|
274 |
+type PortMapping map[string]string // Deprecated |
|
275 | 275 |
|
276 | 276 |
type NetworkSettings struct { |
277 | 277 |
IPAddress string |
278 | 278 |
IPPrefixLen int |
279 | 279 |
Gateway string |
280 | 280 |
Bridge string |
281 |
- PortMapping map[string]PortMapping |
|
281 |
+ PortMapping map[string]PortMapping // Deprecated |
|
282 |
+ Ports map[Port][]PortBinding |
|
282 | 283 |
} |
283 | 284 |
|
284 |
-// returns a more easy to process description of the port mapping defined in the settings |
|
285 | 285 |
func (settings *NetworkSettings) PortMappingAPI() []APIPort { |
286 | 286 |
var mapping []APIPort |
287 |
- for private, public := range settings.PortMapping["Tcp"] { |
|
288 |
- pubint, _ := strconv.ParseInt(public, 0, 0) |
|
289 |
- privint, _ := strconv.ParseInt(private, 0, 0) |
|
290 |
- mapping = append(mapping, APIPort{ |
|
291 |
- PrivatePort: privint, |
|
292 |
- PublicPort: pubint, |
|
293 |
- Type: "tcp", |
|
294 |
- }) |
|
295 |
- } |
|
296 |
- for private, public := range settings.PortMapping["Udp"] { |
|
297 |
- pubint, _ := strconv.ParseInt(public, 0, 0) |
|
298 |
- privint, _ := strconv.ParseInt(private, 0, 0) |
|
299 |
- mapping = append(mapping, APIPort{ |
|
300 |
- PrivatePort: privint, |
|
301 |
- PublicPort: pubint, |
|
302 |
- Type: "udp", |
|
303 |
- }) |
|
287 |
+ for port, bindings := range settings.Ports { |
|
288 |
+ p, _ := parsePort(port.Port()) |
|
289 |
+ if len(bindings) == 0 { |
|
290 |
+ mapping = append(mapping, APIPort{ |
|
291 |
+ PublicPort: int64(p), |
|
292 |
+ Type: port.Proto(), |
|
293 |
+ }) |
|
294 |
+ continue |
|
295 |
+ } |
|
296 |
+ for _, binding := range bindings { |
|
297 |
+ p, _ := parsePort(port.Port()) |
|
298 |
+ h, _ := parsePort(binding.HostPort) |
|
299 |
+ mapping = append(mapping, APIPort{ |
|
300 |
+ PrivatePort: int64(p), |
|
301 |
+ PublicPort: int64(h), |
|
302 |
+ Type: port.Proto(), |
|
303 |
+ IP: binding.HostIp, |
|
304 |
+ }) |
|
305 |
+ } |
|
304 | 306 |
} |
305 | 307 |
return mapping |
306 | 308 |
} |
... | ... |
@@ -602,7 +664,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { |
602 | 602 |
if container.runtime.networkManager.disabled { |
603 | 603 |
container.Config.NetworkDisabled = true |
604 | 604 |
} else { |
605 |
- if err := container.allocateNetwork(); err != nil { |
|
605 |
+ if err := container.allocateNetwork(hostConfig); err != nil { |
|
606 | 606 |
return err |
607 | 607 |
} |
608 | 608 |
} |
... | ... |
@@ -792,6 +854,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { |
792 | 792 |
"-e", "container=lxc", |
793 | 793 |
"-e", "HOSTNAME="+container.Config.Hostname, |
794 | 794 |
) |
795 |
+ |
|
796 |
+ // Init any links between the parent and children |
|
797 |
+ runtime := container.runtime |
|
798 |
+ |
|
799 |
+ children, err := runtime.Children(fmt.Sprintf("/%s", container.ID)) |
|
800 |
+ if err != nil { |
|
801 |
+ return err |
|
802 |
+ } |
|
803 |
+ |
|
804 |
+ if len(children) > 0 { |
|
805 |
+ container.activeLinks = make(map[string]*Link, len(children)) |
|
806 |
+ |
|
807 |
+ // If we encounter an error make sure that we rollback any network |
|
808 |
+ // config and ip table changes |
|
809 |
+ rollback := func() { |
|
810 |
+ for _, link := range container.activeLinks { |
|
811 |
+ link.Disable() |
|
812 |
+ } |
|
813 |
+ container.activeLinks = nil |
|
814 |
+ } |
|
815 |
+ |
|
816 |
+ for p, child := range children { |
|
817 |
+ link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) |
|
818 |
+ if err != nil { |
|
819 |
+ rollback() |
|
820 |
+ return err |
|
821 |
+ } |
|
822 |
+ |
|
823 |
+ container.activeLinks[link.Alias()] = link |
|
824 |
+ if err := link.Enable(); err != nil { |
|
825 |
+ rollback() |
|
826 |
+ return err |
|
827 |
+ } |
|
828 |
+ |
|
829 |
+ for _, envVar := range link.ToEnv() { |
|
830 |
+ params = append(params, "-e", envVar) |
|
831 |
+ } |
|
832 |
+ } |
|
833 |
+ } |
|
834 |
+ |
|
795 | 835 |
if container.Config.WorkingDir != "" { |
796 | 836 |
workingDir := path.Clean(container.Config.WorkingDir) |
797 | 837 |
utils.Debugf("[working dir] working dir is %s", workingDir) |
... | ... |
@@ -925,7 +1027,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { |
925 | 925 |
return utils.NewBufReader(reader), nil |
926 | 926 |
} |
927 | 927 |
|
928 |
-func (container *Container) allocateNetwork() error { |
|
928 |
+func (container *Container) allocateNetwork(hostConfig *HostConfig) error { |
|
929 | 929 |
if container.Config.NetworkDisabled { |
930 | 930 |
return nil |
931 | 931 |
} |
... | ... |
@@ -952,36 +1054,59 @@ func (container *Container) allocateNetwork() error { |
952 | 952 |
} |
953 | 953 |
} |
954 | 954 |
|
955 |
- var portSpecs []string |
|
955 |
+ if container.Config.PortSpecs != nil { |
|
956 |
+ utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", ")) |
|
957 |
+ if err := migratePortMappings(container.Config); err != nil { |
|
958 |
+ return err |
|
959 |
+ } |
|
960 |
+ container.Config.PortSpecs = nil |
|
961 |
+ } |
|
962 |
+ |
|
963 |
+ portSpecs := make(map[Port]struct{}) |
|
964 |
+ bindings := make(map[Port][]PortBinding) |
|
965 |
+ |
|
956 | 966 |
if !container.State.Ghost { |
957 |
- portSpecs = container.Config.PortSpecs |
|
958 |
- } else { |
|
959 |
- for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { |
|
960 |
- portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) |
|
967 |
+ if container.Config.ExposedPorts != nil { |
|
968 |
+ portSpecs = container.Config.ExposedPorts |
|
961 | 969 |
} |
962 |
- for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { |
|
963 |
- portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) |
|
970 |
+ if hostConfig.PortBindings != nil { |
|
971 |
+ bindings = hostConfig.PortBindings |
|
972 |
+ } |
|
973 |
+ } else { |
|
974 |
+ if container.NetworkSettings.Ports != nil { |
|
975 |
+ for port, binding := range container.NetworkSettings.Ports { |
|
976 |
+ portSpecs[port] = struct{}{} |
|
977 |
+ bindings[port] = binding |
|
978 |
+ } |
|
964 | 979 |
} |
965 | 980 |
} |
966 | 981 |
|
967 |
- container.NetworkSettings.PortMapping = make(map[string]PortMapping) |
|
968 |
- container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping) |
|
969 |
- container.NetworkSettings.PortMapping["Udp"] = make(PortMapping) |
|
970 |
- for _, spec := range portSpecs { |
|
971 |
- nat, err := iface.AllocatePort(spec) |
|
972 |
- if err != nil { |
|
973 |
- iface.Release() |
|
974 |
- return err |
|
982 |
+ container.NetworkSettings.PortMapping = nil |
|
983 |
+ |
|
984 |
+ for port := range portSpecs { |
|
985 |
+ binding := bindings[port] |
|
986 |
+ for i := 0; i < len(binding); i++ { |
|
987 |
+ b := binding[i] |
|
988 |
+ nat, err := iface.AllocatePort(port, b) |
|
989 |
+ if err != nil { |
|
990 |
+ iface.Release() |
|
991 |
+ return err |
|
992 |
+ } |
|
993 |
+ utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort) |
|
994 |
+ binding[i] = nat.Binding |
|
975 | 995 |
} |
976 |
- proto := strings.Title(nat.Proto) |
|
977 |
- backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend) |
|
978 |
- container.NetworkSettings.PortMapping[proto][backend] = frontend |
|
996 |
+ bindings[port] = binding |
|
979 | 997 |
} |
998 |
+ container.SaveHostConfig(hostConfig) |
|
999 |
+ |
|
1000 |
+ container.NetworkSettings.Ports = bindings |
|
980 | 1001 |
container.network = iface |
1002 |
+ |
|
981 | 1003 |
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface |
982 | 1004 |
container.NetworkSettings.IPAddress = iface.IPNet.IP.String() |
983 | 1005 |
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() |
984 | 1006 |
container.NetworkSettings.Gateway = iface.Gateway.String() |
1007 |
+ |
|
985 | 1008 |
return nil |
986 | 1009 |
} |
987 | 1010 |
|
... | ... |
@@ -1064,6 +1189,14 @@ func (container *Container) monitor(hostConfig *HostConfig) { |
1064 | 1064 |
|
1065 | 1065 |
func (container *Container) cleanup() { |
1066 | 1066 |
container.releaseNetwork() |
1067 |
+ |
|
1068 |
+ // Disable all active links |
|
1069 |
+ if container.activeLinks != nil { |
|
1070 |
+ for _, link := range container.activeLinks { |
|
1071 |
+ link.Disable() |
|
1072 |
+ } |
|
1073 |
+ } |
|
1074 |
+ |
|
1067 | 1075 |
if container.Config.OpenStdin { |
1068 | 1076 |
if err := container.stdin.Close(); err != nil { |
1069 | 1077 |
utils.Errorf("%s: Error close stdin: %s", container.ID, err) |
... | ... |
@@ -1345,3 +1478,9 @@ func (container *Container) Copy(resource string) (Archive, error) { |
1345 | 1345 |
} |
1346 | 1346 |
return TarFilter(basePath, Uncompressed, filter) |
1347 | 1347 |
} |
1348 |
+ |
|
1349 |
+// Returns true if the container exposes a certain port |
|
1350 |
+func (container *Container) Exposes(p Port) bool { |
|
1351 |
+ _, exists := container.Config.ExposedPorts[p] |
|
1352 |
+ return exists |
|
1353 |
+} |
... | ... |
@@ -18,7 +18,7 @@ import ( |
18 | 18 |
func TestIDFormat(t *testing.T) { |
19 | 19 |
runtime := mkRuntime(t) |
20 | 20 |
defer nuke(runtime) |
21 |
- container1, err := runtime.Create( |
|
21 |
+ container1, _, err := runtime.Create( |
|
22 | 22 |
&Config{ |
23 | 23 |
Image: GetTestImage(runtime).ID, |
24 | 24 |
Cmd: []string{"/bin/sh", "-c", "echo hello world"}, |
... | ... |
@@ -388,7 +388,7 @@ func TestRun(t *testing.T) { |
388 | 388 |
func TestOutput(t *testing.T) { |
389 | 389 |
runtime := mkRuntime(t) |
390 | 390 |
defer nuke(runtime) |
391 |
- container, err := runtime.Create( |
|
391 |
+ container, _, err := runtime.Create( |
|
392 | 392 |
&Config{ |
393 | 393 |
Image: GetTestImage(runtime).ID, |
394 | 394 |
Cmd: []string{"echo", "-n", "foobar"}, |
... | ... |
@@ -411,7 +411,7 @@ func TestKillDifferentUser(t *testing.T) { |
411 | 411 |
runtime := mkRuntime(t) |
412 | 412 |
defer nuke(runtime) |
413 | 413 |
|
414 |
- container, err := runtime.Create(&Config{ |
|
414 |
+ container, _, err := runtime.Create(&Config{ |
|
415 | 415 |
Image: GetTestImage(runtime).ID, |
416 | 416 |
Cmd: []string{"cat"}, |
417 | 417 |
OpenStdin: true, |
... | ... |
@@ -471,7 +471,7 @@ func TestCreateVolume(t *testing.T) { |
471 | 471 |
if err != nil { |
472 | 472 |
t.Fatal(err) |
473 | 473 |
} |
474 |
- c, err := runtime.Create(config) |
|
474 |
+ c, _, err := runtime.Create(config) |
|
475 | 475 |
if err != nil { |
476 | 476 |
t.Fatal(err) |
477 | 477 |
} |
... | ... |
@@ -486,7 +486,7 @@ func TestCreateVolume(t *testing.T) { |
486 | 486 |
func TestKill(t *testing.T) { |
487 | 487 |
runtime := mkRuntime(t) |
488 | 488 |
defer nuke(runtime) |
489 |
- container, err := runtime.Create(&Config{ |
|
489 |
+ container, _, err := runtime.Create(&Config{ |
|
490 | 490 |
Image: GetTestImage(runtime).ID, |
491 | 491 |
Cmd: []string{"sleep", "2"}, |
492 | 492 |
}, |
... | ... |
@@ -530,7 +530,7 @@ func TestExitCode(t *testing.T) { |
530 | 530 |
runtime := mkRuntime(t) |
531 | 531 |
defer nuke(runtime) |
532 | 532 |
|
533 |
- trueContainer, err := runtime.Create(&Config{ |
|
533 |
+ trueContainer, _, err := runtime.Create(&Config{ |
|
534 | 534 |
Image: GetTestImage(runtime).ID, |
535 | 535 |
Cmd: []string{"/bin/true", ""}, |
536 | 536 |
}) |
... | ... |
@@ -545,7 +545,7 @@ func TestExitCode(t *testing.T) { |
545 | 545 |
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode) |
546 | 546 |
} |
547 | 547 |
|
548 |
- falseContainer, err := runtime.Create(&Config{ |
|
548 |
+ falseContainer, _, err := runtime.Create(&Config{ |
|
549 | 549 |
Image: GetTestImage(runtime).ID, |
550 | 550 |
Cmd: []string{"/bin/false", ""}, |
551 | 551 |
}) |
... | ... |
@@ -564,7 +564,7 @@ func TestExitCode(t *testing.T) { |
564 | 564 |
func TestRestart(t *testing.T) { |
565 | 565 |
runtime := mkRuntime(t) |
566 | 566 |
defer nuke(runtime) |
567 |
- container, err := runtime.Create(&Config{ |
|
567 |
+ container, _, err := runtime.Create(&Config{ |
|
568 | 568 |
Image: GetTestImage(runtime).ID, |
569 | 569 |
Cmd: []string{"echo", "-n", "foobar"}, |
570 | 570 |
}, |
... | ... |
@@ -594,7 +594,7 @@ func TestRestart(t *testing.T) { |
594 | 594 |
func TestRestartStdin(t *testing.T) { |
595 | 595 |
runtime := mkRuntime(t) |
596 | 596 |
defer nuke(runtime) |
597 |
- container, err := runtime.Create(&Config{ |
|
597 |
+ container, _, err := runtime.Create(&Config{ |
|
598 | 598 |
Image: GetTestImage(runtime).ID, |
599 | 599 |
Cmd: []string{"cat"}, |
600 | 600 |
|
... | ... |
@@ -672,7 +672,7 @@ func TestUser(t *testing.T) { |
672 | 672 |
defer nuke(runtime) |
673 | 673 |
|
674 | 674 |
// Default user must be root |
675 |
- container, err := runtime.Create(&Config{ |
|
675 |
+ container, _, err := runtime.Create(&Config{ |
|
676 | 676 |
Image: GetTestImage(runtime).ID, |
677 | 677 |
Cmd: []string{"id"}, |
678 | 678 |
}, |
... | ... |
@@ -690,7 +690,7 @@ func TestUser(t *testing.T) { |
690 | 690 |
} |
691 | 691 |
|
692 | 692 |
// Set a username |
693 |
- container, err = runtime.Create(&Config{ |
|
693 |
+ container, _, err = runtime.Create(&Config{ |
|
694 | 694 |
Image: GetTestImage(runtime).ID, |
695 | 695 |
Cmd: []string{"id"}, |
696 | 696 |
|
... | ... |
@@ -710,7 +710,7 @@ func TestUser(t *testing.T) { |
710 | 710 |
} |
711 | 711 |
|
712 | 712 |
// Set a UID |
713 |
- container, err = runtime.Create(&Config{ |
|
713 |
+ container, _, err = runtime.Create(&Config{ |
|
714 | 714 |
Image: GetTestImage(runtime).ID, |
715 | 715 |
Cmd: []string{"id"}, |
716 | 716 |
|
... | ... |
@@ -730,7 +730,7 @@ func TestUser(t *testing.T) { |
730 | 730 |
} |
731 | 731 |
|
732 | 732 |
// Set a different user by uid |
733 |
- container, err = runtime.Create(&Config{ |
|
733 |
+ container, _, err = runtime.Create(&Config{ |
|
734 | 734 |
Image: GetTestImage(runtime).ID, |
735 | 735 |
Cmd: []string{"id"}, |
736 | 736 |
|
... | ... |
@@ -752,7 +752,7 @@ func TestUser(t *testing.T) { |
752 | 752 |
} |
753 | 753 |
|
754 | 754 |
// Set a different user by username |
755 |
- container, err = runtime.Create(&Config{ |
|
755 |
+ container, _, err = runtime.Create(&Config{ |
|
756 | 756 |
Image: GetTestImage(runtime).ID, |
757 | 757 |
Cmd: []string{"id"}, |
758 | 758 |
|
... | ... |
@@ -772,7 +772,7 @@ func TestUser(t *testing.T) { |
772 | 772 |
} |
773 | 773 |
|
774 | 774 |
// Test an wrong username |
775 |
- container, err = runtime.Create(&Config{ |
|
775 |
+ container, _, err = runtime.Create(&Config{ |
|
776 | 776 |
Image: GetTestImage(runtime).ID, |
777 | 777 |
Cmd: []string{"id"}, |
778 | 778 |
|
... | ... |
@@ -793,7 +793,7 @@ func TestMultipleContainers(t *testing.T) { |
793 | 793 |
runtime := mkRuntime(t) |
794 | 794 |
defer nuke(runtime) |
795 | 795 |
|
796 |
- container1, err := runtime.Create(&Config{ |
|
796 |
+ container1, _, err := runtime.Create(&Config{ |
|
797 | 797 |
Image: GetTestImage(runtime).ID, |
798 | 798 |
Cmd: []string{"sleep", "2"}, |
799 | 799 |
}, |
... | ... |
@@ -803,7 +803,7 @@ func TestMultipleContainers(t *testing.T) { |
803 | 803 |
} |
804 | 804 |
defer runtime.Destroy(container1) |
805 | 805 |
|
806 |
- container2, err := runtime.Create(&Config{ |
|
806 |
+ container2, _, err := runtime.Create(&Config{ |
|
807 | 807 |
Image: GetTestImage(runtime).ID, |
808 | 808 |
Cmd: []string{"sleep", "2"}, |
809 | 809 |
}, |
... | ... |
@@ -847,7 +847,7 @@ func TestMultipleContainers(t *testing.T) { |
847 | 847 |
func TestStdin(t *testing.T) { |
848 | 848 |
runtime := mkRuntime(t) |
849 | 849 |
defer nuke(runtime) |
850 |
- container, err := runtime.Create(&Config{ |
|
850 |
+ container, _, err := runtime.Create(&Config{ |
|
851 | 851 |
Image: GetTestImage(runtime).ID, |
852 | 852 |
Cmd: []string{"cat"}, |
853 | 853 |
|
... | ... |
@@ -892,7 +892,7 @@ func TestStdin(t *testing.T) { |
892 | 892 |
func TestTty(t *testing.T) { |
893 | 893 |
runtime := mkRuntime(t) |
894 | 894 |
defer nuke(runtime) |
895 |
- container, err := runtime.Create(&Config{ |
|
895 |
+ container, _, err := runtime.Create(&Config{ |
|
896 | 896 |
Image: GetTestImage(runtime).ID, |
897 | 897 |
Cmd: []string{"cat"}, |
898 | 898 |
|
... | ... |
@@ -937,7 +937,7 @@ func TestTty(t *testing.T) { |
937 | 937 |
func TestEnv(t *testing.T) { |
938 | 938 |
runtime := mkRuntime(t) |
939 | 939 |
defer nuke(runtime) |
940 |
- container, err := runtime.Create(&Config{ |
|
940 |
+ container, _, err := runtime.Create(&Config{ |
|
941 | 941 |
Image: GetTestImage(runtime).ID, |
942 | 942 |
Cmd: []string{"env"}, |
943 | 943 |
}, |
... | ... |
@@ -986,7 +986,7 @@ func TestEnv(t *testing.T) { |
986 | 986 |
func TestEntrypoint(t *testing.T) { |
987 | 987 |
runtime := mkRuntime(t) |
988 | 988 |
defer nuke(runtime) |
989 |
- container, err := runtime.Create( |
|
989 |
+ container, _, err := runtime.Create( |
|
990 | 990 |
&Config{ |
991 | 991 |
Image: GetTestImage(runtime).ID, |
992 | 992 |
Entrypoint: []string{"/bin/echo"}, |
... | ... |
@@ -1009,7 +1009,7 @@ func TestEntrypoint(t *testing.T) { |
1009 | 1009 |
func TestEntrypointNoCmd(t *testing.T) { |
1010 | 1010 |
runtime := mkRuntime(t) |
1011 | 1011 |
defer nuke(runtime) |
1012 |
- container, err := runtime.Create( |
|
1012 |
+ container, _, err := runtime.Create( |
|
1013 | 1013 |
&Config{ |
1014 | 1014 |
Image: GetTestImage(runtime).ID, |
1015 | 1015 |
Entrypoint: []string{"/bin/echo", "foobar"}, |
... | ... |
@@ -1060,7 +1060,7 @@ func TestLXCConfig(t *testing.T) { |
1060 | 1060 |
cpuMin := 100 |
1061 | 1061 |
cpuMax := 10000 |
1062 | 1062 |
cpu := cpuMin + rand.Intn(cpuMax-cpuMin) |
1063 |
- container, err := runtime.Create(&Config{ |
|
1063 |
+ container, _, err := runtime.Create(&Config{ |
|
1064 | 1064 |
Image: GetTestImage(runtime).ID, |
1065 | 1065 |
Cmd: []string{"/bin/true"}, |
1066 | 1066 |
|
... | ... |
@@ -1084,7 +1084,7 @@ func TestLXCConfig(t *testing.T) { |
1084 | 1084 |
func TestCustomLxcConfig(t *testing.T) { |
1085 | 1085 |
runtime := mkRuntime(t) |
1086 | 1086 |
defer nuke(runtime) |
1087 |
- container, err := runtime.Create(&Config{ |
|
1087 |
+ container, _, err := runtime.Create(&Config{ |
|
1088 | 1088 |
Image: GetTestImage(runtime).ID, |
1089 | 1089 |
Cmd: []string{"/bin/true"}, |
1090 | 1090 |
|
... | ... |
@@ -1115,7 +1115,7 @@ func BenchmarkRunSequencial(b *testing.B) { |
1115 | 1115 |
runtime := mkRuntime(b) |
1116 | 1116 |
defer nuke(runtime) |
1117 | 1117 |
for i := 0; i < b.N; i++ { |
1118 |
- container, err := runtime.Create(&Config{ |
|
1118 |
+ container, _, err := runtime.Create(&Config{ |
|
1119 | 1119 |
Image: GetTestImage(runtime).ID, |
1120 | 1120 |
Cmd: []string{"echo", "-n", "foo"}, |
1121 | 1121 |
}, |
... | ... |
@@ -1147,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) { |
1147 | 1147 |
complete := make(chan error) |
1148 | 1148 |
tasks = append(tasks, complete) |
1149 | 1149 |
go func(i int, complete chan error) { |
1150 |
- container, err := runtime.Create(&Config{ |
|
1150 |
+ container, _, err := runtime.Create(&Config{ |
|
1151 | 1151 |
Image: GetTestImage(runtime).ID, |
1152 | 1152 |
Cmd: []string{"echo", "-n", "foo"}, |
1153 | 1153 |
}, |
... | ... |
@@ -1297,7 +1297,7 @@ func TestBindMounts(t *testing.T) { |
1297 | 1297 |
func TestVolumesFromReadonlyMount(t *testing.T) { |
1298 | 1298 |
runtime := mkRuntime(t) |
1299 | 1299 |
defer nuke(runtime) |
1300 |
- container, err := runtime.Create( |
|
1300 |
+ container, _, err := runtime.Create( |
|
1301 | 1301 |
&Config{ |
1302 | 1302 |
Image: GetTestImage(runtime).ID, |
1303 | 1303 |
Cmd: []string{"/bin/echo", "-n", "foobar"}, |
... | ... |
@@ -1316,7 +1316,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { |
1316 | 1316 |
t.Fail() |
1317 | 1317 |
} |
1318 | 1318 |
|
1319 |
- container2, err := runtime.Create( |
|
1319 |
+ container2, _, err := runtime.Create( |
|
1320 | 1320 |
&Config{ |
1321 | 1321 |
Image: GetTestImage(runtime).ID, |
1322 | 1322 |
Cmd: []string{"/bin/echo", "-n", "foobar"}, |
... | ... |
@@ -1352,7 +1352,7 @@ func TestRestartWithVolumes(t *testing.T) { |
1352 | 1352 |
runtime := mkRuntime(t) |
1353 | 1353 |
defer nuke(runtime) |
1354 | 1354 |
|
1355 |
- container, err := runtime.Create(&Config{ |
|
1355 |
+ container, _, err := runtime.Create(&Config{ |
|
1356 | 1356 |
Image: GetTestImage(runtime).ID, |
1357 | 1357 |
Cmd: []string{"echo", "-n", "foobar"}, |
1358 | 1358 |
Volumes: map[string]struct{}{"/test": {}}, |
... | ... |
@@ -1395,7 +1395,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { |
1395 | 1395 |
runtime := mkRuntime(t) |
1396 | 1396 |
defer nuke(runtime) |
1397 | 1397 |
|
1398 |
- container, err := runtime.Create(&Config{ |
|
1398 |
+ container, _, err := runtime.Create(&Config{ |
|
1399 | 1399 |
Image: GetTestImage(runtime).ID, |
1400 | 1400 |
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, |
1401 | 1401 |
Volumes: map[string]struct{}{"/test": {}}, |
... | ... |
@@ -1422,7 +1422,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { |
1422 | 1422 |
t.Fail() |
1423 | 1423 |
} |
1424 | 1424 |
|
1425 |
- container2, err := runtime.Create( |
|
1425 |
+ container2, _, err := runtime.Create( |
|
1426 | 1426 |
&Config{ |
1427 | 1427 |
Image: GetTestImage(runtime).ID, |
1428 | 1428 |
Cmd: []string{"cat", "/test/foo"}, |
... | ... |
@@ -1463,7 +1463,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { |
1463 | 1463 |
if err != nil { |
1464 | 1464 |
t.Fatal(err) |
1465 | 1465 |
} |
1466 |
- c, err := runtime.Create(config) |
|
1466 |
+ c, _, err := runtime.Create(config) |
|
1467 | 1467 |
if err != nil { |
1468 | 1468 |
t.Fatal(err) |
1469 | 1469 |
} |
... | ... |
@@ -1529,7 +1529,7 @@ func TestMultipleVolumesFrom(t *testing.T) { |
1529 | 1529 |
runtime := mkRuntime(t) |
1530 | 1530 |
defer nuke(runtime) |
1531 | 1531 |
|
1532 |
- container, err := runtime.Create(&Config{ |
|
1532 |
+ container, _, err := runtime.Create(&Config{ |
|
1533 | 1533 |
Image: GetTestImage(runtime).ID, |
1534 | 1534 |
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, |
1535 | 1535 |
Volumes: map[string]struct{}{"/test": {}}, |
... | ... |
@@ -1556,7 +1556,7 @@ func TestMultipleVolumesFrom(t *testing.T) { |
1556 | 1556 |
t.Fail() |
1557 | 1557 |
} |
1558 | 1558 |
|
1559 |
- container2, err := runtime.Create( |
|
1559 |
+ container2, _, err := runtime.Create( |
|
1560 | 1560 |
&Config{ |
1561 | 1561 |
Image: GetTestImage(runtime).ID, |
1562 | 1562 |
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, |
... | ... |
@@ -1577,7 +1577,7 @@ func TestMultipleVolumesFrom(t *testing.T) { |
1577 | 1577 |
t.Fatal(err) |
1578 | 1578 |
} |
1579 | 1579 |
|
1580 |
- container3, err := runtime.Create( |
|
1580 |
+ container3, _, err := runtime.Create( |
|
1581 | 1581 |
&Config{ |
1582 | 1582 |
Image: GetTestImage(runtime).ID, |
1583 | 1583 |
Cmd: []string{"/bin/echo", "-n", "foobar"}, |
... | ... |
@@ -7,6 +7,7 @@ import ( |
7 | 7 |
"github.com/dotcloud/docker/utils" |
8 | 8 |
"io/ioutil" |
9 | 9 |
"log" |
10 |
+ "net" |
|
10 | 11 |
"os" |
11 | 12 |
"os/signal" |
12 | 13 |
"strconv" |
... | ... |
@@ -37,7 +38,11 @@ func main() { |
37 | 37 |
flDns := flag.String("dns", "", "Set custom dns servers") |
38 | 38 |
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} |
39 | 39 |
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") |
40 |
+ flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") |
|
41 |
+ flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") |
|
42 |
+ |
|
40 | 43 |
flag.Parse() |
44 |
+ |
|
41 | 45 |
if *flVersion { |
42 | 46 |
showVersion() |
43 | 47 |
return |
... | ... |
@@ -54,10 +59,9 @@ func main() { |
54 | 54 |
} |
55 | 55 |
} |
56 | 56 |
|
57 |
+ bridge := docker.DefaultNetworkBridge |
|
57 | 58 |
if *bridgeName != "" { |
58 |
- docker.NetworkBridgeIface = *bridgeName |
|
59 |
- } else { |
|
60 |
- docker.NetworkBridgeIface = docker.DefaultNetworkBridge |
|
59 |
+ bridge = *bridgeName |
|
61 | 60 |
} |
62 | 61 |
if *flDebug { |
63 | 62 |
os.Setenv("DEBUG", "1") |
... | ... |
@@ -69,7 +73,25 @@ func main() { |
69 | 69 |
flag.Usage() |
70 | 70 |
return |
71 | 71 |
} |
72 |
- if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { |
|
72 |
+ var dns []string |
|
73 |
+ if *flDns != "" { |
|
74 |
+ dns = []string{*flDns} |
|
75 |
+ } |
|
76 |
+ |
|
77 |
+ ip := net.ParseIP(*flDefaultIp) |
|
78 |
+ |
|
79 |
+ config := &docker.DaemonConfig{ |
|
80 |
+ Pidfile: *pidfile, |
|
81 |
+ GraphPath: *flGraphPath, |
|
82 |
+ AutoRestart: *flAutoRestart, |
|
83 |
+ EnableCors: *flEnableCors, |
|
84 |
+ Dns: dns, |
|
85 |
+ EnableIptables: *flEnableIptables, |
|
86 |
+ BridgeIface: bridge, |
|
87 |
+ ProtoAddresses: flHosts, |
|
88 |
+ DefaultIp: ip, |
|
89 |
+ } |
|
90 |
+ if err := daemon(config); err != nil { |
|
73 | 91 |
log.Fatal(err) |
74 | 92 |
} |
75 | 93 |
} else { |
... | ... |
@@ -117,30 +139,26 @@ func removePidFile(pidfile string) { |
117 | 117 |
} |
118 | 118 |
} |
119 | 119 |
|
120 |
-func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { |
|
121 |
- if err := createPidFile(pidfile); err != nil { |
|
120 |
+func daemon(config *docker.DaemonConfig) error { |
|
121 |
+ if err := createPidFile(config.Pidfile); err != nil { |
|
122 | 122 |
log.Fatal(err) |
123 | 123 |
} |
124 |
- defer removePidFile(pidfile) |
|
124 |
+ defer removePidFile(config.Pidfile) |
|
125 | 125 |
|
126 | 126 |
c := make(chan os.Signal, 1) |
127 | 127 |
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) |
128 | 128 |
go func() { |
129 | 129 |
sig := <-c |
130 | 130 |
log.Printf("Received signal '%v', exiting\n", sig) |
131 |
- removePidFile(pidfile) |
|
131 |
+ removePidFile(config.Pidfile) |
|
132 | 132 |
os.Exit(0) |
133 | 133 |
}() |
134 |
- var dns []string |
|
135 |
- if flDns != "" { |
|
136 |
- dns = []string{flDns} |
|
137 |
- } |
|
138 |
- server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) |
|
134 |
+ server, err := docker.NewServer(config) |
|
139 | 135 |
if err != nil { |
140 | 136 |
return err |
141 | 137 |
} |
142 |
- chErrors := make(chan error, len(protoAddrs)) |
|
143 |
- for _, protoAddr := range protoAddrs { |
|
138 |
+ chErrors := make(chan error, len(config.ProtoAddresses)) |
|
139 |
+ for _, protoAddr := range config.ProtoAddresses { |
|
144 | 140 |
protoAddrParts := strings.SplitN(protoAddr, "://", 2) |
145 | 141 |
if protoAddrParts[0] == "unix" { |
146 | 142 |
syscall.Unlink(protoAddrParts[1]) |
... | ... |
@@ -155,7 +173,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart |
155 | 155 |
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true) |
156 | 156 |
}() |
157 | 157 |
} |
158 |
- for i := 0; i < len(protoAddrs); i += 1 { |
|
158 |
+ for i := 0; i < len(config.ProtoAddresses); i += 1 { |
|
159 | 159 |
err := <-chErrors |
160 | 160 |
if err != nil { |
161 | 161 |
return err |
... | ... |
@@ -96,8 +96,8 @@ Examples: |
96 | 96 |
|
97 | 97 |
.. _cli_build_examples: |
98 | 98 |
|
99 |
-Examples |
|
100 |
-~~~~~~~~ |
|
99 |
+Examples: |
|
100 |
+~~~~~~~~~ |
|
101 | 101 |
|
102 | 102 |
.. code-block:: bash |
103 | 103 |
|
... | ... |
@@ -403,6 +403,33 @@ Insert file from github |
403 | 403 |
|
404 | 404 |
Kill a running container |
405 | 405 |
|
406 |
+.. _cli_link: |
|
407 |
+ |
|
408 |
+``link`` |
|
409 |
+-------- |
|
410 |
+ |
|
411 |
+:: |
|
412 |
+ |
|
413 |
+ Usage: docker link CURRENT_NAME NEW_NAME |
|
414 |
+ |
|
415 |
+ Link a container to a new name. |
|
416 |
+ |
|
417 |
+ |
|
418 |
+Examples: |
|
419 |
+~~~~~~~~~ |
|
420 |
+ |
|
421 |
+.. code-block:: bash |
|
422 |
+ |
|
423 |
+ $ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis |
|
424 |
+ $ docker ls |
|
425 |
+ NAME ID IMAGE |
|
426 |
+ /redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest |
|
427 |
+ /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest |
|
428 |
+ |
|
429 |
+ |
|
430 |
+This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc`` |
|
431 |
+with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``. |
|
432 |
+ |
|
406 | 433 |
.. _cli_login: |
407 | 434 |
|
408 | 435 |
``login`` |
... | ... |
@@ -430,7 +457,6 @@ Insert file from github |
430 | 430 |
``logs`` |
431 | 431 |
-------- |
432 | 432 |
|
433 |
- |
|
434 | 433 |
:: |
435 | 434 |
|
436 | 435 |
Usage: docker logs [OPTIONS] CONTAINER |
... | ... |
@@ -510,6 +536,29 @@ Insert file from github |
510 | 510 |
Usage: docker rm [OPTIONS] CONTAINER |
511 | 511 |
|
512 | 512 |
Remove one or more containers |
513 |
+ -link="": Remove the link instead of the actual container |
|
514 |
+ |
|
515 |
+ |
|
516 |
+Examples: |
|
517 |
+~~~~~~~~~ |
|
518 |
+ |
|
519 |
+.. code-block:: bash |
|
520 |
+ |
|
521 |
+ $ docker rm /redis |
|
522 |
+ /redis |
|
523 |
+ |
|
524 |
+ |
|
525 |
+This will remove the container referenced under the link ``/redis``. |
|
526 |
+ |
|
527 |
+ |
|
528 |
+.. code-block:: bash |
|
529 |
+ |
|
530 |
+ $ docker rm -link /webapp/redis |
|
531 |
+ /webapp/redis |
|
532 |
+ |
|
533 |
+ |
|
534 |
+This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all |
|
535 |
+network communication. |
|
513 | 536 |
|
514 | 537 |
.. _cli_rmi: |
515 | 538 |
|
... | ... |
@@ -533,7 +582,7 @@ Insert file from github |
533 | 533 |
|
534 | 534 |
Run a command in a new container |
535 | 535 |
|
536 |
- -a=map[]: Attach to stdin, stdout or stderr. |
|
536 |
+ -a=map[]: Attach to stdin, stdout or stderr |
|
537 | 537 |
-c=0: CPU shares (relative weight) |
538 | 538 |
-cidfile="": Write the container ID to the file |
539 | 539 |
-d=false: Detached mode: Run container in the background, print new container id |
... | ... |
@@ -549,14 +598,16 @@ Insert file from github |
549 | 549 |
-u="": Username or UID |
550 | 550 |
-dns=[]: Set custom dns servers for the container |
551 | 551 |
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. |
552 |
- -volumes-from="": Mount all volumes from the given container. |
|
553 |
- -entrypoint="": Overwrite the default entrypoint set by the image. |
|
552 |
+ -volumes-from="": Mount all volumes from the given container |
|
553 |
+ -entrypoint="": Overwrite the default entrypoint set by the image |
|
554 | 554 |
-w="": Working directory inside the container |
555 | 555 |
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" |
556 | 556 |
-sig-proxy=false: Proxify all received signal to the process (even in non-tty mode) |
557 |
+ -expose=[]: Expose a port from the container without publishing it to your host |
|
558 |
+ -link="": Add link to another container (containerid:alias) |
|
557 | 559 |
|
558 | 560 |
Examples |
559 |
-~~~~~~~~ |
|
561 |
+-------- |
|
560 | 562 |
|
561 | 563 |
.. code-block:: bash |
562 | 564 |
|
... | ... |
@@ -604,6 +655,38 @@ working directory, by changing into the directory to the value |
604 | 604 |
returned by ``pwd``. So this combination executes the command |
605 | 605 |
using the container, but inside the current working directory. |
606 | 606 |
|
607 |
+.. code-block:: bash |
|
608 |
+ |
|
609 |
+ docker run -p 127.0.0.0::80 ubuntu bash |
|
610 |
+ |
|
611 |
+This the ``-p`` flag now allows you to bind a port to a specific |
|
612 |
+interface of the host machine. In this example port ``80`` of the |
|
613 |
+container will have a dynamically allocated port bound to 127.0.0.1 |
|
614 |
+of the host. |
|
615 |
+ |
|
616 |
+.. code-block:: bash |
|
617 |
+ |
|
618 |
+ docker run -p 127.0.0.1:80:80 ubuntu bash |
|
619 |
+ |
|
620 |
+This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your |
|
621 |
+host machine. |
|
622 |
+ |
|
623 |
+.. code-block:: bash |
|
624 |
+ |
|
625 |
+ docker run -expose 80 ubuntu bash |
|
626 |
+ |
|
627 |
+This will expose port ``80`` of the container for use within a link |
|
628 |
+without publishing the port to the host system's interfaces. |
|
629 |
+ |
|
630 |
+.. code-block:: bash |
|
631 |
+ |
|
632 |
+ docker run -link /redis:redis ubuntu bash |
|
633 |
+ |
|
634 |
+The ``-link`` flag will link the container named ``/redis`` into the |
|
635 |
+newly created container with the alias ``redis``. The new container |
|
636 |
+can access the network and environment of the redis container via |
|
637 |
+environment variables. |
|
638 |
+ |
|
607 | 639 |
.. _cli_search: |
608 | 640 |
|
609 | 641 |
``search`` |
... | ... |
@@ -1,6 +1,6 @@ |
1 | 1 |
:title: Docker Examples |
2 | 2 |
:description: Examples on how to use Docker |
3 |
-:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql |
|
3 |
+:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link |
|
4 | 4 |
|
5 | 5 |
|
6 | 6 |
.. _example_list: |
... | ... |
@@ -24,3 +24,4 @@ to more substantial services like you might find in production. |
24 | 24 |
postgresql_service |
25 | 25 |
mongodb |
26 | 26 |
running_riak_service |
27 |
+ linking_into_redis |
27 | 28 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,146 @@ |
0 |
+:title: Linking to an Redis container |
|
1 |
+:description: Running redis linked into your web app |
|
2 |
+:keywords: docker, example, networking, redis, link |
|
3 |
+ |
|
4 |
+.. _linking_redis: |
|
5 |
+ |
|
6 |
+Linking Redis |
|
7 |
+============= |
|
8 |
+ |
|
9 |
+.. include:: example_header.inc |
|
10 |
+ |
|
11 |
+Building a redis container to link as a child of our web application. |
|
12 |
+ |
|
13 |
+Building the redis container |
|
14 |
+---------------------------- |
|
15 |
+ |
|
16 |
+We will use a pre-build version of redis from the index under |
|
17 |
+the name ``crosbymichael/redis``. If you are interested in the |
|
18 |
+Dockerfile that was used to build this container here it is. |
|
19 |
+ |
|
20 |
+.. code-block:: bash |
|
21 |
+ |
|
22 |
+ # Build redis from source |
|
23 |
+ # Make sure you have the redis source code checked out in |
|
24 |
+ # the same directory as this Dockerfile |
|
25 |
+ FROM ubuntu |
|
26 |
+ |
|
27 |
+ RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list |
|
28 |
+ RUN apt-get update |
|
29 |
+ RUN apt-get upgrade -y |
|
30 |
+ |
|
31 |
+ RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl |
|
32 |
+ |
|
33 |
+ ADD . /redis |
|
34 |
+ |
|
35 |
+ RUN (cd /redis && make) |
|
36 |
+ RUN (cd /redis && make test) |
|
37 |
+ |
|
38 |
+ RUN mkdir -p /redis-data |
|
39 |
+ VOLUME ["/redis-data"] |
|
40 |
+ EXPOSE 6379 |
|
41 |
+ |
|
42 |
+ ENTRYPOINT ["/redis/src/redis-server"] |
|
43 |
+ CMD ["--dir", "/redis-data"] |
|
44 |
+ |
|
45 |
+ |
|
46 |
+We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports |
|
47 |
+to connect to our redis container on. If you do not expose any ports for the |
|
48 |
+image then docker will not be able to establish the link between containers. |
|
49 |
+ |
|
50 |
+ |
|
51 |
+Run the redis container |
|
52 |
+----------------------- |
|
53 |
+ |
|
54 |
+.. code-block:: bash |
|
55 |
+ |
|
56 |
+ docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass=docker |
|
57 |
+ |
|
58 |
+This will run our redis container using the default port of 6379 and using |
|
59 |
+as password to secure our service. Next we will link the redis container to |
|
60 |
+a new name using ``docker link`` and ``docker ls``. |
|
61 |
+ |
|
62 |
+ |
|
63 |
+Linking an existing container |
|
64 |
+----------------------------- |
|
65 |
+ |
|
66 |
+.. code-block:: bash |
|
67 |
+ |
|
68 |
+ docker ls |
|
69 |
+ |
|
70 |
+ NAME ID IMAGE |
|
71 |
+ /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest |
|
72 |
+ |
|
73 |
+ |
|
74 |
+Docker will automatically create an initial link with the container's id but |
|
75 |
+because the is long and not very user friendly we can link the container with |
|
76 |
+a new name. |
|
77 |
+ |
|
78 |
+.. code-block:: bash |
|
79 |
+ |
|
80 |
+ docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis |
|
81 |
+ |
|
82 |
+ docker ls |
|
83 |
+ |
|
84 |
+ NAME ID IMAGE |
|
85 |
+ /redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest |
|
86 |
+ /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest |
|
87 |
+ |
|
88 |
+Now we can reference our running redis service using the friendly name ``/redis``. |
|
89 |
+We can issue all the commands that you would expect; start, stop, attach, using the new name. |
|
90 |
+ |
|
91 |
+Linking redis as a child |
|
92 |
+------------------------ |
|
93 |
+ |
|
94 |
+Next we can start a new web application that has a dependency on redis and apply a link |
|
95 |
+to connect both containers. If you noticed when running our redis service we did not use |
|
96 |
+the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379 |
|
97 |
+but we did not publish the port. This allows docker to prevent all network traffic to |
|
98 |
+the redis container except when explicitly specified within a link. This is a big win |
|
99 |
+for security. |
|
100 |
+ |
|
101 |
+ |
|
102 |
+Now lets start our web application with a link into redis. |
|
103 |
+ |
|
104 |
+.. code-block:: bash |
|
105 |
+ |
|
106 |
+ docker run -t -i -link /redis:db ubuntu bash |
|
107 |
+ |
|
108 |
+ root@4c01db0b339c:/# env |
|
109 |
+ |
|
110 |
+ HOSTNAME=4c01db0b339c |
|
111 |
+ DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db |
|
112 |
+ TERM=xterm |
|
113 |
+ DB_PORT=tcp://172.17.0.8:6379 |
|
114 |
+ DB_PORT_6379_TCP=tcp://172.17.0.8:6379 |
|
115 |
+ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
|
116 |
+ PWD=/ |
|
117 |
+ DB_ENV_PASSWORD=dockerpass |
|
118 |
+ SHLVL=1 |
|
119 |
+ HOME=/ |
|
120 |
+ container=lxc |
|
121 |
+ _=/usr/bin/env |
|
122 |
+ root@4c01db0b339c:/# |
|
123 |
+ |
|
124 |
+ |
|
125 |
+When we inspect the environment of the linked container we can see a few extra environment |
|
126 |
+variables have been added. When you specified ``-link /redis:db`` you are telling docker |
|
127 |
+to link the container named ``/redis`` into this new container with the alias ``db``. |
|
128 |
+Environment variables are prefixed with the alias so that the parent container can access |
|
129 |
+network and environment information from the child. |
|
130 |
+ |
|
131 |
+.. code-block:: bash |
|
132 |
+ |
|
133 |
+ # The name of the child container |
|
134 |
+ DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db |
|
135 |
+ # The default protocol, ip, and port of the service running in the container |
|
136 |
+ DB_PORT=tcp://172.17.0.8:6379 |
|
137 |
+ # A specific protocol, ip, and port of various services |
|
138 |
+ DB_PORT_6379_TCP=tcp://172.17.0.8:6379 |
|
139 |
+ # Get environment variables of the container |
|
140 |
+ DB_ENV_PASSWORD=dockerpass |
|
141 |
+ |
|
142 |
+ |
|
143 |
+Accessing the network information along with the environment of the child container allows |
|
144 |
+us to easily connect to the redis service on the specific ip and port and use the password |
|
145 |
+specified in the environment. |
0 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,455 @@ |
0 |
+package gograph |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ _ "code.google.com/p/gosqlite/sqlite3" |
|
4 |
+ "database/sql" |
|
5 |
+ "fmt" |
|
6 |
+ "os" |
|
7 |
+ "path" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+const ( |
|
11 |
+ createEntityTable = ` |
|
12 |
+ CREATE TABLE IF NOT EXISTS entity ( |
|
13 |
+ id text NOT NULL PRIMARY KEY |
|
14 |
+ );` |
|
15 |
+ |
|
16 |
+ createEdgeTable = ` |
|
17 |
+ CREATE TABLE IF NOT EXISTS edge ( |
|
18 |
+ "entity_id" text NOT NULL, |
|
19 |
+ "parent_id" text NULL, |
|
20 |
+ "name" text NOT NULL, |
|
21 |
+ CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"), |
|
22 |
+ CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") |
|
23 |
+ ); |
|
24 |
+ |
|
25 |
+ CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name); |
|
26 |
+ ` |
|
27 |
+) |
|
28 |
+ |
|
29 |
+// Entity with a unique id |
|
30 |
+type Entity struct { |
|
31 |
+ id string |
|
32 |
+} |
|
33 |
+ |
|
34 |
+// An Edge connects two entities together |
|
35 |
+type Edge struct { |
|
36 |
+ EntityID string |
|
37 |
+ Name string |
|
38 |
+ ParentID string |
|
39 |
+} |
|
40 |
+ |
|
41 |
+type Entities map[string]*Entity |
|
42 |
+type Edges []*Edge |
|
43 |
+ |
|
44 |
+type WalkFunc func(fullPath string, entity *Entity) error |
|
45 |
+ |
|
46 |
+// Graph database for storing entities and their relationships |
|
47 |
+type Database struct { |
|
48 |
+ dbPath string |
|
49 |
+} |
|
50 |
+ |
|
51 |
+// Create a new graph database initialized with a root entity |
|
52 |
+func NewDatabase(dbPath string) (*Database, error) { |
|
53 |
+ db := &Database{dbPath} |
|
54 |
+ if _, err := os.Stat(dbPath); err == nil { |
|
55 |
+ return db, nil |
|
56 |
+ } |
|
57 |
+ conn, err := db.openConn() |
|
58 |
+ if err != nil { |
|
59 |
+ return nil, err |
|
60 |
+ } |
|
61 |
+ defer conn.Close() |
|
62 |
+ |
|
63 |
+ if _, err := conn.Exec(createEntityTable); err != nil { |
|
64 |
+ return nil, err |
|
65 |
+ } |
|
66 |
+ if _, err := conn.Exec(createEdgeTable); err != nil { |
|
67 |
+ return nil, err |
|
68 |
+ } |
|
69 |
+ |
|
70 |
+ rollback := func() { |
|
71 |
+ conn.Exec("ROLLBACK") |
|
72 |
+ } |
|
73 |
+ |
|
74 |
+ // Create root entities |
|
75 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
76 |
+ return nil, err |
|
77 |
+ } |
|
78 |
+ if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil { |
|
79 |
+ rollback() |
|
80 |
+ return nil, err |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil { |
|
84 |
+ rollback() |
|
85 |
+ return nil, err |
|
86 |
+ } |
|
87 |
+ |
|
88 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
89 |
+ return nil, err |
|
90 |
+ } |
|
91 |
+ return db, nil |
|
92 |
+} |
|
93 |
+ |
|
94 |
+// Set the entity id for a given path |
|
95 |
+func (db *Database) Set(fullPath, id string) (*Entity, error) { |
|
96 |
+ conn, err := db.openConn() |
|
97 |
+ if err != nil { |
|
98 |
+ return nil, err |
|
99 |
+ } |
|
100 |
+ defer conn.Close() |
|
101 |
+ rollback := func() { |
|
102 |
+ conn.Exec("ROLLBACK") |
|
103 |
+ } |
|
104 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
105 |
+ return nil, err |
|
106 |
+ } |
|
107 |
+ var entityId string |
|
108 |
+ if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil { |
|
109 |
+ if err == sql.ErrNoRows { |
|
110 |
+ if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { |
|
111 |
+ rollback() |
|
112 |
+ return nil, err |
|
113 |
+ } |
|
114 |
+ } else { |
|
115 |
+ rollback() |
|
116 |
+ return nil, err |
|
117 |
+ } |
|
118 |
+ } |
|
119 |
+ e := &Entity{id} |
|
120 |
+ |
|
121 |
+ parentPath, name := splitPath(fullPath) |
|
122 |
+ if err := db.setEdge(conn, parentPath, name, e); err != nil { |
|
123 |
+ rollback() |
|
124 |
+ return nil, err |
|
125 |
+ } |
|
126 |
+ |
|
127 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
128 |
+ return nil, err |
|
129 |
+ } |
|
130 |
+ return e, nil |
|
131 |
+} |
|
132 |
+ |
|
133 |
+func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error { |
|
134 |
+ parent, err := db.get(conn, parentPath) |
|
135 |
+ if err != nil { |
|
136 |
+ return err |
|
137 |
+ } |
|
138 |
+ if parent.id == e.id { |
|
139 |
+ return fmt.Errorf("Cannot set self as child") |
|
140 |
+ } |
|
141 |
+ |
|
142 |
+ if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { |
|
143 |
+ return err |
|
144 |
+ } |
|
145 |
+ return nil |
|
146 |
+} |
|
147 |
+ |
|
148 |
+// Return the root "/" entity for the database |
|
149 |
+func (db *Database) RootEntity() *Entity { |
|
150 |
+ return &Entity{ |
|
151 |
+ id: "0", |
|
152 |
+ } |
|
153 |
+} |
|
154 |
+ |
|
155 |
+// Return the entity for a given path |
|
156 |
+func (db *Database) Get(name string) *Entity { |
|
157 |
+ conn, err := db.openConn() |
|
158 |
+ if err != nil { |
|
159 |
+ return nil |
|
160 |
+ } |
|
161 |
+ e, err := db.get(conn, name) |
|
162 |
+ if err != nil { |
|
163 |
+ return nil |
|
164 |
+ } |
|
165 |
+ return e |
|
166 |
+} |
|
167 |
+ |
|
168 |
+func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { |
|
169 |
+ e := db.RootEntity() |
|
170 |
+ // We always know the root name so return it if |
|
171 |
+ // it is requested |
|
172 |
+ if name == "/" { |
|
173 |
+ return e, nil |
|
174 |
+ } |
|
175 |
+ |
|
176 |
+ parts := split(name) |
|
177 |
+ for i := 1; i < len(parts); i++ { |
|
178 |
+ p := parts[i] |
|
179 |
+ |
|
180 |
+ next := db.child(conn, e, p) |
|
181 |
+ if next == nil { |
|
182 |
+ return nil, fmt.Errorf("Cannot find child") |
|
183 |
+ } |
|
184 |
+ e = next |
|
185 |
+ } |
|
186 |
+ return e, nil |
|
187 |
+ |
|
188 |
+} |
|
189 |
+ |
|
190 |
+// List all entities by from the name |
|
191 |
+// The key will be the full path of the entity |
|
192 |
+func (db *Database) List(name string, depth int) Entities { |
|
193 |
+ out := Entities{} |
|
194 |
+ conn, err := db.openConn() |
|
195 |
+ if err != nil { |
|
196 |
+ return out |
|
197 |
+ } |
|
198 |
+ defer conn.Close() |
|
199 |
+ |
|
200 |
+ for c := range db.children(conn, name, depth) { |
|
201 |
+ out[c.FullPath] = c.Entity |
|
202 |
+ } |
|
203 |
+ return out |
|
204 |
+} |
|
205 |
+ |
|
206 |
+func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { |
|
207 |
+ conn, err := db.openConn() |
|
208 |
+ if err != nil { |
|
209 |
+ return err |
|
210 |
+ } |
|
211 |
+ defer conn.Close() |
|
212 |
+ |
|
213 |
+ for c := range db.children(conn, name, depth) { |
|
214 |
+ if err := walkFunc(c.FullPath, c.Entity); err != nil { |
|
215 |
+ return err |
|
216 |
+ } |
|
217 |
+ } |
|
218 |
+ return nil |
|
219 |
+} |
|
220 |
+ |
|
221 |
+// Return the refrence count for a specified id |
|
222 |
+func (db *Database) Refs(id string) int { |
|
223 |
+ conn, err := db.openConn() |
|
224 |
+ if err != nil { |
|
225 |
+ return -1 |
|
226 |
+ } |
|
227 |
+ defer conn.Close() |
|
228 |
+ |
|
229 |
+ var count int |
|
230 |
+ if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { |
|
231 |
+ return 0 |
|
232 |
+ } |
|
233 |
+ return count |
|
234 |
+} |
|
235 |
+ |
|
236 |
+// Return all the id's path references |
|
237 |
+func (db *Database) RefPaths(id string) Edges { |
|
238 |
+ refs := Edges{} |
|
239 |
+ conn, err := db.openConn() |
|
240 |
+ if err != nil { |
|
241 |
+ return refs |
|
242 |
+ } |
|
243 |
+ defer conn.Close() |
|
244 |
+ |
|
245 |
+ rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) |
|
246 |
+ if err != nil { |
|
247 |
+ return refs |
|
248 |
+ } |
|
249 |
+ defer rows.Close() |
|
250 |
+ |
|
251 |
+ for rows.Next() { |
|
252 |
+ var name string |
|
253 |
+ var parentId string |
|
254 |
+ if err := rows.Scan(&name, &parentId); err != nil { |
|
255 |
+ return refs |
|
256 |
+ } |
|
257 |
+ refs = append(refs, &Edge{ |
|
258 |
+ EntityID: id, |
|
259 |
+ Name: name, |
|
260 |
+ ParentID: parentId, |
|
261 |
+ }) |
|
262 |
+ } |
|
263 |
+ return refs |
|
264 |
+} |
|
265 |
+ |
|
266 |
+// Delete the reference to an entity at a given path |
|
267 |
+func (db *Database) Delete(name string) error { |
|
268 |
+ if name == "/" { |
|
269 |
+ return fmt.Errorf("Cannot delete root entity") |
|
270 |
+ } |
|
271 |
+ conn, err := db.openConn() |
|
272 |
+ if err != nil { |
|
273 |
+ return err |
|
274 |
+ } |
|
275 |
+ defer conn.Close() |
|
276 |
+ |
|
277 |
+ parentPath, n := splitPath(name) |
|
278 |
+ parent, err := db.get(conn, parentPath) |
|
279 |
+ if err != nil { |
|
280 |
+ return err |
|
281 |
+ } |
|
282 |
+ |
|
283 |
+ if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { |
|
284 |
+ return err |
|
285 |
+ } |
|
286 |
+ return nil |
|
287 |
+} |
|
288 |
+ |
|
289 |
+// Remove the entity with the specified id |
|
290 |
+// Walk the graph to make sure all references to the entity |
|
291 |
+// are removed and return the number of references removed |
|
292 |
+func (db *Database) Purge(id string) (int, error) { |
|
293 |
+ conn, err := db.openConn() |
|
294 |
+ if err != nil { |
|
295 |
+ return -1, err |
|
296 |
+ } |
|
297 |
+ defer conn.Close() |
|
298 |
+ |
|
299 |
+ rollback := func() { |
|
300 |
+ conn.Exec("ROLLBACK") |
|
301 |
+ } |
|
302 |
+ |
|
303 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
304 |
+ return -1, err |
|
305 |
+ } |
|
306 |
+ |
|
307 |
+ // Delete all edges |
|
308 |
+ rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id) |
|
309 |
+ if err != nil { |
|
310 |
+ rollback() |
|
311 |
+ return -1, err |
|
312 |
+ } |
|
313 |
+ |
|
314 |
+ changes, err := rows.RowsAffected() |
|
315 |
+ if err != nil { |
|
316 |
+ return -1, err |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ // Delete entity |
|
320 |
+ if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil { |
|
321 |
+ rollback() |
|
322 |
+ return -1, err |
|
323 |
+ } |
|
324 |
+ |
|
325 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
326 |
+ return -1, err |
|
327 |
+ } |
|
328 |
+ return int(changes), nil |
|
329 |
+} |
|
330 |
+ |
|
331 |
+// Rename an edge for a given path |
|
332 |
+func (db *Database) Rename(currentName, newName string) error { |
|
333 |
+ parentPath, name := splitPath(currentName) |
|
334 |
+ newParentPath, newEdgeName := splitPath(newName) |
|
335 |
+ |
|
336 |
+ if parentPath != newParentPath { |
|
337 |
+ return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ conn, err := db.openConn() |
|
341 |
+ if err != nil { |
|
342 |
+ return err |
|
343 |
+ } |
|
344 |
+ defer conn.Close() |
|
345 |
+ |
|
346 |
+ parent, err := db.get(conn, parentPath) |
|
347 |
+ if err != nil { |
|
348 |
+ return err |
|
349 |
+ } |
|
350 |
+ |
|
351 |
+ rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) |
|
352 |
+ if err != nil { |
|
353 |
+ return err |
|
354 |
+ } |
|
355 |
+ i, err := rows.RowsAffected() |
|
356 |
+ if err != nil { |
|
357 |
+ return err |
|
358 |
+ } |
|
359 |
+ if i == 0 { |
|
360 |
+ return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name) |
|
361 |
+ } |
|
362 |
+ return nil |
|
363 |
+} |
|
364 |
+ |
|
365 |
+type WalkMeta struct { |
|
366 |
+ Parent *Entity |
|
367 |
+ Entity *Entity |
|
368 |
+ FullPath string |
|
369 |
+ Edge *Edge |
|
370 |
+} |
|
371 |
+ |
|
372 |
+func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta { |
|
373 |
+ out := make(chan WalkMeta) |
|
374 |
+ e, err := db.get(conn, name) |
|
375 |
+ if err != nil { |
|
376 |
+ close(out) |
|
377 |
+ return out |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ go func() { |
|
381 |
+ rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) |
|
382 |
+ if err != nil { |
|
383 |
+ close(out) |
|
384 |
+ } |
|
385 |
+ defer rows.Close() |
|
386 |
+ |
|
387 |
+ for rows.Next() { |
|
388 |
+ var entityId, entityName string |
|
389 |
+ if err := rows.Scan(&entityId, &entityName); err != nil { |
|
390 |
+ // Log error |
|
391 |
+ continue |
|
392 |
+ } |
|
393 |
+ child := &Entity{entityId} |
|
394 |
+ edge := &Edge{ |
|
395 |
+ ParentID: e.id, |
|
396 |
+ Name: entityName, |
|
397 |
+ EntityID: child.id, |
|
398 |
+ } |
|
399 |
+ |
|
400 |
+ meta := WalkMeta{ |
|
401 |
+ Parent: e, |
|
402 |
+ Entity: child, |
|
403 |
+ FullPath: path.Join(name, edge.Name), |
|
404 |
+ Edge: edge, |
|
405 |
+ } |
|
406 |
+ |
|
407 |
+ out <- meta |
|
408 |
+ if depth == 0 { |
|
409 |
+ continue |
|
410 |
+ } |
|
411 |
+ nDepth := depth |
|
412 |
+ if depth != -1 { |
|
413 |
+ nDepth -= 1 |
|
414 |
+ } |
|
415 |
+ sc := db.children(conn, meta.FullPath, nDepth) |
|
416 |
+ for c := range sc { |
|
417 |
+ out <- c |
|
418 |
+ } |
|
419 |
+ } |
|
420 |
+ close(out) |
|
421 |
+ }() |
|
422 |
+ return out |
|
423 |
+} |
|
424 |
+ |
|
425 |
+// Return the entity based on the parent path and name |
|
426 |
+func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity { |
|
427 |
+ var id string |
|
428 |
+ if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { |
|
429 |
+ return nil |
|
430 |
+ } |
|
431 |
+ return &Entity{id} |
|
432 |
+} |
|
433 |
+ |
|
434 |
+func (db *Database) openConn() (*sql.DB, error) { |
|
435 |
+ return sql.Open("sqlite3", db.dbPath) |
|
436 |
+} |
|
437 |
+ |
|
438 |
+// Return the id used to reference this entity |
|
439 |
+func (e *Entity) ID() string { |
|
440 |
+ return e.id |
|
441 |
+} |
|
442 |
+ |
|
443 |
+// Return the paths sorted by depth |
|
444 |
+func (e Entities) Paths() []string { |
|
445 |
+ out := make([]string, len(e)) |
|
446 |
+ var i int |
|
447 |
+ for k := range e { |
|
448 |
+ out[i] = k |
|
449 |
+ i++ |
|
450 |
+ } |
|
451 |
+ sortByDepth(out) |
|
452 |
+ |
|
453 |
+ return out |
|
454 |
+} |
0 | 455 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,452 @@ |
0 |
+package gograph |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "os" |
|
4 |
+ "path" |
|
5 |
+ "strconv" |
|
6 |
+ "testing" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+func newTestDb(t *testing.T) *Database { |
|
10 |
+ db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db")) |
|
11 |
+ if err != nil { |
|
12 |
+ t.Fatal(err) |
|
13 |
+ } |
|
14 |
+ return db |
|
15 |
+} |
|
16 |
+ |
|
17 |
+func destroyTestDb(db *Database) { |
|
18 |
+ os.Remove(db.dbPath) |
|
19 |
+} |
|
20 |
+ |
|
21 |
+func TestNewDatabase(t *testing.T) { |
|
22 |
+ db := newTestDb(t) |
|
23 |
+ if db == nil { |
|
24 |
+ t.Fatal("Datbase should not be nil") |
|
25 |
+ } |
|
26 |
+ defer destroyTestDb(db) |
|
27 |
+} |
|
28 |
+ |
|
29 |
+func TestCreateRootEnity(t *testing.T) { |
|
30 |
+ db := newTestDb(t) |
|
31 |
+ defer destroyTestDb(db) |
|
32 |
+ root := db.RootEntity() |
|
33 |
+ if root == nil { |
|
34 |
+ t.Fatal("Root entity should not be nil") |
|
35 |
+ } |
|
36 |
+} |
|
37 |
+ |
|
38 |
+func TestGetRootEntity(t *testing.T) { |
|
39 |
+ db := newTestDb(t) |
|
40 |
+ defer destroyTestDb(db) |
|
41 |
+ |
|
42 |
+ e := db.Get("/") |
|
43 |
+ if e == nil { |
|
44 |
+ t.Fatal("Entity should not be nil") |
|
45 |
+ } |
|
46 |
+ if e.ID() != "0" { |
|
47 |
+ t.Fatalf("Enity id should be 0, got %s", e.ID()) |
|
48 |
+ } |
|
49 |
+} |
|
50 |
+ |
|
51 |
+func TestSetEntityWithDifferentName(t *testing.T) { |
|
52 |
+ db := newTestDb(t) |
|
53 |
+ defer destroyTestDb(db) |
|
54 |
+ |
|
55 |
+ db.Set("/test", "1") |
|
56 |
+ if _, err := db.Set("/other", "1"); err != nil { |
|
57 |
+ t.Fatal(err) |
|
58 |
+ } |
|
59 |
+} |
|
60 |
+ |
|
61 |
+func TestCreateChild(t *testing.T) { |
|
62 |
+ db := newTestDb(t) |
|
63 |
+ defer destroyTestDb(db) |
|
64 |
+ |
|
65 |
+ child, err := db.Set("/db", "1") |
|
66 |
+ if err != nil { |
|
67 |
+ t.Fatal(err) |
|
68 |
+ } |
|
69 |
+ if child == nil { |
|
70 |
+ t.Fatal("Child should not be nil") |
|
71 |
+ } |
|
72 |
+ if child.ID() != "1" { |
|
73 |
+ t.Fail() |
|
74 |
+ } |
|
75 |
+} |
|
76 |
+ |
|
77 |
+func TestListAllRootChildren(t *testing.T) { |
|
78 |
+ db := newTestDb(t) |
|
79 |
+ defer destroyTestDb(db) |
|
80 |
+ |
|
81 |
+ for i := 1; i < 6; i++ { |
|
82 |
+ a := strconv.Itoa(i) |
|
83 |
+ if _, err := db.Set("/"+a, a); err != nil { |
|
84 |
+ t.Fatal(err) |
|
85 |
+ } |
|
86 |
+ } |
|
87 |
+ entries := db.List("/", -1) |
|
88 |
+ if len(entries) != 5 { |
|
89 |
+ t.Fatalf("Expect 5 entries for / got %d", len(entries)) |
|
90 |
+ } |
|
91 |
+} |
|
92 |
+ |
|
93 |
+func TestListAllSubChildren(t *testing.T) { |
|
94 |
+ db := newTestDb(t) |
|
95 |
+ defer destroyTestDb(db) |
|
96 |
+ |
|
97 |
+ _, err := db.Set("/webapp", "1") |
|
98 |
+ if err != nil { |
|
99 |
+ t.Fatal(err) |
|
100 |
+ } |
|
101 |
+ child2, err := db.Set("/db", "2") |
|
102 |
+ if err != nil { |
|
103 |
+ t.Fatal(err) |
|
104 |
+ } |
|
105 |
+ child4, err := db.Set("/logs", "4") |
|
106 |
+ if err != nil { |
|
107 |
+ t.Fatal(err) |
|
108 |
+ } |
|
109 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil { |
|
110 |
+ t.Fatal(err) |
|
111 |
+ } |
|
112 |
+ |
|
113 |
+ child3, err := db.Set("/sentry", "3") |
|
114 |
+ if err != nil { |
|
115 |
+ t.Fatal(err) |
|
116 |
+ } |
|
117 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { |
|
118 |
+ t.Fatal(err) |
|
119 |
+ } |
|
120 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil { |
|
121 |
+ t.Fatal(err) |
|
122 |
+ } |
|
123 |
+ |
|
124 |
+ entries := db.List("/webapp", 1) |
|
125 |
+ if len(entries) != 3 { |
|
126 |
+ t.Fatalf("Expect 3 entries for / got %d", len(entries)) |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ entries = db.List("/webapp", 0) |
|
130 |
+ if len(entries) != 2 { |
|
131 |
+ t.Fatalf("Expect 2 entries for / got %d", len(entries)) |
|
132 |
+ } |
|
133 |
+} |
|
134 |
+ |
|
135 |
+func TestAddSelfAsChild(t *testing.T) { |
|
136 |
+ db := newTestDb(t) |
|
137 |
+ defer destroyTestDb(db) |
|
138 |
+ |
|
139 |
+ child, err := db.Set("/test", "1") |
|
140 |
+ if err != nil { |
|
141 |
+ t.Fatal(err) |
|
142 |
+ } |
|
143 |
+ if _, err := db.Set("/test/other", child.ID()); err == nil { |
|
144 |
+ t.Fatal("Error should not be nil") |
|
145 |
+ } |
|
146 |
+} |
|
147 |
+ |
|
148 |
+func TestAddChildToNonExistantRoot(t *testing.T) { |
|
149 |
+ db := newTestDb(t) |
|
150 |
+ defer destroyTestDb(db) |
|
151 |
+ |
|
152 |
+ if _, err := db.Set("/myapp", "1"); err != nil { |
|
153 |
+ t.Fatal(err) |
|
154 |
+ } |
|
155 |
+ |
|
156 |
+ if _, err := db.Set("/myapp/proxy/db", "2"); err == nil { |
|
157 |
+ t.Fatal("Error should not be nil") |
|
158 |
+ } |
|
159 |
+} |
|
160 |
+ |
|
161 |
+func TestWalkAll(t *testing.T) { |
|
162 |
+ db := newTestDb(t) |
|
163 |
+ defer destroyTestDb(db) |
|
164 |
+ _, err := db.Set("/webapp", "1") |
|
165 |
+ if err != nil { |
|
166 |
+ t.Fatal(err) |
|
167 |
+ } |
|
168 |
+ child2, err := db.Set("/db", "2") |
|
169 |
+ if err != nil { |
|
170 |
+ t.Fatal(err) |
|
171 |
+ } |
|
172 |
+ child4, err := db.Set("/db/logs", "4") |
|
173 |
+ if err != nil { |
|
174 |
+ t.Fatal(err) |
|
175 |
+ } |
|
176 |
+ if _, err := db.Set("/webapp/logs", child4.ID()); err != nil { |
|
177 |
+ t.Fatal(err) |
|
178 |
+ } |
|
179 |
+ |
|
180 |
+ child3, err := db.Set("/sentry", "3") |
|
181 |
+ if err != nil { |
|
182 |
+ t.Fatal(err) |
|
183 |
+ } |
|
184 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { |
|
185 |
+ t.Fatal(err) |
|
186 |
+ } |
|
187 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil { |
|
188 |
+ t.Fatal(err) |
|
189 |
+ } |
|
190 |
+ |
|
191 |
+ child5, err := db.Set("/gograph", "5") |
|
192 |
+ if err != nil { |
|
193 |
+ t.Fatal(err) |
|
194 |
+ } |
|
195 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { |
|
196 |
+ t.Fatal(err) |
|
197 |
+ } |
|
198 |
+ |
|
199 |
+ if err := db.Walk("/", func(p string, e *Entity) error { |
|
200 |
+ t.Logf("Path: %s Entity: %s", p, e.ID()) |
|
201 |
+ return nil |
|
202 |
+ }, -1); err != nil { |
|
203 |
+ t.Fatal(err) |
|
204 |
+ } |
|
205 |
+} |
|
206 |
+ |
|
207 |
+func TestGetEntityByPath(t *testing.T) { |
|
208 |
+ db := newTestDb(t) |
|
209 |
+ defer destroyTestDb(db) |
|
210 |
+ _, err := db.Set("/webapp", "1") |
|
211 |
+ if err != nil { |
|
212 |
+ t.Fatal(err) |
|
213 |
+ } |
|
214 |
+ child2, err := db.Set("/db", "2") |
|
215 |
+ if err != nil { |
|
216 |
+ t.Fatal(err) |
|
217 |
+ } |
|
218 |
+ child4, err := db.Set("/logs", "4") |
|
219 |
+ if err != nil { |
|
220 |
+ t.Fatal(err) |
|
221 |
+ } |
|
222 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil { |
|
223 |
+ t.Fatal(err) |
|
224 |
+ } |
|
225 |
+ |
|
226 |
+ child3, err := db.Set("/sentry", "3") |
|
227 |
+ if err != nil { |
|
228 |
+ t.Fatal(err) |
|
229 |
+ } |
|
230 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { |
|
231 |
+ t.Fatal(err) |
|
232 |
+ } |
|
233 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil { |
|
234 |
+ t.Fatal(err) |
|
235 |
+ } |
|
236 |
+ |
|
237 |
+ child5, err := db.Set("/gograph", "5") |
|
238 |
+ if err != nil { |
|
239 |
+ t.Fatal(err) |
|
240 |
+ } |
|
241 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { |
|
242 |
+ t.Fatal(err) |
|
243 |
+ } |
|
244 |
+ |
|
245 |
+ entity := db.Get("/webapp/db/logs") |
|
246 |
+ if entity == nil { |
|
247 |
+ t.Fatal("Entity should not be nil") |
|
248 |
+ } |
|
249 |
+ if entity.ID() != "4" { |
|
250 |
+ t.Fatalf("Expected to get entity with id 4, got %s", entity.ID()) |
|
251 |
+ } |
|
252 |
+} |
|
253 |
+ |
|
254 |
+func TestEnitiesPaths(t *testing.T) { |
|
255 |
+ db := newTestDb(t) |
|
256 |
+ defer destroyTestDb(db) |
|
257 |
+ _, err := db.Set("/webapp", "1") |
|
258 |
+ if err != nil { |
|
259 |
+ t.Fatal(err) |
|
260 |
+ } |
|
261 |
+ child2, err := db.Set("/db", "2") |
|
262 |
+ if err != nil { |
|
263 |
+ t.Fatal(err) |
|
264 |
+ } |
|
265 |
+ child4, err := db.Set("/logs", "4") |
|
266 |
+ if err != nil { |
|
267 |
+ t.Fatal(err) |
|
268 |
+ } |
|
269 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil { |
|
270 |
+ t.Fatal(err) |
|
271 |
+ } |
|
272 |
+ |
|
273 |
+ child3, err := db.Set("/sentry", "3") |
|
274 |
+ if err != nil { |
|
275 |
+ t.Fatal(err) |
|
276 |
+ } |
|
277 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { |
|
278 |
+ t.Fatal(err) |
|
279 |
+ } |
|
280 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil { |
|
281 |
+ t.Fatal(err) |
|
282 |
+ } |
|
283 |
+ |
|
284 |
+ child5, err := db.Set("/gograph", "5") |
|
285 |
+ if err != nil { |
|
286 |
+ t.Fatal(err) |
|
287 |
+ } |
|
288 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { |
|
289 |
+ t.Fatal(err) |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ out := db.List("/", -1) |
|
293 |
+ for _, p := range out.Paths() { |
|
294 |
+ t.Log(p) |
|
295 |
+ } |
|
296 |
+} |
|
297 |
+ |
|
298 |
+func TestDeleteRootEntity(t *testing.T) { |
|
299 |
+ db := newTestDb(t) |
|
300 |
+ defer destroyTestDb(db) |
|
301 |
+ |
|
302 |
+ if err := db.Delete("/"); err == nil { |
|
303 |
+ t.Fatal("Error should not be nil") |
|
304 |
+ } |
|
305 |
+} |
|
306 |
+ |
|
307 |
+func TestDeleteEntity(t *testing.T) { |
|
308 |
+ db := newTestDb(t) |
|
309 |
+ defer destroyTestDb(db) |
|
310 |
+ _, err := db.Set("/webapp", "1") |
|
311 |
+ if err != nil { |
|
312 |
+ t.Fatal(err) |
|
313 |
+ } |
|
314 |
+ child2, err := db.Set("/db", "2") |
|
315 |
+ if err != nil { |
|
316 |
+ t.Fatal(err) |
|
317 |
+ } |
|
318 |
+ child4, err := db.Set("/logs", "4") |
|
319 |
+ if err != nil { |
|
320 |
+ t.Fatal(err) |
|
321 |
+ } |
|
322 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil { |
|
323 |
+ t.Fatal(err) |
|
324 |
+ } |
|
325 |
+ |
|
326 |
+ child3, err := db.Set("/sentry", "3") |
|
327 |
+ if err != nil { |
|
328 |
+ t.Fatal(err) |
|
329 |
+ } |
|
330 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { |
|
331 |
+ t.Fatal(err) |
|
332 |
+ } |
|
333 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil { |
|
334 |
+ t.Fatal(err) |
|
335 |
+ } |
|
336 |
+ |
|
337 |
+ child5, err := db.Set("/gograph", "5") |
|
338 |
+ if err != nil { |
|
339 |
+ t.Fatal(err) |
|
340 |
+ } |
|
341 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { |
|
342 |
+ t.Fatal(err) |
|
343 |
+ } |
|
344 |
+ |
|
345 |
+ if err := db.Delete("/webapp/sentry"); err != nil { |
|
346 |
+ t.Fatal(err) |
|
347 |
+ } |
|
348 |
+ entity := db.Get("/webapp/sentry") |
|
349 |
+ if entity != nil { |
|
350 |
+ t.Fatal("Entity /webapp/sentry should be nil") |
|
351 |
+ } |
|
352 |
+} |
|
353 |
+ |
|
354 |
+func TestCountRefs(t *testing.T) { |
|
355 |
+ db := newTestDb(t) |
|
356 |
+ defer destroyTestDb(db) |
|
357 |
+ |
|
358 |
+ db.Set("/webapp", "1") |
|
359 |
+ |
|
360 |
+ if db.Refs("1") != 1 { |
|
361 |
+ t.Fatal("Expect reference count to be 1") |
|
362 |
+ } |
|
363 |
+ |
|
364 |
+ db.Set("/db", "2") |
|
365 |
+ db.Set("/webapp/db", "2") |
|
366 |
+ if db.Refs("2") != 2 { |
|
367 |
+ t.Fatal("Expect reference count to be 2") |
|
368 |
+ } |
|
369 |
+} |
|
370 |
+ |
|
371 |
+func TestPurgeId(t *testing.T) { |
|
372 |
+ db := newTestDb(t) |
|
373 |
+ defer destroyTestDb(db) |
|
374 |
+ |
|
375 |
+ db.Set("/webapp", "1") |
|
376 |
+ |
|
377 |
+ if db.Refs("1") != 1 { |
|
378 |
+ t.Fatal("Expect reference count to be 1") |
|
379 |
+ } |
|
380 |
+ |
|
381 |
+ db.Set("/db", "2") |
|
382 |
+ db.Set("/webapp/db", "2") |
|
383 |
+ |
|
384 |
+ count, err := db.Purge("2") |
|
385 |
+ if err != nil { |
|
386 |
+ t.Fatal(err) |
|
387 |
+ } |
|
388 |
+ if count != 2 { |
|
389 |
+ t.Fatal("Expected 2 references to be removed") |
|
390 |
+ } |
|
391 |
+} |
|
392 |
+ |
|
393 |
+func TestRename(t *testing.T) { |
|
394 |
+ db := newTestDb(t) |
|
395 |
+ defer destroyTestDb(db) |
|
396 |
+ |
|
397 |
+ db.Set("/webapp", "1") |
|
398 |
+ |
|
399 |
+ if db.Refs("1") != 1 { |
|
400 |
+ t.Fatal("Expect reference count to be 1") |
|
401 |
+ } |
|
402 |
+ |
|
403 |
+ db.Set("/db", "2") |
|
404 |
+ db.Set("/webapp/db", "2") |
|
405 |
+ |
|
406 |
+ if db.Get("/webapp/db") == nil { |
|
407 |
+ t.Fatal("Cannot find entity at path /webapp/db") |
|
408 |
+ } |
|
409 |
+ |
|
410 |
+ if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil { |
|
411 |
+ t.Fatal(err) |
|
412 |
+ } |
|
413 |
+ if db.Get("/webapp/db") != nil { |
|
414 |
+ t.Fatal("Entity should not exist at /webapp/db") |
|
415 |
+ } |
|
416 |
+ if db.Get("/webapp/newdb") == nil { |
|
417 |
+ t.Fatal("Cannot find entity at path /webapp/newdb") |
|
418 |
+ } |
|
419 |
+ |
|
420 |
+} |
|
421 |
+ |
|
422 |
+func TestCreateMultipleNames(t *testing.T) { |
|
423 |
+ db := newTestDb(t) |
|
424 |
+ defer destroyTestDb(db) |
|
425 |
+ |
|
426 |
+ db.Set("/db", "1") |
|
427 |
+ if _, err := db.Set("/myapp", "1"); err != nil { |
|
428 |
+ t.Fatal(err) |
|
429 |
+ } |
|
430 |
+ |
|
431 |
+ db.Walk("/", func(p string, e *Entity) error { |
|
432 |
+ t.Logf("%s\n", p) |
|
433 |
+ return nil |
|
434 |
+ }, -1) |
|
435 |
+} |
|
436 |
+ |
|
437 |
+func TestRefPaths(t *testing.T) { |
|
438 |
+ db := newTestDb(t) |
|
439 |
+ defer destroyTestDb(db) |
|
440 |
+ |
|
441 |
+ db.Set("/webapp", "1") |
|
442 |
+ |
|
443 |
+ db.Set("/db", "2") |
|
444 |
+ db.Set("/webapp/db", "2") |
|
445 |
+ |
|
446 |
+ refs := db.RefPaths("2") |
|
447 |
+ if len(refs) != 2 { |
|
448 |
+ t.Fatalf("Expected reference count to be 2, got %d", len(refs)) |
|
449 |
+ } |
|
450 |
+ |
|
451 |
+} |
0 | 452 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,27 @@ |
0 |
+package gograph |
|
1 |
+ |
|
2 |
+import "sort" |
|
3 |
+ |
|
4 |
+type pathSorter struct { |
|
5 |
+ paths []string |
|
6 |
+ by func(i, j string) bool |
|
7 |
+} |
|
8 |
+ |
|
9 |
+func sortByDepth(paths []string) { |
|
10 |
+ s := &pathSorter{paths, func(i, j string) bool { |
|
11 |
+ return pathDepth(i) > pathDepth(j) |
|
12 |
+ }} |
|
13 |
+ sort.Sort(s) |
|
14 |
+} |
|
15 |
+ |
|
16 |
+func (s *pathSorter) Len() int { |
|
17 |
+ return len(s.paths) |
|
18 |
+} |
|
19 |
+ |
|
20 |
+func (s *pathSorter) Swap(i, j int) { |
|
21 |
+ s.paths[i], s.paths[j] = s.paths[j], s.paths[i] |
|
22 |
+} |
|
23 |
+ |
|
24 |
+func (s *pathSorter) Less(i, j int) bool { |
|
25 |
+ return s.by(s.paths[i], s.paths[j]) |
|
26 |
+} |
0 | 27 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,29 @@ |
0 |
+package gograph |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "testing" |
|
4 |
+) |
|
5 |
+ |
|
6 |
+func TestSort(t *testing.T) { |
|
7 |
+ paths := []string{ |
|
8 |
+ "/", |
|
9 |
+ "/myreallylongname", |
|
10 |
+ "/app/db", |
|
11 |
+ } |
|
12 |
+ |
|
13 |
+ sortByDepth(paths) |
|
14 |
+ |
|
15 |
+ if len(paths) != 3 { |
|
16 |
+ t.Fatalf("Expected 3 parts got %d", len(paths)) |
|
17 |
+ } |
|
18 |
+ |
|
19 |
+ if paths[0] != "/app/db" { |
|
20 |
+ t.Fatalf("Expected /app/db got %s", paths[0]) |
|
21 |
+ } |
|
22 |
+ if paths[1] != "/myreallylongname" { |
|
23 |
+ t.Fatalf("Expected /myreallylongname got %s", paths[1]) |
|
24 |
+ } |
|
25 |
+ if paths[2] != "/" { |
|
26 |
+ t.Fatalf("Expected / got %s", paths[2]) |
|
27 |
+ } |
|
28 |
+} |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,32 @@ |
0 |
+package gograph |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "path" |
|
4 |
+ "strings" |
|
5 |
+) |
|
6 |
+ |
|
7 |
+// Split p on / |
|
8 |
+func split(p string) []string { |
|
9 |
+ return strings.Split(p, "/") |
|
10 |
+} |
|
11 |
+ |
|
12 |
+// Returns the depth or number of / in a given path |
|
13 |
+func pathDepth(p string) int { |
|
14 |
+ parts := split(p) |
|
15 |
+ if len(parts) == 2 && parts[1] == "" { |
|
16 |
+ return 1 |
|
17 |
+ } |
|
18 |
+ return len(parts) |
|
19 |
+} |
|
20 |
+ |
|
21 |
+func splitPath(p string) (parent, name string) { |
|
22 |
+ if p[0] != '/' { |
|
23 |
+ p = "/" + p |
|
24 |
+ } |
|
25 |
+ parent, name = path.Split(p) |
|
26 |
+ l := len(parent) |
|
27 |
+ if parent[l-1] == '/' { |
|
28 |
+ parent = parent[:l-1] |
|
29 |
+ } |
|
30 |
+ return |
|
31 |
+} |
... | ... |
@@ -45,7 +45,8 @@ if [ -n "$(git status --porcelain)" ]; then |
45 | 45 |
fi |
46 | 46 |
|
47 | 47 |
# Use these flags when compiling the tests and final binary |
48 |
-LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" |
|
48 |
+LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' |
|
49 |
+BUILDFLAGS='-tags netgo' |
|
49 | 50 |
|
50 | 51 |
|
51 | 52 |
bundle() { |
... | ... |
@@ -2,6 +2,6 @@ |
2 | 2 |
|
3 | 3 |
DEST=$1 |
4 | 4 |
|
5 |
-if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then |
|
6 |
- echo "Created binary: $DEST/docker-$VERSION" |
|
7 |
-fi |
|
5 |
+go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker |
|
6 |
+ |
|
7 |
+echo "Created binary: $DEST/docker-$VERSION" |
0 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,105 @@ |
0 |
+package iptables |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "errors" |
|
4 |
+ "fmt" |
|
5 |
+ "net" |
|
6 |
+ "os/exec" |
|
7 |
+ "strconv" |
|
8 |
+ "strings" |
|
9 |
+) |
|
10 |
+ |
|
11 |
+type Action string |
|
12 |
+ |
|
13 |
+const ( |
|
14 |
+ Add Action = "-A" |
|
15 |
+ Delete Action = "-D" |
|
16 |
+) |
|
17 |
+ |
|
18 |
+var ( |
|
19 |
+ ErrIptablesNotFound = errors.New("Iptables not found") |
|
20 |
+ nat = []string{"-t", "nat"} |
|
21 |
+) |
|
22 |
+ |
|
23 |
+type Chain struct { |
|
24 |
+ Name string |
|
25 |
+ Bridge string |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func NewChain(name, bridge string) (*Chain, error) { |
|
29 |
+ if err := Raw("-t", "nat", "-N", name); err != nil { |
|
30 |
+ return nil, err |
|
31 |
+ } |
|
32 |
+ chain := &Chain{ |
|
33 |
+ Name: name, |
|
34 |
+ Bridge: bridge, |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil { |
|
38 |
+ return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) |
|
39 |
+ } |
|
40 |
+ if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil { |
|
41 |
+ return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) |
|
42 |
+ } |
|
43 |
+ return chain, nil |
|
44 |
+} |
|
45 |
+ |
|
46 |
+func RemoveExistingChain(name string) error { |
|
47 |
+ chain := &Chain{ |
|
48 |
+ Name: name, |
|
49 |
+ } |
|
50 |
+ return chain.Remove() |
|
51 |
+} |
|
52 |
+ |
|
53 |
+func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error { |
|
54 |
+ return Raw("-t", "nat", fmt.Sprint(action), c.Name, |
|
55 |
+ "-p", proto, |
|
56 |
+ "-d", ip.String(), |
|
57 |
+ "--dport", strconv.Itoa(port), |
|
58 |
+ "!", "-i", c.Bridge, |
|
59 |
+ "-j", "DNAT", |
|
60 |
+ "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) |
|
61 |
+} |
|
62 |
+ |
|
63 |
+func (c *Chain) Prerouting(action Action, args ...string) error { |
|
64 |
+ a := append(nat, fmt.Sprint(action), "PREROUTING") |
|
65 |
+ if len(args) > 0 { |
|
66 |
+ a = append(a, args...) |
|
67 |
+ } |
|
68 |
+ return Raw(append(a, "-j", c.Name)...) |
|
69 |
+} |
|
70 |
+ |
|
71 |
+func (c *Chain) Output(action Action, args ...string) error { |
|
72 |
+ a := append(nat, fmt.Sprint(action), "OUTPUT") |
|
73 |
+ if len(args) > 0 { |
|
74 |
+ a = append(a, args...) |
|
75 |
+ } |
|
76 |
+ return Raw(append(a, "-j", c.Name)...) |
|
77 |
+} |
|
78 |
+ |
|
79 |
+func (c *Chain) Remove() error { |
|
80 |
+ // Ignore errors - This could mean the chains were never set up |
|
81 |
+ c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL") |
|
82 |
+ c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8") |
|
83 |
+ c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6 |
|
84 |
+ |
|
85 |
+ c.Prerouting(Delete) |
|
86 |
+ c.Output(Delete) |
|
87 |
+ |
|
88 |
+ Raw("-t", "nat", "-F", c.Name) |
|
89 |
+ Raw("-t", "nat", "-X", c.Name) |
|
90 |
+ |
|
91 |
+ return nil |
|
92 |
+} |
|
93 |
+ |
|
94 |
+func Raw(args ...string) error { |
|
95 |
+ path, err := exec.LookPath("iptables") |
|
96 |
+ if err != nil { |
|
97 |
+ return ErrIptablesNotFound |
|
98 |
+ } |
|
99 |
+ if err := exec.Command(path, args...).Run(); err != nil { |
|
100 |
+ return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) |
|
101 |
+ } |
|
102 |
+ return nil |
|
103 |
+ |
|
104 |
+} |
0 | 105 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
0 |
+package iptables |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "os" |
|
4 |
+ "testing" |
|
5 |
+) |
|
6 |
+ |
|
7 |
+func TestIptables(t *testing.T) { |
|
8 |
+ if err := Raw("-L"); err != nil { |
|
9 |
+ t.Fatal(err) |
|
10 |
+ } |
|
11 |
+ path := os.Getenv("PATH") |
|
12 |
+ os.Setenv("PATH", "") |
|
13 |
+ defer os.Setenv("PATH", path) |
|
14 |
+ if err := Raw("-L"); err == nil { |
|
15 |
+ t.Fatal("Not finding iptables in the PATH should cause an error") |
|
16 |
+ } |
|
17 |
+} |
0 | 18 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,141 @@ |
0 |
+package docker |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "github.com/dotcloud/docker/iptables" |
|
5 |
+ "path" |
|
6 |
+ "strings" |
|
7 |
+) |
|
8 |
+ |
|
9 |
+type Link struct { |
|
10 |
+ ParentIP string |
|
11 |
+ ChildIP string |
|
12 |
+ Name string |
|
13 |
+ BridgeInterface string |
|
14 |
+ ChildEnvironment []string |
|
15 |
+ Ports []Port |
|
16 |
+ IsEnabled bool |
|
17 |
+} |
|
18 |
+ |
|
19 |
+func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) { |
|
20 |
+ if parent.ID == child.ID { |
|
21 |
+ return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID) |
|
22 |
+ } |
|
23 |
+ if !child.State.Running { |
|
24 |
+ return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.ID, name) |
|
25 |
+ } |
|
26 |
+ |
|
27 |
+ ports := make([]Port, len(child.Config.ExposedPorts)) |
|
28 |
+ var i int |
|
29 |
+ for p := range child.Config.ExposedPorts { |
|
30 |
+ ports[i] = p |
|
31 |
+ i++ |
|
32 |
+ } |
|
33 |
+ |
|
34 |
+ l := &Link{ |
|
35 |
+ BridgeInterface: bridgeInterface, |
|
36 |
+ Name: name, |
|
37 |
+ ChildIP: child.NetworkSettings.IPAddress, |
|
38 |
+ ParentIP: parent.NetworkSettings.IPAddress, |
|
39 |
+ ChildEnvironment: child.Config.Env, |
|
40 |
+ Ports: ports, |
|
41 |
+ } |
|
42 |
+ return l, nil |
|
43 |
+ |
|
44 |
+} |
|
45 |
+ |
|
46 |
+func (l *Link) Alias() string { |
|
47 |
+ _, alias := path.Split(l.Name) |
|
48 |
+ return alias |
|
49 |
+} |
|
50 |
+ |
|
51 |
+func (l *Link) ToEnv() []string { |
|
52 |
+ env := []string{} |
|
53 |
+ alias := strings.ToUpper(l.Alias()) |
|
54 |
+ |
|
55 |
+ if p := l.getDefaultPort(); p != nil { |
|
56 |
+ env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port())) |
|
57 |
+ } |
|
58 |
+ |
|
59 |
+ // Load exposed ports into the environment |
|
60 |
+ for _, p := range l.Ports { |
|
61 |
+ env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port())) |
|
62 |
+ } |
|
63 |
+ |
|
64 |
+ // Load the linked container's name into the environment |
|
65 |
+ env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name)) |
|
66 |
+ |
|
67 |
+ if l.ChildEnvironment != nil { |
|
68 |
+ for _, v := range l.ChildEnvironment { |
|
69 |
+ parts := strings.Split(v, "=") |
|
70 |
+ if len(parts) != 2 { |
|
71 |
+ continue |
|
72 |
+ } |
|
73 |
+ // Ignore a few variables that are added during docker build |
|
74 |
+ if parts[0] == "HOME" || parts[0] == "PATH" { |
|
75 |
+ continue |
|
76 |
+ } |
|
77 |
+ env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1])) |
|
78 |
+ } |
|
79 |
+ } |
|
80 |
+ return env |
|
81 |
+} |
|
82 |
+ |
|
83 |
+// Default port rules |
|
84 |
+func (l *Link) getDefaultPort() *Port { |
|
85 |
+ var p Port |
|
86 |
+ i := len(l.Ports) |
|
87 |
+ |
|
88 |
+ if i == 0 { |
|
89 |
+ return nil |
|
90 |
+ } else if i > 1 { |
|
91 |
+ sortPorts(l.Ports, func(ip, jp Port) bool { |
|
92 |
+ // If the two ports have the same number, tcp takes priority |
|
93 |
+ // Sort in desc order |
|
94 |
+ return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp") |
|
95 |
+ }) |
|
96 |
+ } |
|
97 |
+ p = l.Ports[0] |
|
98 |
+ return &p |
|
99 |
+} |
|
100 |
+ |
|
101 |
+func (l *Link) Enable() error { |
|
102 |
+ if err := l.toggle("-I", false); err != nil { |
|
103 |
+ return err |
|
104 |
+ } |
|
105 |
+ l.IsEnabled = true |
|
106 |
+ return nil |
|
107 |
+} |
|
108 |
+ |
|
109 |
+func (l *Link) Disable() { |
|
110 |
+ // We do not care about errors here because the link may not |
|
111 |
+ // exist in iptables |
|
112 |
+ l.toggle("-D", true) |
|
113 |
+ |
|
114 |
+ l.IsEnabled = false |
|
115 |
+} |
|
116 |
+ |
|
117 |
+func (l *Link) toggle(action string, ignoreErrors bool) error { |
|
118 |
+ for _, p := range l.Ports { |
|
119 |
+ if err := iptables.Raw(action, "FORWARD", |
|
120 |
+ "-i", l.BridgeInterface, "-o", l.BridgeInterface, |
|
121 |
+ "-p", p.Proto(), |
|
122 |
+ "-s", l.ParentIP, |
|
123 |
+ "--dport", p.Port(), |
|
124 |
+ "-d", l.ChildIP, |
|
125 |
+ "-j", "ACCEPT"); !ignoreErrors && err != nil { |
|
126 |
+ return err |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ if err := iptables.Raw(action, "FORWARD", |
|
130 |
+ "-i", l.BridgeInterface, "-o", l.BridgeInterface, |
|
131 |
+ "-p", p.Proto(), |
|
132 |
+ "-s", l.ChildIP, |
|
133 |
+ "--sport", p.Port(), |
|
134 |
+ "-d", l.ParentIP, |
|
135 |
+ "-j", "ACCEPT"); !ignoreErrors && err != nil { |
|
136 |
+ return err |
|
137 |
+ } |
|
138 |
+ } |
|
139 |
+ return nil |
|
140 |
+} |
0 | 141 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,104 @@ |
0 |
+package docker |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "strings" |
|
4 |
+ "testing" |
|
5 |
+) |
|
6 |
+ |
|
7 |
+func newMockLinkContainer(id string, ip string) *Container { |
|
8 |
+ return &Container{ |
|
9 |
+ Config: &Config{}, |
|
10 |
+ ID: id, |
|
11 |
+ NetworkSettings: &NetworkSettings{ |
|
12 |
+ IPAddress: ip, |
|
13 |
+ }, |
|
14 |
+ } |
|
15 |
+} |
|
16 |
+ |
|
17 |
+func TestLinkNew(t *testing.T) { |
|
18 |
+ toID := GenerateID() |
|
19 |
+ fromID := GenerateID() |
|
20 |
+ |
|
21 |
+ from := newMockLinkContainer(fromID, "172.0.17.2") |
|
22 |
+ from.Config.Env = []string{} |
|
23 |
+ from.State = State{Running: true} |
|
24 |
+ ports := make(map[Port]struct{}) |
|
25 |
+ |
|
26 |
+ ports[Port("6379/tcp")] = struct{}{} |
|
27 |
+ |
|
28 |
+ from.Config.ExposedPorts = ports |
|
29 |
+ |
|
30 |
+ to := newMockLinkContainer(toID, "172.0.17.3") |
|
31 |
+ |
|
32 |
+ link, err := NewLink(to, from, "/db/docker", "172.0.17.1") |
|
33 |
+ if err != nil { |
|
34 |
+ t.Fatal(err) |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ if link == nil { |
|
38 |
+ t.FailNow() |
|
39 |
+ } |
|
40 |
+ if link.Name != "/db/docker" { |
|
41 |
+ t.Fail() |
|
42 |
+ } |
|
43 |
+ if link.Alias() != "docker" { |
|
44 |
+ t.Fail() |
|
45 |
+ } |
|
46 |
+ if link.ParentIP != "172.0.17.3" { |
|
47 |
+ t.Fail() |
|
48 |
+ } |
|
49 |
+ if link.ChildIP != "172.0.17.2" { |
|
50 |
+ t.Fail() |
|
51 |
+ } |
|
52 |
+ if link.BridgeInterface != "172.0.17.1" { |
|
53 |
+ t.Fail() |
|
54 |
+ } |
|
55 |
+ for _, p := range link.Ports { |
|
56 |
+ if p != Port("6379/tcp") { |
|
57 |
+ t.Fail() |
|
58 |
+ } |
|
59 |
+ } |
|
60 |
+} |
|
61 |
+ |
|
62 |
+func TestLinkEnv(t *testing.T) { |
|
63 |
+ toID := GenerateID() |
|
64 |
+ fromID := GenerateID() |
|
65 |
+ |
|
66 |
+ from := newMockLinkContainer(fromID, "172.0.17.2") |
|
67 |
+ from.Config.Env = []string{"PASSWORD=gordon"} |
|
68 |
+ from.State = State{Running: true} |
|
69 |
+ ports := make(map[Port]struct{}) |
|
70 |
+ |
|
71 |
+ ports[Port("6379/tcp")] = struct{}{} |
|
72 |
+ |
|
73 |
+ from.Config.ExposedPorts = ports |
|
74 |
+ |
|
75 |
+ to := newMockLinkContainer(toID, "172.0.17.3") |
|
76 |
+ |
|
77 |
+ link, err := NewLink(to, from, "/db/docker", "172.0.17.1") |
|
78 |
+ if err != nil { |
|
79 |
+ t.Fatal(err) |
|
80 |
+ } |
|
81 |
+ |
|
82 |
+ rawEnv := link.ToEnv() |
|
83 |
+ env := make(map[string]string, len(rawEnv)) |
|
84 |
+ for _, e := range rawEnv { |
|
85 |
+ parts := strings.Split(e, "=") |
|
86 |
+ if len(parts) != 2 { |
|
87 |
+ t.FailNow() |
|
88 |
+ } |
|
89 |
+ env[parts[0]] = parts[1] |
|
90 |
+ } |
|
91 |
+ if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" { |
|
92 |
+ t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"]) |
|
93 |
+ } |
|
94 |
+ if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" { |
|
95 |
+ t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"]) |
|
96 |
+ } |
|
97 |
+ if env["DOCKER_NAME"] != "/db/docker" { |
|
98 |
+ t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"]) |
|
99 |
+ } |
|
100 |
+ if env["DOCKER_ENV_PASSWORD"] != "gordon" { |
|
101 |
+ t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"]) |
|
102 |
+ } |
|
103 |
+} |
... | ... |
@@ -4,6 +4,8 @@ import ( |
4 | 4 |
"encoding/binary" |
5 | 5 |
"errors" |
6 | 6 |
"fmt" |
7 |
+ "github.com/dotcloud/docker/iptables" |
|
8 |
+ "github.com/dotcloud/docker/proxy" |
|
7 | 9 |
"github.com/dotcloud/docker/utils" |
8 | 10 |
"log" |
9 | 11 |
"net" |
... | ... |
@@ -13,8 +15,6 @@ import ( |
13 | 13 |
"sync" |
14 | 14 |
) |
15 | 15 |
|
16 |
-var NetworkBridgeIface string |
|
17 |
- |
|
18 | 16 |
const ( |
19 | 17 |
DefaultNetworkBridge = "docker0" |
20 | 18 |
DisableNetworkBridge = "none" |
... | ... |
@@ -81,18 +81,6 @@ func ip(args ...string) (string, error) { |
81 | 81 |
return string(output), nil |
82 | 82 |
} |
83 | 83 |
|
84 |
-// Wrapper around the iptables command |
|
85 |
-func iptables(args ...string) error { |
|
86 |
- path, err := exec.LookPath("iptables") |
|
87 |
- if err != nil { |
|
88 |
- return fmt.Errorf("command not found: iptables") |
|
89 |
- } |
|
90 |
- if err := exec.Command(path, args...).Run(); err != nil { |
|
91 |
- return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) |
|
92 |
- } |
|
93 |
- return nil |
|
94 |
-} |
|
95 |
- |
|
96 | 84 |
func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { |
97 | 85 |
utils.Debugf("Routes:\n\n%s", routes) |
98 | 86 |
for _, line := range strings.Split(routes, "\n") { |
... | ... |
@@ -124,7 +112,7 @@ func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { |
124 | 124 |
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, |
125 | 125 |
// and attempts to configure it with an address which doesn't conflict with any other interface on the host. |
126 | 126 |
// If it can't find an address which doesn't conflict, it will return an error. |
127 |
-func CreateBridgeIface(ifaceName string) error { |
|
127 |
+func CreateBridgeIface(config *DaemonConfig) error { |
|
128 | 128 |
addrs := []string{ |
129 | 129 |
// Here we don't follow the convention of using the 1st IP of the range for the gateway. |
130 | 130 |
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. |
... | ... |
@@ -163,23 +151,29 @@ func CreateBridgeIface(ifaceName string) error { |
163 | 163 |
} |
164 | 164 |
} |
165 | 165 |
if ifaceAddr == "" { |
166 |
- return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName) |
|
166 |
+ return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface) |
|
167 | 167 |
} |
168 |
- utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr) |
|
168 |
+ utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr) |
|
169 | 169 |
|
170 |
- if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil { |
|
170 |
+ if output, err := ip("link", "add", config.BridgeIface, "type", "bridge"); err != nil { |
|
171 | 171 |
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output) |
172 | 172 |
} |
173 | 173 |
|
174 |
- if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil { |
|
174 |
+ if output, err := ip("addr", "add", ifaceAddr, "dev", config.BridgeIface); err != nil { |
|
175 | 175 |
return fmt.Errorf("Unable to add private network: %s (%s)", err, output) |
176 | 176 |
} |
177 |
- if output, err := ip("link", "set", ifaceName, "up"); err != nil { |
|
177 |
+ if output, err := ip("link", "set", config.BridgeIface, "up"); err != nil { |
|
178 | 178 |
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output) |
179 | 179 |
} |
180 |
- if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, |
|
181 |
- "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { |
|
182 |
- return fmt.Errorf("Unable to enable network bridge NAT: %s", err) |
|
180 |
+ if config.EnableIptables { |
|
181 |
+ if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, |
|
182 |
+ "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { |
|
183 |
+ return fmt.Errorf("Unable to enable network bridge NAT: %s", err) |
|
184 |
+ } |
|
185 |
+ // Prevent inter-container communication by default |
|
186 |
+ if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil { |
|
187 |
+ return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) |
|
188 |
+ } |
|
183 | 189 |
} |
184 | 190 |
return nil |
185 | 191 |
} |
... | ... |
@@ -216,58 +210,27 @@ func getIfaceAddr(name string) (net.Addr, error) { |
216 | 216 |
// It keeps track of all mappings and is able to unmap at will |
217 | 217 |
type PortMapper struct { |
218 | 218 |
tcpMapping map[int]*net.TCPAddr |
219 |
- tcpProxies map[int]Proxy |
|
219 |
+ tcpProxies map[int]proxy.Proxy |
|
220 | 220 |
udpMapping map[int]*net.UDPAddr |
221 |
- udpProxies map[int]Proxy |
|
222 |
-} |
|
223 |
- |
|
224 |
-func (mapper *PortMapper) cleanup() error { |
|
225 |
- // Ignore errors - This could mean the chains were never set up |
|
226 |
- iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") |
|
227 |
- iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER") |
|
228 |
- iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6 |
|
229 |
- // Also cleanup rules created by older versions, or -X might fail. |
|
230 |
- iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER") |
|
231 |
- iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") |
|
232 |
- iptables("-t", "nat", "-F", "DOCKER") |
|
233 |
- iptables("-t", "nat", "-X", "DOCKER") |
|
234 |
- mapper.tcpMapping = make(map[int]*net.TCPAddr) |
|
235 |
- mapper.tcpProxies = make(map[int]Proxy) |
|
236 |
- mapper.udpMapping = make(map[int]*net.UDPAddr) |
|
237 |
- mapper.udpProxies = make(map[int]Proxy) |
|
238 |
- return nil |
|
239 |
-} |
|
240 |
- |
|
241 |
-func (mapper *PortMapper) setup() error { |
|
242 |
- if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil { |
|
243 |
- return fmt.Errorf("Failed to create DOCKER chain: %s", err) |
|
244 |
- } |
|
245 |
- if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { |
|
246 |
- return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) |
|
247 |
- } |
|
248 |
- if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil { |
|
249 |
- return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) |
|
250 |
- } |
|
251 |
- return nil |
|
252 |
-} |
|
221 |
+ udpProxies map[int]proxy.Proxy |
|
253 | 222 |
|
254 |
-func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error { |
|
255 |
- return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port), |
|
256 |
- "!", "-i", NetworkBridgeIface, |
|
257 |
- "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) |
|
223 |
+ iptables *iptables.Chain |
|
224 |
+ defaultIp net.IP |
|
258 | 225 |
} |
259 | 226 |
|
260 |
-func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { |
|
227 |
+func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error { |
|
261 | 228 |
if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { |
262 | 229 |
backendPort := backendAddr.(*net.TCPAddr).Port |
263 | 230 |
backendIP := backendAddr.(*net.TCPAddr).IP |
264 |
- if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil { |
|
265 |
- return err |
|
231 |
+ if mapper.iptables != nil { |
|
232 |
+ if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil { |
|
233 |
+ return err |
|
234 |
+ } |
|
266 | 235 |
} |
267 | 236 |
mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) |
268 |
- proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) |
|
237 |
+ proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr) |
|
269 | 238 |
if err != nil { |
270 |
- mapper.Unmap(port, "tcp") |
|
239 |
+ mapper.Unmap(ip, port, "tcp") |
|
271 | 240 |
return err |
272 | 241 |
} |
273 | 242 |
mapper.tcpProxies[port] = proxy |
... | ... |
@@ -275,13 +238,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { |
275 | 275 |
} else { |
276 | 276 |
backendPort := backendAddr.(*net.UDPAddr).Port |
277 | 277 |
backendIP := backendAddr.(*net.UDPAddr).IP |
278 |
- if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil { |
|
279 |
- return err |
|
278 |
+ if mapper.iptables != nil { |
|
279 |
+ if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil { |
|
280 |
+ return err |
|
281 |
+ } |
|
280 | 282 |
} |
281 | 283 |
mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) |
282 |
- proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) |
|
284 |
+ proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr) |
|
283 | 285 |
if err != nil { |
284 |
- mapper.Unmap(port, "udp") |
|
286 |
+ mapper.Unmap(ip, port, "udp") |
|
285 | 287 |
return err |
286 | 288 |
} |
287 | 289 |
mapper.udpProxies[port] = proxy |
... | ... |
@@ -290,7 +255,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { |
290 | 290 |
return nil |
291 | 291 |
} |
292 | 292 |
|
293 |
-func (mapper *PortMapper) Unmap(port int, proto string) error { |
|
293 |
+func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error { |
|
294 | 294 |
if proto == "tcp" { |
295 | 295 |
backendAddr, ok := mapper.tcpMapping[port] |
296 | 296 |
if !ok { |
... | ... |
@@ -300,8 +265,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { |
300 | 300 |
proxy.Close() |
301 | 301 |
delete(mapper.tcpProxies, port) |
302 | 302 |
} |
303 |
- if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { |
|
304 |
- return err |
|
303 |
+ if mapper.iptables != nil { |
|
304 |
+ if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { |
|
305 |
+ return err |
|
306 |
+ } |
|
305 | 307 |
} |
306 | 308 |
delete(mapper.tcpMapping, port) |
307 | 309 |
} else { |
... | ... |
@@ -313,21 +280,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { |
313 | 313 |
proxy.Close() |
314 | 314 |
delete(mapper.udpProxies, port) |
315 | 315 |
} |
316 |
- if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { |
|
317 |
- return err |
|
316 |
+ if mapper.iptables != nil { |
|
317 |
+ if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { |
|
318 |
+ return err |
|
319 |
+ } |
|
318 | 320 |
} |
319 | 321 |
delete(mapper.udpMapping, port) |
320 | 322 |
} |
321 | 323 |
return nil |
322 | 324 |
} |
323 | 325 |
|
324 |
-func newPortMapper() (*PortMapper, error) { |
|
325 |
- mapper := &PortMapper{} |
|
326 |
- if err := mapper.cleanup(); err != nil { |
|
326 |
+func newPortMapper(config *DaemonConfig) (*PortMapper, error) { |
|
327 |
+ // We can always try removing the iptables |
|
328 |
+ if err := iptables.RemoveExistingChain("DOCKER"); err != nil { |
|
327 | 329 |
return nil, err |
328 | 330 |
} |
329 |
- if err := mapper.setup(); err != nil { |
|
330 |
- return nil, err |
|
331 |
+ var chain *iptables.Chain |
|
332 |
+ if config.EnableIptables { |
|
333 |
+ var err error |
|
334 |
+ chain, err = iptables.NewChain("DOCKER", config.BridgeIface) |
|
335 |
+ if err != nil { |
|
336 |
+ return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err) |
|
337 |
+ } |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ mapper := &PortMapper{ |
|
341 |
+ tcpMapping: make(map[int]*net.TCPAddr), |
|
342 |
+ tcpProxies: make(map[int]proxy.Proxy), |
|
343 |
+ udpMapping: make(map[int]*net.UDPAddr), |
|
344 |
+ udpProxies: make(map[int]proxy.Proxy), |
|
345 |
+ iptables: chain, |
|
346 |
+ defaultIp: config.DefaultIp, |
|
331 | 347 |
} |
332 | 348 |
return mapper, nil |
333 | 349 |
} |
... | ... |
@@ -519,40 +502,56 @@ type NetworkInterface struct { |
519 | 519 |
disabled bool |
520 | 520 |
} |
521 | 521 |
|
522 |
-// Allocate an external TCP port and map it to the interface |
|
523 |
-func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { |
|
522 |
+// Allocate an external port and map it to the interface |
|
523 |
+func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) { |
|
524 | 524 |
|
525 | 525 |
if iface.disabled { |
526 | 526 |
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME |
527 | 527 |
} |
528 | 528 |
|
529 |
- nat, err := parseNat(spec) |
|
529 |
+ ip := iface.manager.portMapper.defaultIp |
|
530 |
+ |
|
531 |
+ if binding.HostIp != "" { |
|
532 |
+ ip = net.ParseIP(binding.HostIp) |
|
533 |
+ } else { |
|
534 |
+ binding.HostIp = ip.String() |
|
535 |
+ } |
|
536 |
+ |
|
537 |
+ nat := &Nat{ |
|
538 |
+ Port: port, |
|
539 |
+ Binding: binding, |
|
540 |
+ } |
|
541 |
+ |
|
542 |
+ containerPort, err := parsePort(port.Port()) |
|
530 | 543 |
if err != nil { |
531 | 544 |
return nil, err |
532 | 545 |
} |
533 | 546 |
|
534 |
- if nat.Proto == "tcp" { |
|
535 |
- extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend) |
|
547 |
+ hostPort, _ := parsePort(nat.Binding.HostPort) |
|
548 |
+ |
|
549 |
+ if nat.Port.Proto() == "tcp" { |
|
550 |
+ extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort) |
|
536 | 551 |
if err != nil { |
537 | 552 |
return nil, err |
538 | 553 |
} |
539 |
- backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend} |
|
540 |
- if err := iface.manager.portMapper.Map(extPort, backend); err != nil { |
|
554 |
+ |
|
555 |
+ backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} |
|
556 |
+ if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { |
|
541 | 557 |
iface.manager.tcpPortAllocator.Release(extPort) |
542 | 558 |
return nil, err |
543 | 559 |
} |
544 |
- nat.Frontend = extPort |
|
560 |
+ nat.Binding.HostPort = strconv.Itoa(extPort) |
|
545 | 561 |
} else { |
546 |
- extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend) |
|
562 |
+ extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort) |
|
547 | 563 |
if err != nil { |
548 | 564 |
return nil, err |
549 | 565 |
} |
550 |
- backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend} |
|
551 |
- if err := iface.manager.portMapper.Map(extPort, backend); err != nil { |
|
566 |
+ backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} |
|
567 |
+ if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { |
|
552 | 568 |
iface.manager.udpPortAllocator.Release(extPort) |
553 | 569 |
return nil, err |
554 | 570 |
} |
555 |
- nat.Frontend = extPort |
|
571 |
+ nat.Binding.HostPort = strconv.Itoa(extPort) |
|
556 | 572 |
} |
557 | 573 |
iface.extPorts = append(iface.extPorts, nat) |
558 | 574 |
|
... | ... |
@@ -560,83 +559,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { |
560 | 560 |
} |
561 | 561 |
|
562 | 562 |
type Nat struct { |
563 |
- Proto string |
|
564 |
- Frontend int |
|
565 |
- Backend int |
|
563 |
+ Port Port |
|
564 |
+ Binding PortBinding |
|
566 | 565 |
} |
567 | 566 |
|
568 |
-func parseNat(spec string) (*Nat, error) { |
|
569 |
- var nat Nat |
|
570 |
- |
|
571 |
- if strings.Contains(spec, "/") { |
|
572 |
- specParts := strings.Split(spec, "/") |
|
573 |
- if len(specParts) != 2 { |
|
574 |
- return nil, fmt.Errorf("Invalid port format.") |
|
575 |
- } |
|
576 |
- proto := specParts[1] |
|
577 |
- spec = specParts[0] |
|
578 |
- if proto != "tcp" && proto != "udp" { |
|
579 |
- return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto) |
|
580 |
- } |
|
581 |
- nat.Proto = proto |
|
582 |
- } else { |
|
583 |
- nat.Proto = "tcp" |
|
584 |
- } |
|
585 |
- |
|
586 |
- if strings.Contains(spec, ":") { |
|
587 |
- specParts := strings.Split(spec, ":") |
|
588 |
- if len(specParts) != 2 { |
|
589 |
- return nil, fmt.Errorf("Invalid port format.") |
|
590 |
- } |
|
591 |
- // If spec starts with ':', external and internal ports must be the same. |
|
592 |
- // This might fail if the requested external port is not available. |
|
593 |
- var sameFrontend bool |
|
594 |
- if len(specParts[0]) == 0 { |
|
595 |
- sameFrontend = true |
|
596 |
- } else { |
|
597 |
- front, err := strconv.ParseUint(specParts[0], 10, 16) |
|
598 |
- if err != nil { |
|
599 |
- return nil, err |
|
600 |
- } |
|
601 |
- nat.Frontend = int(front) |
|
602 |
- } |
|
603 |
- back, err := strconv.ParseUint(specParts[1], 10, 16) |
|
604 |
- if err != nil { |
|
605 |
- return nil, err |
|
606 |
- } |
|
607 |
- nat.Backend = int(back) |
|
608 |
- if sameFrontend { |
|
609 |
- nat.Frontend = nat.Backend |
|
610 |
- } |
|
611 |
- } else { |
|
612 |
- port, err := strconv.ParseUint(spec, 10, 16) |
|
613 |
- if err != nil { |
|
614 |
- return nil, err |
|
615 |
- } |
|
616 |
- nat.Backend = int(port) |
|
617 |
- } |
|
618 |
- |
|
619 |
- return &nat, nil |
|
567 |
+func (n *Nat) String() string { |
|
568 |
+ return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto()) |
|
620 | 569 |
} |
621 | 570 |
|
622 | 571 |
// Release: Network cleanup - release all resources |
623 | 572 |
func (iface *NetworkInterface) Release() { |
624 |
- |
|
625 | 573 |
if iface.disabled { |
626 | 574 |
return |
627 | 575 |
} |
628 | 576 |
|
629 | 577 |
for _, nat := range iface.extPorts { |
630 |
- utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) |
|
631 |
- if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { |
|
632 |
- log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err) |
|
578 |
+ hostPort, err := parsePort(nat.Binding.HostPort) |
|
579 |
+ if err != nil { |
|
580 |
+ log.Printf("Unable to get host port: %s", err) |
|
581 |
+ continue |
|
582 |
+ } |
|
583 |
+ ip := net.ParseIP(nat.Binding.HostIp) |
|
584 |
+ utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort) |
|
585 |
+ if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil { |
|
586 |
+ log.Printf("Unable to unmap port %s: %s", nat, err) |
|
633 | 587 |
} |
634 |
- if nat.Proto == "tcp" { |
|
635 |
- if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil { |
|
636 |
- log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err) |
|
588 |
+ if nat.Port.Proto() == "tcp" { |
|
589 |
+ if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil { |
|
590 |
+ log.Printf("Unable to release port %s", nat) |
|
637 | 591 |
} |
638 |
- } else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil { |
|
639 |
- log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err) |
|
592 |
+ } else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil { |
|
593 |
+ log.Printf("Unable to release port %s: %s", nat, err) |
|
640 | 594 |
} |
641 | 595 |
} |
642 | 596 |
|
... | ... |
@@ -704,22 +657,21 @@ func (manager *NetworkManager) Close() error { |
704 | 704 |
return err3 |
705 | 705 |
} |
706 | 706 |
|
707 |
-func newNetworkManager(bridgeIface string) (*NetworkManager, error) { |
|
708 |
- |
|
709 |
- if bridgeIface == DisableNetworkBridge { |
|
707 |
+func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { |
|
708 |
+ if config.BridgeIface == DisableNetworkBridge { |
|
710 | 709 |
manager := &NetworkManager{ |
711 | 710 |
disabled: true, |
712 | 711 |
} |
713 | 712 |
return manager, nil |
714 | 713 |
} |
715 | 714 |
|
716 |
- addr, err := getIfaceAddr(bridgeIface) |
|
715 |
+ addr, err := getIfaceAddr(config.BridgeIface) |
|
717 | 716 |
if err != nil { |
718 | 717 |
// If the iface is not found, try to create it |
719 |
- if err := CreateBridgeIface(bridgeIface); err != nil { |
|
718 |
+ if err := CreateBridgeIface(config); err != nil { |
|
720 | 719 |
return nil, err |
721 | 720 |
} |
722 |
- addr, err = getIfaceAddr(bridgeIface) |
|
721 |
+ addr, err = getIfaceAddr(config.BridgeIface) |
|
723 | 722 |
if err != nil { |
724 | 723 |
return nil, err |
725 | 724 |
} |
... | ... |
@@ -737,13 +689,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) { |
737 | 737 |
return nil, err |
738 | 738 |
} |
739 | 739 |
|
740 |
- portMapper, err := newPortMapper() |
|
740 |
+ portMapper, err := newPortMapper(config) |
|
741 | 741 |
if err != nil { |
742 | 742 |
return nil, err |
743 | 743 |
} |
744 | 744 |
|
745 | 745 |
manager := &NetworkManager{ |
746 |
- bridgeIface: bridgeIface, |
|
746 |
+ bridgeIface: config.BridgeIface, |
|
747 | 747 |
bridgeNetwork: network, |
748 | 748 |
ipAllocator: ipAllocator, |
749 | 749 |
tcpPortAllocator: tcpPortAllocator, |
750 | 750 |
deleted file mode 100644 |
... | ... |
@@ -1,266 +0,0 @@ |
1 |
-package docker |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "encoding/binary" |
|
5 |
- "fmt" |
|
6 |
- "github.com/dotcloud/docker/utils" |
|
7 |
- "io" |
|
8 |
- "log" |
|
9 |
- "net" |
|
10 |
- "sync" |
|
11 |
- "syscall" |
|
12 |
- "time" |
|
13 |
-) |
|
14 |
- |
|
15 |
-const ( |
|
16 |
- UDPConnTrackTimeout = 90 * time.Second |
|
17 |
- UDPBufSize = 2048 |
|
18 |
-) |
|
19 |
- |
|
20 |
-type Proxy interface { |
|
21 |
- // Start forwarding traffic back and forth the front and back-end |
|
22 |
- // addresses. |
|
23 |
- Run() |
|
24 |
- // Stop forwarding traffic and close both ends of the Proxy. |
|
25 |
- Close() |
|
26 |
- // Return the address on which the proxy is listening. |
|
27 |
- FrontendAddr() net.Addr |
|
28 |
- // Return the proxied address. |
|
29 |
- BackendAddr() net.Addr |
|
30 |
-} |
|
31 |
- |
|
32 |
-type TCPProxy struct { |
|
33 |
- listener *net.TCPListener |
|
34 |
- frontendAddr *net.TCPAddr |
|
35 |
- backendAddr *net.TCPAddr |
|
36 |
-} |
|
37 |
- |
|
38 |
-func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { |
|
39 |
- listener, err := net.ListenTCP("tcp", frontendAddr) |
|
40 |
- if err != nil { |
|
41 |
- return nil, err |
|
42 |
- } |
|
43 |
- // If the port in frontendAddr was 0 then ListenTCP will have a picked |
|
44 |
- // a port to listen on, hence the call to Addr to get that actual port: |
|
45 |
- return &TCPProxy{ |
|
46 |
- listener: listener, |
|
47 |
- frontendAddr: listener.Addr().(*net.TCPAddr), |
|
48 |
- backendAddr: backendAddr, |
|
49 |
- }, nil |
|
50 |
-} |
|
51 |
- |
|
52 |
-func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { |
|
53 |
- backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) |
|
54 |
- if err != nil { |
|
55 |
- log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error()) |
|
56 |
- client.Close() |
|
57 |
- return |
|
58 |
- } |
|
59 |
- |
|
60 |
- event := make(chan int64) |
|
61 |
- var broker = func(to, from *net.TCPConn) { |
|
62 |
- written, err := io.Copy(to, from) |
|
63 |
- if err != nil { |
|
64 |
- // If the socket we are writing to is shutdown with |
|
65 |
- // SHUT_WR, forward it to the other end of the pipe: |
|
66 |
- if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { |
|
67 |
- from.CloseWrite() |
|
68 |
- } |
|
69 |
- } |
|
70 |
- to.CloseRead() |
|
71 |
- event <- written |
|
72 |
- } |
|
73 |
- utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr()) |
|
74 |
- go broker(client, backend) |
|
75 |
- go broker(backend, client) |
|
76 |
- |
|
77 |
- var transferred int64 = 0 |
|
78 |
- for i := 0; i < 2; i++ { |
|
79 |
- select { |
|
80 |
- case written := <-event: |
|
81 |
- transferred += written |
|
82 |
- case <-quit: |
|
83 |
- // Interrupt the two brokers and "join" them. |
|
84 |
- client.Close() |
|
85 |
- backend.Close() |
|
86 |
- for ; i < 2; i++ { |
|
87 |
- transferred += <-event |
|
88 |
- } |
|
89 |
- goto done |
|
90 |
- } |
|
91 |
- } |
|
92 |
- client.Close() |
|
93 |
- backend.Close() |
|
94 |
-done: |
|
95 |
- utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr()) |
|
96 |
-} |
|
97 |
- |
|
98 |
-func (proxy *TCPProxy) Run() { |
|
99 |
- quit := make(chan bool) |
|
100 |
- defer close(quit) |
|
101 |
- |
|
102 |
- utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr) |
|
103 |
- for { |
|
104 |
- client, err := proxy.listener.Accept() |
|
105 |
- if err != nil { |
|
106 |
- if utils.IsClosedError(err) { |
|
107 |
- utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr) |
|
108 |
- } else { |
|
109 |
- utils.Errorf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) |
|
110 |
- } |
|
111 |
- return |
|
112 |
- } |
|
113 |
- go proxy.clientLoop(client.(*net.TCPConn), quit) |
|
114 |
- } |
|
115 |
-} |
|
116 |
- |
|
117 |
-func (proxy *TCPProxy) Close() { proxy.listener.Close() } |
|
118 |
-func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } |
|
119 |
-func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } |
|
120 |
- |
|
121 |
-// A net.Addr where the IP is split into two fields so you can use it as a key |
|
122 |
-// in a map: |
|
123 |
-type connTrackKey struct { |
|
124 |
- IPHigh uint64 |
|
125 |
- IPLow uint64 |
|
126 |
- Port int |
|
127 |
-} |
|
128 |
- |
|
129 |
-func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { |
|
130 |
- if len(addr.IP) == net.IPv4len { |
|
131 |
- return &connTrackKey{ |
|
132 |
- IPHigh: 0, |
|
133 |
- IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), |
|
134 |
- Port: addr.Port, |
|
135 |
- } |
|
136 |
- } |
|
137 |
- return &connTrackKey{ |
|
138 |
- IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), |
|
139 |
- IPLow: binary.BigEndian.Uint64(addr.IP[8:]), |
|
140 |
- Port: addr.Port, |
|
141 |
- } |
|
142 |
-} |
|
143 |
- |
|
144 |
-type connTrackMap map[connTrackKey]*net.UDPConn |
|
145 |
- |
|
146 |
-type UDPProxy struct { |
|
147 |
- listener *net.UDPConn |
|
148 |
- frontendAddr *net.UDPAddr |
|
149 |
- backendAddr *net.UDPAddr |
|
150 |
- connTrackTable connTrackMap |
|
151 |
- connTrackLock sync.Mutex |
|
152 |
-} |
|
153 |
- |
|
154 |
-func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) { |
|
155 |
- listener, err := net.ListenUDP("udp", frontendAddr) |
|
156 |
- if err != nil { |
|
157 |
- return nil, err |
|
158 |
- } |
|
159 |
- return &UDPProxy{ |
|
160 |
- listener: listener, |
|
161 |
- frontendAddr: listener.LocalAddr().(*net.UDPAddr), |
|
162 |
- backendAddr: backendAddr, |
|
163 |
- connTrackTable: make(connTrackMap), |
|
164 |
- }, nil |
|
165 |
-} |
|
166 |
- |
|
167 |
-func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { |
|
168 |
- defer func() { |
|
169 |
- proxy.connTrackLock.Lock() |
|
170 |
- delete(proxy.connTrackTable, *clientKey) |
|
171 |
- proxy.connTrackLock.Unlock() |
|
172 |
- utils.Debugf("Done proxying between udp/%v and udp/%v", clientAddr.String(), proxy.backendAddr.String()) |
|
173 |
- proxyConn.Close() |
|
174 |
- }() |
|
175 |
- |
|
176 |
- readBuf := make([]byte, UDPBufSize) |
|
177 |
- for { |
|
178 |
- proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) |
|
179 |
- again: |
|
180 |
- read, err := proxyConn.Read(readBuf) |
|
181 |
- if err != nil { |
|
182 |
- if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { |
|
183 |
- // This will happen if the last write failed |
|
184 |
- // (e.g: nothing is actually listening on the |
|
185 |
- // proxied port on the container), ignore it |
|
186 |
- // and continue until UDPConnTrackTimeout |
|
187 |
- // expires: |
|
188 |
- goto again |
|
189 |
- } |
|
190 |
- return |
|
191 |
- } |
|
192 |
- for i := 0; i != read; { |
|
193 |
- written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) |
|
194 |
- if err != nil { |
|
195 |
- return |
|
196 |
- } |
|
197 |
- i += written |
|
198 |
- utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, clientAddr.String()) |
|
199 |
- } |
|
200 |
- } |
|
201 |
-} |
|
202 |
- |
|
203 |
-func (proxy *UDPProxy) Run() { |
|
204 |
- readBuf := make([]byte, UDPBufSize) |
|
205 |
- utils.Debugf("Starting proxy on udp/%v for udp/%v", proxy.frontendAddr, proxy.backendAddr) |
|
206 |
- for { |
|
207 |
- read, from, err := proxy.listener.ReadFromUDP(readBuf) |
|
208 |
- if err != nil { |
|
209 |
- // NOTE: Apparently ReadFrom doesn't return |
|
210 |
- // ECONNREFUSED like Read do (see comment in |
|
211 |
- // UDPProxy.replyLoop) |
|
212 |
- if utils.IsClosedError(err) { |
|
213 |
- utils.Debugf("Stopping proxy on udp/%v for udp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr) |
|
214 |
- } else { |
|
215 |
- utils.Errorf("Stopping proxy on udp/%v for udp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) |
|
216 |
- } |
|
217 |
- break |
|
218 |
- } |
|
219 |
- |
|
220 |
- fromKey := newConnTrackKey(from) |
|
221 |
- proxy.connTrackLock.Lock() |
|
222 |
- proxyConn, hit := proxy.connTrackTable[*fromKey] |
|
223 |
- if !hit { |
|
224 |
- proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) |
|
225 |
- if err != nil { |
|
226 |
- log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) |
|
227 |
- continue |
|
228 |
- } |
|
229 |
- proxy.connTrackTable[*fromKey] = proxyConn |
|
230 |
- go proxy.replyLoop(proxyConn, from, fromKey) |
|
231 |
- } |
|
232 |
- proxy.connTrackLock.Unlock() |
|
233 |
- for i := 0; i != read; { |
|
234 |
- written, err := proxyConn.Write(readBuf[i:read]) |
|
235 |
- if err != nil { |
|
236 |
- log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) |
|
237 |
- break |
|
238 |
- } |
|
239 |
- i += written |
|
240 |
- utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, proxy.backendAddr.String()) |
|
241 |
- } |
|
242 |
- } |
|
243 |
-} |
|
244 |
- |
|
245 |
-func (proxy *UDPProxy) Close() { |
|
246 |
- proxy.listener.Close() |
|
247 |
- proxy.connTrackLock.Lock() |
|
248 |
- defer proxy.connTrackLock.Unlock() |
|
249 |
- for _, conn := range proxy.connTrackTable { |
|
250 |
- conn.Close() |
|
251 |
- } |
|
252 |
-} |
|
253 |
- |
|
254 |
-func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } |
|
255 |
-func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } |
|
256 |
- |
|
257 |
-func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { |
|
258 |
- switch frontendAddr.(type) { |
|
259 |
- case *net.UDPAddr: |
|
260 |
- return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) |
|
261 |
- case *net.TCPAddr: |
|
262 |
- return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) |
|
263 |
- default: |
|
264 |
- panic(fmt.Errorf("Unsupported protocol")) |
|
265 |
- } |
|
266 |
-} |
267 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,216 +0,0 @@ |
1 |
-package docker |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "bytes" |
|
5 |
- "fmt" |
|
6 |
- "io" |
|
7 |
- "net" |
|
8 |
- "strings" |
|
9 |
- "testing" |
|
10 |
- "time" |
|
11 |
-) |
|
12 |
- |
|
13 |
-var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") |
|
14 |
-var testBufSize = len(testBuf) |
|
15 |
- |
|
16 |
-type EchoServer interface { |
|
17 |
- Run() |
|
18 |
- Close() |
|
19 |
- LocalAddr() net.Addr |
|
20 |
-} |
|
21 |
- |
|
22 |
-type TCPEchoServer struct { |
|
23 |
- listener net.Listener |
|
24 |
- testCtx *testing.T |
|
25 |
-} |
|
26 |
- |
|
27 |
-type UDPEchoServer struct { |
|
28 |
- conn net.PacketConn |
|
29 |
- testCtx *testing.T |
|
30 |
-} |
|
31 |
- |
|
32 |
-func NewEchoServer(t *testing.T, proto, address string) EchoServer { |
|
33 |
- var server EchoServer |
|
34 |
- if strings.HasPrefix(proto, "tcp") { |
|
35 |
- listener, err := net.Listen(proto, address) |
|
36 |
- if err != nil { |
|
37 |
- t.Fatal(err) |
|
38 |
- } |
|
39 |
- server = &TCPEchoServer{listener: listener, testCtx: t} |
|
40 |
- } else { |
|
41 |
- socket, err := net.ListenPacket(proto, address) |
|
42 |
- if err != nil { |
|
43 |
- t.Fatal(err) |
|
44 |
- } |
|
45 |
- server = &UDPEchoServer{conn: socket, testCtx: t} |
|
46 |
- } |
|
47 |
- return server |
|
48 |
-} |
|
49 |
- |
|
50 |
-func (server *TCPEchoServer) Run() { |
|
51 |
- go func() { |
|
52 |
- for { |
|
53 |
- client, err := server.listener.Accept() |
|
54 |
- if err != nil { |
|
55 |
- return |
|
56 |
- } |
|
57 |
- go func(client net.Conn) { |
|
58 |
- if _, err := io.Copy(client, client); err != nil { |
|
59 |
- server.testCtx.Logf("can't echo to the client: %v\n", err.Error()) |
|
60 |
- } |
|
61 |
- client.Close() |
|
62 |
- }(client) |
|
63 |
- } |
|
64 |
- }() |
|
65 |
-} |
|
66 |
- |
|
67 |
-func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() } |
|
68 |
-func (server *TCPEchoServer) Close() { server.listener.Addr() } |
|
69 |
- |
|
70 |
-func (server *UDPEchoServer) Run() { |
|
71 |
- go func() { |
|
72 |
- readBuf := make([]byte, 1024) |
|
73 |
- for { |
|
74 |
- read, from, err := server.conn.ReadFrom(readBuf) |
|
75 |
- if err != nil { |
|
76 |
- return |
|
77 |
- } |
|
78 |
- for i := 0; i != read; { |
|
79 |
- written, err := server.conn.WriteTo(readBuf[i:read], from) |
|
80 |
- if err != nil { |
|
81 |
- break |
|
82 |
- } |
|
83 |
- i += written |
|
84 |
- } |
|
85 |
- } |
|
86 |
- }() |
|
87 |
-} |
|
88 |
- |
|
89 |
-func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() } |
|
90 |
-func (server *UDPEchoServer) Close() { server.conn.Close() } |
|
91 |
- |
|
92 |
-func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) { |
|
93 |
- defer proxy.Close() |
|
94 |
- go proxy.Run() |
|
95 |
- client, err := net.Dial(proto, addr) |
|
96 |
- if err != nil { |
|
97 |
- t.Fatalf("Can't connect to the proxy: %v", err) |
|
98 |
- } |
|
99 |
- defer client.Close() |
|
100 |
- client.SetDeadline(time.Now().Add(10 * time.Second)) |
|
101 |
- if _, err = client.Write(testBuf); err != nil { |
|
102 |
- t.Fatal(err) |
|
103 |
- } |
|
104 |
- recvBuf := make([]byte, testBufSize) |
|
105 |
- if _, err = client.Read(recvBuf); err != nil { |
|
106 |
- t.Fatal(err) |
|
107 |
- } |
|
108 |
- if !bytes.Equal(testBuf, recvBuf) { |
|
109 |
- t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) |
|
110 |
- } |
|
111 |
-} |
|
112 |
- |
|
113 |
-func testProxy(t *testing.T, proto string, proxy Proxy) { |
|
114 |
- testProxyAt(t, proto, proxy, proxy.FrontendAddr().String()) |
|
115 |
-} |
|
116 |
- |
|
117 |
-func TestTCP4Proxy(t *testing.T) { |
|
118 |
- backend := NewEchoServer(t, "tcp", "127.0.0.1:0") |
|
119 |
- defer backend.Close() |
|
120 |
- backend.Run() |
|
121 |
- frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
122 |
- proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
123 |
- if err != nil { |
|
124 |
- t.Fatal(err) |
|
125 |
- } |
|
126 |
- testProxy(t, "tcp", proxy) |
|
127 |
-} |
|
128 |
- |
|
129 |
-func TestTCP6Proxy(t *testing.T) { |
|
130 |
- backend := NewEchoServer(t, "tcp", "[::1]:0") |
|
131 |
- defer backend.Close() |
|
132 |
- backend.Run() |
|
133 |
- frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} |
|
134 |
- proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
135 |
- if err != nil { |
|
136 |
- t.Fatal(err) |
|
137 |
- } |
|
138 |
- testProxy(t, "tcp", proxy) |
|
139 |
-} |
|
140 |
- |
|
141 |
-func TestTCPDualStackProxy(t *testing.T) { |
|
142 |
- // If I understand `godoc -src net favoriteAddrFamily` (used by the |
|
143 |
- // net.Listen* functions) correctly this should work, but it doesn't. |
|
144 |
- t.Skip("No support for dual stack yet") |
|
145 |
- backend := NewEchoServer(t, "tcp", "[::1]:0") |
|
146 |
- defer backend.Close() |
|
147 |
- backend.Run() |
|
148 |
- frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} |
|
149 |
- proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
150 |
- if err != nil { |
|
151 |
- t.Fatal(err) |
|
152 |
- } |
|
153 |
- ipv4ProxyAddr := &net.TCPAddr{ |
|
154 |
- IP: net.IPv4(127, 0, 0, 1), |
|
155 |
- Port: proxy.FrontendAddr().(*net.TCPAddr).Port, |
|
156 |
- } |
|
157 |
- testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String()) |
|
158 |
-} |
|
159 |
- |
|
160 |
-func TestUDP4Proxy(t *testing.T) { |
|
161 |
- backend := NewEchoServer(t, "udp", "127.0.0.1:0") |
|
162 |
- defer backend.Close() |
|
163 |
- backend.Run() |
|
164 |
- frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
165 |
- proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
166 |
- if err != nil { |
|
167 |
- t.Fatal(err) |
|
168 |
- } |
|
169 |
- testProxy(t, "udp", proxy) |
|
170 |
-} |
|
171 |
- |
|
172 |
-func TestUDP6Proxy(t *testing.T) { |
|
173 |
- backend := NewEchoServer(t, "udp", "[::1]:0") |
|
174 |
- defer backend.Close() |
|
175 |
- backend.Run() |
|
176 |
- frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0} |
|
177 |
- proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
178 |
- if err != nil { |
|
179 |
- t.Fatal(err) |
|
180 |
- } |
|
181 |
- testProxy(t, "udp", proxy) |
|
182 |
-} |
|
183 |
- |
|
184 |
-func TestUDPWriteError(t *testing.T) { |
|
185 |
- frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
186 |
- // Hopefully, this port will be free: */ |
|
187 |
- backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587} |
|
188 |
- proxy, err := NewProxy(frontendAddr, backendAddr) |
|
189 |
- if err != nil { |
|
190 |
- t.Fatal(err) |
|
191 |
- } |
|
192 |
- defer proxy.Close() |
|
193 |
- go proxy.Run() |
|
194 |
- client, err := net.Dial("udp", "127.0.0.1:25587") |
|
195 |
- if err != nil { |
|
196 |
- t.Fatalf("Can't connect to the proxy: %v", err) |
|
197 |
- } |
|
198 |
- defer client.Close() |
|
199 |
- // Make sure the proxy doesn't stop when there is no actual backend: |
|
200 |
- client.Write(testBuf) |
|
201 |
- client.Write(testBuf) |
|
202 |
- backend := NewEchoServer(t, "udp", "127.0.0.1:25587") |
|
203 |
- defer backend.Close() |
|
204 |
- backend.Run() |
|
205 |
- client.SetDeadline(time.Now().Add(10 * time.Second)) |
|
206 |
- if _, err = client.Write(testBuf); err != nil { |
|
207 |
- t.Fatal(err) |
|
208 |
- } |
|
209 |
- recvBuf := make([]byte, testBufSize) |
|
210 |
- if _, err = client.Read(recvBuf); err != nil { |
|
211 |
- t.Fatal(err) |
|
212 |
- } |
|
213 |
- if !bytes.Equal(testBuf, recvBuf) { |
|
214 |
- t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) |
|
215 |
- } |
|
216 |
-} |
... | ... |
@@ -2,117 +2,9 @@ package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"net" |
5 |
- "os" |
|
6 | 5 |
"testing" |
7 | 6 |
) |
8 | 7 |
|
9 |
-func TestIptables(t *testing.T) { |
|
10 |
- if err := iptables("-L"); err != nil { |
|
11 |
- t.Fatal(err) |
|
12 |
- } |
|
13 |
- path := os.Getenv("PATH") |
|
14 |
- os.Setenv("PATH", "") |
|
15 |
- defer os.Setenv("PATH", path) |
|
16 |
- if err := iptables("-L"); err == nil { |
|
17 |
- t.Fatal("Not finding iptables in the PATH should cause an error") |
|
18 |
- } |
|
19 |
-} |
|
20 |
- |
|
21 |
-func TestParseNat(t *testing.T) { |
|
22 |
- if nat, err := parseNat("4500"); err == nil { |
|
23 |
- if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" { |
|
24 |
- t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s", |
|
25 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
26 |
- } |
|
27 |
- } else { |
|
28 |
- t.Fatal(err) |
|
29 |
- } |
|
30 |
- |
|
31 |
- if nat, err := parseNat(":4501"); err == nil { |
|
32 |
- if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" { |
|
33 |
- t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s", |
|
34 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
35 |
- } |
|
36 |
- } else { |
|
37 |
- t.Fatal(err) |
|
38 |
- } |
|
39 |
- |
|
40 |
- if nat, err := parseNat("4502:4503"); err == nil { |
|
41 |
- if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { |
|
42 |
- t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s", |
|
43 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
44 |
- } |
|
45 |
- } else { |
|
46 |
- t.Fatal(err) |
|
47 |
- } |
|
48 |
- |
|
49 |
- if nat, err := parseNat("4502:4503/tcp"); err == nil { |
|
50 |
- if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { |
|
51 |
- t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s", |
|
52 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
53 |
- } |
|
54 |
- } else { |
|
55 |
- t.Fatal(err) |
|
56 |
- } |
|
57 |
- |
|
58 |
- if nat, err := parseNat("4502:4503/udp"); err == nil { |
|
59 |
- if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" { |
|
60 |
- t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s", |
|
61 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
62 |
- } |
|
63 |
- } else { |
|
64 |
- t.Fatal(err) |
|
65 |
- } |
|
66 |
- |
|
67 |
- if nat, err := parseNat(":4503/udp"); err == nil { |
|
68 |
- if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" { |
|
69 |
- t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s", |
|
70 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
71 |
- } |
|
72 |
- } else { |
|
73 |
- t.Fatal(err) |
|
74 |
- } |
|
75 |
- |
|
76 |
- if nat, err := parseNat(":4503/tcp"); err == nil { |
|
77 |
- if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" { |
|
78 |
- t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s", |
|
79 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
80 |
- } |
|
81 |
- } else { |
|
82 |
- t.Fatal(err) |
|
83 |
- } |
|
84 |
- |
|
85 |
- if nat, err := parseNat("4503/tcp"); err == nil { |
|
86 |
- if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" { |
|
87 |
- t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s", |
|
88 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
89 |
- } |
|
90 |
- } else { |
|
91 |
- t.Fatal(err) |
|
92 |
- } |
|
93 |
- |
|
94 |
- if nat, err := parseNat("4503/udp"); err == nil { |
|
95 |
- if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" { |
|
96 |
- t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s", |
|
97 |
- nat.Frontend, nat.Backend, nat.Proto) |
|
98 |
- } |
|
99 |
- } else { |
|
100 |
- t.Fatal(err) |
|
101 |
- } |
|
102 |
- |
|
103 |
- if _, err := parseNat("4503/tcpgarbage"); err == nil { |
|
104 |
- t.Fatal(err) |
|
105 |
- } |
|
106 |
- |
|
107 |
- if _, err := parseNat("4503/tcp/udp"); err == nil { |
|
108 |
- t.Fatal(err) |
|
109 |
- } |
|
110 |
- |
|
111 |
- if _, err := parseNat("4503/"); err == nil { |
|
112 |
- t.Fatal(err) |
|
113 |
- } |
|
114 |
-} |
|
115 |
- |
|
116 | 8 |
func TestPortAllocation(t *testing.T) { |
117 | 9 |
allocator, err := newPortAllocator() |
118 | 10 |
if err != nil { |
0 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,216 @@ |
0 |
+package proxy |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "fmt" |
|
5 |
+ "io" |
|
6 |
+ "net" |
|
7 |
+ "strings" |
|
8 |
+ "testing" |
|
9 |
+ "time" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") |
|
13 |
+var testBufSize = len(testBuf) |
|
14 |
+ |
|
15 |
+type EchoServer interface { |
|
16 |
+ Run() |
|
17 |
+ Close() |
|
18 |
+ LocalAddr() net.Addr |
|
19 |
+} |
|
20 |
+ |
|
21 |
+type TCPEchoServer struct { |
|
22 |
+ listener net.Listener |
|
23 |
+ testCtx *testing.T |
|
24 |
+} |
|
25 |
+ |
|
26 |
+type UDPEchoServer struct { |
|
27 |
+ conn net.PacketConn |
|
28 |
+ testCtx *testing.T |
|
29 |
+} |
|
30 |
+ |
|
31 |
+func NewEchoServer(t *testing.T, proto, address string) EchoServer { |
|
32 |
+ var server EchoServer |
|
33 |
+ if strings.HasPrefix(proto, "tcp") { |
|
34 |
+ listener, err := net.Listen(proto, address) |
|
35 |
+ if err != nil { |
|
36 |
+ t.Fatal(err) |
|
37 |
+ } |
|
38 |
+ server = &TCPEchoServer{listener: listener, testCtx: t} |
|
39 |
+ } else { |
|
40 |
+ socket, err := net.ListenPacket(proto, address) |
|
41 |
+ if err != nil { |
|
42 |
+ t.Fatal(err) |
|
43 |
+ } |
|
44 |
+ server = &UDPEchoServer{conn: socket, testCtx: t} |
|
45 |
+ } |
|
46 |
+ return server |
|
47 |
+} |
|
48 |
+ |
|
49 |
+func (server *TCPEchoServer) Run() { |
|
50 |
+ go func() { |
|
51 |
+ for { |
|
52 |
+ client, err := server.listener.Accept() |
|
53 |
+ if err != nil { |
|
54 |
+ return |
|
55 |
+ } |
|
56 |
+ go func(client net.Conn) { |
|
57 |
+ if _, err := io.Copy(client, client); err != nil { |
|
58 |
+ server.testCtx.Logf("can't echo to the client: %v\n", err.Error()) |
|
59 |
+ } |
|
60 |
+ client.Close() |
|
61 |
+ }(client) |
|
62 |
+ } |
|
63 |
+ }() |
|
64 |
+} |
|
65 |
+ |
|
66 |
+func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() } |
|
67 |
+func (server *TCPEchoServer) Close() { server.listener.Addr() } |
|
68 |
+ |
|
69 |
+func (server *UDPEchoServer) Run() { |
|
70 |
+ go func() { |
|
71 |
+ readBuf := make([]byte, 1024) |
|
72 |
+ for { |
|
73 |
+ read, from, err := server.conn.ReadFrom(readBuf) |
|
74 |
+ if err != nil { |
|
75 |
+ return |
|
76 |
+ } |
|
77 |
+ for i := 0; i != read; { |
|
78 |
+ written, err := server.conn.WriteTo(readBuf[i:read], from) |
|
79 |
+ if err != nil { |
|
80 |
+ break |
|
81 |
+ } |
|
82 |
+ i += written |
|
83 |
+ } |
|
84 |
+ } |
|
85 |
+ }() |
|
86 |
+} |
|
87 |
+ |
|
88 |
+func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() } |
|
89 |
+func (server *UDPEchoServer) Close() { server.conn.Close() } |
|
90 |
+ |
|
91 |
+func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) { |
|
92 |
+ defer proxy.Close() |
|
93 |
+ go proxy.Run() |
|
94 |
+ client, err := net.Dial(proto, addr) |
|
95 |
+ if err != nil { |
|
96 |
+ t.Fatalf("Can't connect to the proxy: %v", err) |
|
97 |
+ } |
|
98 |
+ defer client.Close() |
|
99 |
+ client.SetDeadline(time.Now().Add(10 * time.Second)) |
|
100 |
+ if _, err = client.Write(testBuf); err != nil { |
|
101 |
+ t.Fatal(err) |
|
102 |
+ } |
|
103 |
+ recvBuf := make([]byte, testBufSize) |
|
104 |
+ if _, err = client.Read(recvBuf); err != nil { |
|
105 |
+ t.Fatal(err) |
|
106 |
+ } |
|
107 |
+ if !bytes.Equal(testBuf, recvBuf) { |
|
108 |
+ t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) |
|
109 |
+ } |
|
110 |
+} |
|
111 |
+ |
|
112 |
+func testProxy(t *testing.T, proto string, proxy Proxy) { |
|
113 |
+ testProxyAt(t, proto, proxy, proxy.FrontendAddr().String()) |
|
114 |
+} |
|
115 |
+ |
|
116 |
+func TestTCP4Proxy(t *testing.T) { |
|
117 |
+ backend := NewEchoServer(t, "tcp", "127.0.0.1:0") |
|
118 |
+ defer backend.Close() |
|
119 |
+ backend.Run() |
|
120 |
+ frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
121 |
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
122 |
+ if err != nil { |
|
123 |
+ t.Fatal(err) |
|
124 |
+ } |
|
125 |
+ testProxy(t, "tcp", proxy) |
|
126 |
+} |
|
127 |
+ |
|
128 |
+func TestTCP6Proxy(t *testing.T) { |
|
129 |
+ backend := NewEchoServer(t, "tcp", "[::1]:0") |
|
130 |
+ defer backend.Close() |
|
131 |
+ backend.Run() |
|
132 |
+ frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} |
|
133 |
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
134 |
+ if err != nil { |
|
135 |
+ t.Fatal(err) |
|
136 |
+ } |
|
137 |
+ testProxy(t, "tcp", proxy) |
|
138 |
+} |
|
139 |
+ |
|
140 |
+func TestTCPDualStackProxy(t *testing.T) { |
|
141 |
+ // If I understand `godoc -src net favoriteAddrFamily` (used by the |
|
142 |
+ // net.Listen* functions) correctly this should work, but it doesn't. |
|
143 |
+ t.Skip("No support for dual stack yet") |
|
144 |
+ backend := NewEchoServer(t, "tcp", "[::1]:0") |
|
145 |
+ defer backend.Close() |
|
146 |
+ backend.Run() |
|
147 |
+ frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} |
|
148 |
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
149 |
+ if err != nil { |
|
150 |
+ t.Fatal(err) |
|
151 |
+ } |
|
152 |
+ ipv4ProxyAddr := &net.TCPAddr{ |
|
153 |
+ IP: net.IPv4(127, 0, 0, 1), |
|
154 |
+ Port: proxy.FrontendAddr().(*net.TCPAddr).Port, |
|
155 |
+ } |
|
156 |
+ testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String()) |
|
157 |
+} |
|
158 |
+ |
|
159 |
+func TestUDP4Proxy(t *testing.T) { |
|
160 |
+ backend := NewEchoServer(t, "udp", "127.0.0.1:0") |
|
161 |
+ defer backend.Close() |
|
162 |
+ backend.Run() |
|
163 |
+ frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
164 |
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
165 |
+ if err != nil { |
|
166 |
+ t.Fatal(err) |
|
167 |
+ } |
|
168 |
+ testProxy(t, "udp", proxy) |
|
169 |
+} |
|
170 |
+ |
|
171 |
+func TestUDP6Proxy(t *testing.T) { |
|
172 |
+ backend := NewEchoServer(t, "udp", "[::1]:0") |
|
173 |
+ defer backend.Close() |
|
174 |
+ backend.Run() |
|
175 |
+ frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0} |
|
176 |
+ proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) |
|
177 |
+ if err != nil { |
|
178 |
+ t.Fatal(err) |
|
179 |
+ } |
|
180 |
+ testProxy(t, "udp", proxy) |
|
181 |
+} |
|
182 |
+ |
|
183 |
+func TestUDPWriteError(t *testing.T) { |
|
184 |
+ frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} |
|
185 |
+ // Hopefully, this port will be free: */ |
|
186 |
+ backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587} |
|
187 |
+ proxy, err := NewProxy(frontendAddr, backendAddr) |
|
188 |
+ if err != nil { |
|
189 |
+ t.Fatal(err) |
|
190 |
+ } |
|
191 |
+ defer proxy.Close() |
|
192 |
+ go proxy.Run() |
|
193 |
+ client, err := net.Dial("udp", "127.0.0.1:25587") |
|
194 |
+ if err != nil { |
|
195 |
+ t.Fatalf("Can't connect to the proxy: %v", err) |
|
196 |
+ } |
|
197 |
+ defer client.Close() |
|
198 |
+ // Make sure the proxy doesn't stop when there is no actual backend: |
|
199 |
+ client.Write(testBuf) |
|
200 |
+ client.Write(testBuf) |
|
201 |
+ backend := NewEchoServer(t, "udp", "127.0.0.1:25587") |
|
202 |
+ defer backend.Close() |
|
203 |
+ backend.Run() |
|
204 |
+ client.SetDeadline(time.Now().Add(10 * time.Second)) |
|
205 |
+ if _, err = client.Write(testBuf); err != nil { |
|
206 |
+ t.Fatal(err) |
|
207 |
+ } |
|
208 |
+ recvBuf := make([]byte, testBufSize) |
|
209 |
+ if _, err = client.Read(recvBuf); err != nil { |
|
210 |
+ t.Fatal(err) |
|
211 |
+ } |
|
212 |
+ if !bytes.Equal(testBuf, recvBuf) { |
|
213 |
+ t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) |
|
214 |
+ } |
|
215 |
+} |
0 | 216 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,29 @@ |
0 |
+package proxy |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "net" |
|
5 |
+) |
|
6 |
+ |
|
7 |
+type Proxy interface { |
|
8 |
+ // Start forwarding traffic back and forth the front and back-end |
|
9 |
+ // addresses. |
|
10 |
+ Run() |
|
11 |
+ // Stop forwarding traffic and close both ends of the Proxy. |
|
12 |
+ Close() |
|
13 |
+ // Return the address on which the proxy is listening. |
|
14 |
+ FrontendAddr() net.Addr |
|
15 |
+ // Return the proxied address. |
|
16 |
+ BackendAddr() net.Addr |
|
17 |
+} |
|
18 |
+ |
|
19 |
+func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { |
|
20 |
+ switch frontendAddr.(type) { |
|
21 |
+ case *net.UDPAddr: |
|
22 |
+ return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) |
|
23 |
+ case *net.TCPAddr: |
|
24 |
+ return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) |
|
25 |
+ default: |
|
26 |
+ panic(fmt.Errorf("Unsupported protocol")) |
|
27 |
+ } |
|
28 |
+} |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,93 @@ |
0 |
+package proxy |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "github.com/dotcloud/docker/utils" |
|
4 |
+ "io" |
|
5 |
+ "log" |
|
6 |
+ "net" |
|
7 |
+ "syscall" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+type TCPProxy struct { |
|
11 |
+ listener *net.TCPListener |
|
12 |
+ frontendAddr *net.TCPAddr |
|
13 |
+ backendAddr *net.TCPAddr |
|
14 |
+} |
|
15 |
+ |
|
16 |
+func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { |
|
17 |
+ listener, err := net.ListenTCP("tcp", frontendAddr) |
|
18 |
+ if err != nil { |
|
19 |
+ return nil, err |
|
20 |
+ } |
|
21 |
+ // If the port in frontendAddr was 0 then ListenTCP will have a picked |
|
22 |
+ // a port to listen on, hence the call to Addr to get that actual port: |
|
23 |
+ return &TCPProxy{ |
|
24 |
+ listener: listener, |
|
25 |
+ frontendAddr: listener.Addr().(*net.TCPAddr), |
|
26 |
+ backendAddr: backendAddr, |
|
27 |
+ }, nil |
|
28 |
+} |
|
29 |
+ |
|
30 |
+func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { |
|
31 |
+ backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) |
|
32 |
+ if err != nil { |
|
33 |
+ log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error()) |
|
34 |
+ client.Close() |
|
35 |
+ return |
|
36 |
+ } |
|
37 |
+ |
|
38 |
+ event := make(chan int64) |
|
39 |
+ var broker = func(to, from *net.TCPConn) { |
|
40 |
+ written, err := io.Copy(to, from) |
|
41 |
+ if err != nil { |
|
42 |
+ // If the socket we are writing to is shutdown with |
|
43 |
+ // SHUT_WR, forward it to the other end of the pipe: |
|
44 |
+ if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { |
|
45 |
+ from.CloseWrite() |
|
46 |
+ } |
|
47 |
+ } |
|
48 |
+ to.CloseRead() |
|
49 |
+ event <- written |
|
50 |
+ } |
|
51 |
+ utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr()) |
|
52 |
+ go broker(client, backend) |
|
53 |
+ go broker(backend, client) |
|
54 |
+ |
|
55 |
+ var transferred int64 = 0 |
|
56 |
+ for i := 0; i < 2; i++ { |
|
57 |
+ select { |
|
58 |
+ case written := <-event: |
|
59 |
+ transferred += written |
|
60 |
+ case <-quit: |
|
61 |
+ // Interrupt the two brokers and "join" them. |
|
62 |
+ client.Close() |
|
63 |
+ backend.Close() |
|
64 |
+ for ; i < 2; i++ { |
|
65 |
+ transferred += <-event |
|
66 |
+ } |
|
67 |
+ goto done |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ client.Close() |
|
71 |
+ backend.Close() |
|
72 |
+done: |
|
73 |
+ utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr()) |
|
74 |
+} |
|
75 |
+ |
|
76 |
+func (proxy *TCPProxy) Run() { |
|
77 |
+ quit := make(chan bool) |
|
78 |
+ defer close(quit) |
|
79 |
+ utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr) |
|
80 |
+ for { |
|
81 |
+ client, err := proxy.listener.Accept() |
|
82 |
+ if err != nil { |
|
83 |
+ utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) |
|
84 |
+ return |
|
85 |
+ } |
|
86 |
+ go proxy.clientLoop(client.(*net.TCPConn), quit) |
|
87 |
+ } |
|
88 |
+} |
|
89 |
+ |
|
90 |
+func (proxy *TCPProxy) Close() { proxy.listener.Close() } |
|
91 |
+func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } |
|
92 |
+func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } |
0 | 93 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,152 @@ |
0 |
+package proxy |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/binary" |
|
4 |
+ "github.com/dotcloud/docker/utils" |
|
5 |
+ "log" |
|
6 |
+ "net" |
|
7 |
+ "sync" |
|
8 |
+ "syscall" |
|
9 |
+ "time" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+const ( |
|
13 |
+ UDPConnTrackTimeout = 90 * time.Second |
|
14 |
+ UDPBufSize = 2048 |
|
15 |
+) |
|
16 |
+ |
|
17 |
+// A net.Addr where the IP is split into two fields so you can use it as a key |
|
18 |
+// in a map: |
|
19 |
+type connTrackKey struct { |
|
20 |
+ IPHigh uint64 |
|
21 |
+ IPLow uint64 |
|
22 |
+ Port int |
|
23 |
+} |
|
24 |
+ |
|
25 |
+func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { |
|
26 |
+ if len(addr.IP) == net.IPv4len { |
|
27 |
+ return &connTrackKey{ |
|
28 |
+ IPHigh: 0, |
|
29 |
+ IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), |
|
30 |
+ Port: addr.Port, |
|
31 |
+ } |
|
32 |
+ } |
|
33 |
+ return &connTrackKey{ |
|
34 |
+ IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), |
|
35 |
+ IPLow: binary.BigEndian.Uint64(addr.IP[8:]), |
|
36 |
+ Port: addr.Port, |
|
37 |
+ } |
|
38 |
+} |
|
39 |
+ |
|
40 |
+type connTrackMap map[connTrackKey]*net.UDPConn |
|
41 |
+ |
|
42 |
+type UDPProxy struct { |
|
43 |
+ listener *net.UDPConn |
|
44 |
+ frontendAddr *net.UDPAddr |
|
45 |
+ backendAddr *net.UDPAddr |
|
46 |
+ connTrackTable connTrackMap |
|
47 |
+ connTrackLock sync.Mutex |
|
48 |
+} |
|
49 |
+ |
|
50 |
+func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) { |
|
51 |
+ listener, err := net.ListenUDP("udp", frontendAddr) |
|
52 |
+ if err != nil { |
|
53 |
+ return nil, err |
|
54 |
+ } |
|
55 |
+ return &UDPProxy{ |
|
56 |
+ listener: listener, |
|
57 |
+ frontendAddr: listener.LocalAddr().(*net.UDPAddr), |
|
58 |
+ backendAddr: backendAddr, |
|
59 |
+ connTrackTable: make(connTrackMap), |
|
60 |
+ }, nil |
|
61 |
+} |
|
62 |
+ |
|
63 |
+func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { |
|
64 |
+ defer func() { |
|
65 |
+ proxy.connTrackLock.Lock() |
|
66 |
+ delete(proxy.connTrackTable, *clientKey) |
|
67 |
+ proxy.connTrackLock.Unlock() |
|
68 |
+ utils.Debugf("Done proxying between udp/%v and udp/%v", clientAddr.String(), proxy.backendAddr.String()) |
|
69 |
+ proxyConn.Close() |
|
70 |
+ }() |
|
71 |
+ |
|
72 |
+ readBuf := make([]byte, UDPBufSize) |
|
73 |
+ for { |
|
74 |
+ proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) |
|
75 |
+ again: |
|
76 |
+ read, err := proxyConn.Read(readBuf) |
|
77 |
+ if err != nil { |
|
78 |
+ if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { |
|
79 |
+ // This will happen if the last write failed |
|
80 |
+ // (e.g: nothing is actually listening on the |
|
81 |
+ // proxied port on the container), ignore it |
|
82 |
+ // and continue until UDPConnTrackTimeout |
|
83 |
+ // expires: |
|
84 |
+ goto again |
|
85 |
+ } |
|
86 |
+ return |
|
87 |
+ } |
|
88 |
+ for i := 0; i != read; { |
|
89 |
+ written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) |
|
90 |
+ if err != nil { |
|
91 |
+ return |
|
92 |
+ } |
|
93 |
+ i += written |
|
94 |
+ utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, clientAddr.String()) |
|
95 |
+ } |
|
96 |
+ } |
|
97 |
+} |
|
98 |
+ |
|
99 |
+func (proxy *UDPProxy) Run() { |
|
100 |
+ readBuf := make([]byte, UDPBufSize) |
|
101 |
+ utils.Debugf("Starting proxy on udp/%v for udp/%v", proxy.frontendAddr, proxy.backendAddr) |
|
102 |
+ for { |
|
103 |
+ read, from, err := proxy.listener.ReadFromUDP(readBuf) |
|
104 |
+ if err != nil { |
|
105 |
+ // NOTE: Apparently ReadFrom doesn't return |
|
106 |
+ // ECONNREFUSED like Read do (see comment in |
|
107 |
+ // UDPProxy.replyLoop) |
|
108 |
+ if utils.IsClosedError(err) { |
|
109 |
+ utils.Debugf("Stopping proxy on udp/%v for udp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr) |
|
110 |
+ } else { |
|
111 |
+ utils.Errorf("Stopping proxy on udp/%v for udp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) |
|
112 |
+ } |
|
113 |
+ break |
|
114 |
+ } |
|
115 |
+ |
|
116 |
+ fromKey := newConnTrackKey(from) |
|
117 |
+ proxy.connTrackLock.Lock() |
|
118 |
+ proxyConn, hit := proxy.connTrackTable[*fromKey] |
|
119 |
+ if !hit { |
|
120 |
+ proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) |
|
121 |
+ if err != nil { |
|
122 |
+ log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) |
|
123 |
+ continue |
|
124 |
+ } |
|
125 |
+ proxy.connTrackTable[*fromKey] = proxyConn |
|
126 |
+ go proxy.replyLoop(proxyConn, from, fromKey) |
|
127 |
+ } |
|
128 |
+ proxy.connTrackLock.Unlock() |
|
129 |
+ for i := 0; i != read; { |
|
130 |
+ written, err := proxyConn.Write(readBuf[i:read]) |
|
131 |
+ if err != nil { |
|
132 |
+ log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) |
|
133 |
+ break |
|
134 |
+ } |
|
135 |
+ i += written |
|
136 |
+ utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, proxy.backendAddr.String()) |
|
137 |
+ } |
|
138 |
+ } |
|
139 |
+} |
|
140 |
+ |
|
141 |
+func (proxy *UDPProxy) Close() { |
|
142 |
+ proxy.listener.Close() |
|
143 |
+ proxy.connTrackLock.Lock() |
|
144 |
+ defer proxy.connTrackLock.Unlock() |
|
145 |
+ for _, conn := range proxy.connTrackTable { |
|
146 |
+ conn.Close() |
|
147 |
+ } |
|
148 |
+} |
|
149 |
+ |
|
150 |
+func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } |
|
151 |
+func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } |
... | ... |
@@ -3,6 +3,7 @@ package docker |
3 | 3 |
import ( |
4 | 4 |
"container/list" |
5 | 5 |
"fmt" |
6 |
+ "github.com/dotcloud/docker/gograph" |
|
6 | 7 |
"github.com/dotcloud/docker/utils" |
7 | 8 |
"io" |
8 | 9 |
"io/ioutil" |
... | ... |
@@ -24,7 +25,6 @@ type Capabilities struct { |
24 | 24 |
} |
25 | 25 |
|
26 | 26 |
type Runtime struct { |
27 |
- root string |
|
28 | 27 |
repository string |
29 | 28 |
containers *list.List |
30 | 29 |
networkManager *NetworkManager |
... | ... |
@@ -32,10 +32,10 @@ type Runtime struct { |
32 | 32 |
repositories *TagStore |
33 | 33 |
idIndex *utils.TruncIndex |
34 | 34 |
capabilities *Capabilities |
35 |
- autoRestart bool |
|
36 | 35 |
volumes *Graph |
37 | 36 |
srv *Server |
38 |
- Dns []string |
|
37 |
+ config *DaemonConfig |
|
38 |
+ containerGraph *gograph.Database |
|
39 | 39 |
} |
40 | 40 |
|
41 | 41 |
var sysInitPath string |
... | ... |
@@ -66,10 +66,15 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { |
66 | 66 |
// Get looks for a container by the specified ID or name, and returns it. |
67 | 67 |
// If the container is not found, or if an error occurs, nil is returned. |
68 | 68 |
func (runtime *Runtime) Get(name string) *Container { |
69 |
+ if c, _ := runtime.GetByName(name); c != nil { |
|
70 |
+ return c |
|
71 |
+ } |
|
72 |
+ |
|
69 | 73 |
id, err := runtime.idIndex.Get(name) |
70 | 74 |
if err != nil { |
71 | 75 |
return nil |
72 | 76 |
} |
77 |
+ |
|
73 | 78 |
e := runtime.getContainerElement(id) |
74 | 79 |
if e == nil { |
75 | 80 |
return nil |
... | ... |
@@ -87,10 +92,9 @@ func (runtime *Runtime) containerRoot(id string) string { |
87 | 87 |
return path.Join(runtime.repository, id) |
88 | 88 |
} |
89 | 89 |
|
90 |
-// Load reads the contents of a container from disk and registers |
|
91 |
-// it with Register. |
|
90 |
+// Load reads the contents of a container from disk |
|
92 | 91 |
// This is typically done at startup. |
93 |
-func (runtime *Runtime) Load(id string) (*Container, error) { |
|
92 |
+func (runtime *Runtime) load(id string) (*Container, error) { |
|
94 | 93 |
container := &Container{root: runtime.containerRoot(id)} |
95 | 94 |
if err := container.FromDisk(); err != nil { |
96 | 95 |
return nil, err |
... | ... |
@@ -101,9 +105,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) { |
101 | 101 |
if container.State.Running { |
102 | 102 |
container.State.Ghost = true |
103 | 103 |
} |
104 |
- if err := runtime.Register(container); err != nil { |
|
105 |
- return nil, err |
|
106 |
- } |
|
107 | 104 |
return container, nil |
108 | 105 |
} |
109 | 106 |
|
... | ... |
@@ -148,11 +149,11 @@ func (runtime *Runtime) Register(container *Container) error { |
148 | 148 |
} |
149 | 149 |
if !strings.Contains(string(output), "RUNNING") { |
150 | 150 |
utils.Debugf("Container %s was supposed to be running be is not.", container.ID) |
151 |
- if runtime.autoRestart { |
|
151 |
+ if runtime.config.AutoRestart { |
|
152 | 152 |
utils.Debugf("Restarting") |
153 | 153 |
container.State.Ghost = false |
154 | 154 |
container.State.setStopped(0) |
155 |
- hostConfig := &HostConfig{} |
|
155 |
+ hostConfig, _ := container.ReadHostConfig() |
|
156 | 156 |
if err := container.Start(hostConfig); err != nil { |
157 | 157 |
return err |
158 | 158 |
} |
... | ... |
@@ -172,9 +173,9 @@ func (runtime *Runtime) Register(container *Container) error { |
172 | 172 |
if !container.State.Running { |
173 | 173 |
close(container.waitLock) |
174 | 174 |
} else if !nomonitor { |
175 |
- container.allocateNetwork() |
|
176 |
- // hostConfig isn't needed here and can be nil |
|
177 |
- go container.monitor(nil) |
|
175 |
+ hostConfig, _ := container.ReadHostConfig() |
|
176 |
+ container.allocateNetwork(hostConfig) |
|
177 |
+ go container.monitor(hostConfig) |
|
178 | 178 |
} |
179 | 179 |
return nil |
180 | 180 |
} |
... | ... |
@@ -202,6 +203,7 @@ func (runtime *Runtime) Destroy(container *Container) error { |
202 | 202 |
if err := container.Stop(3); err != nil { |
203 | 203 |
return err |
204 | 204 |
} |
205 |
+ |
|
205 | 206 |
if mounted, err := container.Mounted(); err != nil { |
206 | 207 |
return err |
207 | 208 |
} else if mounted { |
... | ... |
@@ -209,6 +211,11 @@ func (runtime *Runtime) Destroy(container *Container) error { |
209 | 209 |
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err) |
210 | 210 |
} |
211 | 211 |
} |
212 |
+ |
|
213 |
+ if _, err := runtime.containerGraph.Purge(container.ID); err != nil { |
|
214 |
+ utils.Debugf("Unable to remove container from link graph: %s", err) |
|
215 |
+ } |
|
216 |
+ |
|
212 | 217 |
// Deregister the container before removing its directory, to avoid race conditions |
213 | 218 |
runtime.idIndex.Delete(container.ID) |
214 | 219 |
runtime.containers.Remove(element) |
... | ... |
@@ -227,9 +234,10 @@ func (runtime *Runtime) restore() error { |
227 | 227 |
if err != nil { |
228 | 228 |
return err |
229 | 229 |
} |
230 |
+ containers := []*Container{} |
|
230 | 231 |
for i, v := range dir { |
231 | 232 |
id := v.Name() |
232 |
- container, err := runtime.Load(id) |
|
233 |
+ container, err := runtime.load(id) |
|
233 | 234 |
if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { |
234 | 235 |
fmt.Printf("\b%c", wheel[i%4]) |
235 | 236 |
} |
... | ... |
@@ -238,10 +246,30 @@ func (runtime *Runtime) restore() error { |
238 | 238 |
continue |
239 | 239 |
} |
240 | 240 |
utils.Debugf("Loaded container %v", container.ID) |
241 |
+ containers = append(containers, container) |
|
242 |
+ } |
|
243 |
+ sortContainers(containers, func(i, j *Container) bool { |
|
244 |
+ ic, _ := i.ReadHostConfig() |
|
245 |
+ jc, _ := j.ReadHostConfig() |
|
246 |
+ |
|
247 |
+ if ic == nil || ic.Links == nil { |
|
248 |
+ return true |
|
249 |
+ } |
|
250 |
+ if jc == nil || jc.Links == nil { |
|
251 |
+ return false |
|
252 |
+ } |
|
253 |
+ return len(ic.Links) < len(jc.Links) |
|
254 |
+ }) |
|
255 |
+ for _, container := range containers { |
|
256 |
+ if err := runtime.Register(container); err != nil { |
|
257 |
+ utils.Debugf("Failed to register container %s: %s", container.ID, err) |
|
258 |
+ continue |
|
259 |
+ } |
|
241 | 260 |
} |
242 | 261 |
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { |
243 | 262 |
fmt.Printf("\bdone.\n") |
244 | 263 |
} |
264 |
+ |
|
245 | 265 |
return nil |
246 | 266 |
} |
247 | 267 |
|
... | ... |
@@ -274,27 +302,45 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { |
274 | 274 |
} |
275 | 275 |
|
276 | 276 |
// Create creates a new container from the given configuration. |
277 |
-func (runtime *Runtime) Create(config *Config) (*Container, error) { |
|
277 |
+func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { |
|
278 | 278 |
// Lookup image |
279 | 279 |
img, err := runtime.repositories.LookupImage(config.Image) |
280 | 280 |
if err != nil { |
281 |
- return nil, err |
|
281 |
+ return nil, nil, err |
|
282 | 282 |
} |
283 | 283 |
|
284 |
+ warnings := []string{} |
|
284 | 285 |
if img.Config != nil { |
286 |
+ if img.Config.PortSpecs != nil && warnings != nil { |
|
287 |
+ for _, p := range img.Config.PortSpecs { |
|
288 |
+ if strings.Contains(p, ":") { |
|
289 |
+ warnings = append(warnings, "This image expects private ports to be mapped to public ports on your host. "+ |
|
290 |
+ "This has been deprecated and the public mappings will not be honored."+ |
|
291 |
+ "Use -p to publish the ports.") |
|
292 |
+ break |
|
293 |
+ } |
|
294 |
+ } |
|
295 |
+ } |
|
285 | 296 |
if err := MergeConfig(config, img.Config); err != nil { |
286 |
- return nil, err |
|
297 |
+ return nil, nil, err |
|
287 | 298 |
} |
299 |
+ |
|
288 | 300 |
} |
289 | 301 |
|
290 | 302 |
if len(config.Entrypoint) != 0 && config.Cmd == nil { |
291 | 303 |
config.Cmd = []string{} |
292 | 304 |
} else if config.Cmd == nil || len(config.Cmd) == 0 { |
293 |
- return nil, fmt.Errorf("No command specified") |
|
305 |
+ return nil, nil, fmt.Errorf("No command specified") |
|
294 | 306 |
} |
295 | 307 |
|
296 | 308 |
// Generate id |
297 | 309 |
id := GenerateID() |
310 |
+ |
|
311 |
+ // Set the default enitity in the graph |
|
312 |
+ if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil { |
|
313 |
+ return nil, nil, err |
|
314 |
+ } |
|
315 |
+ |
|
298 | 316 |
// Generate default hostname |
299 | 317 |
// FIXME: the lxc template no longer needs to set a default hostname |
300 | 318 |
if config.Hostname == "" { |
... | ... |
@@ -328,36 +374,36 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { |
328 | 328 |
// Step 1: create the container directory. |
329 | 329 |
// This doubles as a barrier to avoid race conditions. |
330 | 330 |
if err := os.Mkdir(container.root, 0700); err != nil { |
331 |
- return nil, err |
|
331 |
+ return nil, nil, err |
|
332 | 332 |
} |
333 | 333 |
|
334 | 334 |
resolvConf, err := utils.GetResolvConf() |
335 | 335 |
if err != nil { |
336 |
- return nil, err |
|
336 |
+ return nil, nil, err |
|
337 | 337 |
} |
338 | 338 |
|
339 |
- if len(config.Dns) == 0 && len(runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { |
|
339 |
+ if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { |
|
340 | 340 |
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns |
341 |
- runtime.Dns = defaultDns |
|
341 |
+ runtime.config.Dns = defaultDns |
|
342 | 342 |
} |
343 | 343 |
|
344 | 344 |
// If custom dns exists, then create a resolv.conf for the container |
345 |
- if len(config.Dns) > 0 || len(runtime.Dns) > 0 { |
|
345 |
+ if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 { |
|
346 | 346 |
var dns []string |
347 | 347 |
if len(config.Dns) > 0 { |
348 | 348 |
dns = config.Dns |
349 | 349 |
} else { |
350 |
- dns = runtime.Dns |
|
350 |
+ dns = runtime.config.Dns |
|
351 | 351 |
} |
352 | 352 |
container.ResolvConfPath = path.Join(container.root, "resolv.conf") |
353 | 353 |
f, err := os.Create(container.ResolvConfPath) |
354 | 354 |
if err != nil { |
355 |
- return nil, err |
|
355 |
+ return nil, nil, err |
|
356 | 356 |
} |
357 | 357 |
defer f.Close() |
358 | 358 |
for _, dns := range dns { |
359 | 359 |
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { |
360 |
- return nil, err |
|
360 |
+ return nil, nil, err |
|
361 | 361 |
} |
362 | 362 |
} |
363 | 363 |
} else { |
... | ... |
@@ -366,7 +412,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { |
366 | 366 |
|
367 | 367 |
// Step 2: save the container json |
368 | 368 |
if err := container.ToDisk(); err != nil { |
369 |
- return nil, err |
|
369 |
+ return nil, nil, err |
|
370 | 370 |
} |
371 | 371 |
|
372 | 372 |
// Step 3: if hostname, build hostname and hosts files |
... | ... |
@@ -396,9 +442,9 @@ ff02::2 ip6-allrouters |
396 | 396 |
|
397 | 397 |
// Step 4: register the container |
398 | 398 |
if err := runtime.Register(container); err != nil { |
399 |
- return nil, err |
|
399 |
+ return nil, nil, err |
|
400 | 400 |
} |
401 |
- return container, nil |
|
401 |
+ return container, warnings, nil |
|
402 | 402 |
} |
403 | 403 |
|
404 | 404 |
// Commit creates a new filesystem image from the current state of a container. |
... | ... |
@@ -428,13 +474,85 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a |
428 | 428 |
return img, nil |
429 | 429 |
} |
430 | 430 |
|
431 |
+func (runtime *Runtime) GetByName(name string) (*Container, error) { |
|
432 |
+ if id, err := runtime.idIndex.Get(name); err == nil { |
|
433 |
+ name = id |
|
434 |
+ } |
|
435 |
+ |
|
436 |
+ entity := runtime.containerGraph.Get(name) |
|
437 |
+ if entity == nil { |
|
438 |
+ return nil, fmt.Errorf("Could not find entity for %s", name) |
|
439 |
+ } |
|
440 |
+ e := runtime.getContainerElement(entity.ID()) |
|
441 |
+ if e == nil { |
|
442 |
+ return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID()) |
|
443 |
+ } |
|
444 |
+ return e.Value.(*Container), nil |
|
445 |
+} |
|
446 |
+ |
|
447 |
+func (runtime *Runtime) Children(name string) (map[string]*Container, error) { |
|
448 |
+ children := make(map[string]*Container) |
|
449 |
+ |
|
450 |
+ err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error { |
|
451 |
+ c := runtime.Get(e.ID()) |
|
452 |
+ if c == nil { |
|
453 |
+ return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p) |
|
454 |
+ } |
|
455 |
+ children[p] = c |
|
456 |
+ return nil |
|
457 |
+ }, 0) |
|
458 |
+ |
|
459 |
+ if err != nil { |
|
460 |
+ return nil, err |
|
461 |
+ } |
|
462 |
+ return children, nil |
|
463 |
+} |
|
464 |
+ |
|
465 |
+func (runtime *Runtime) RenameLink(oldName, newName string) error { |
|
466 |
+ if id, err := runtime.idIndex.Get(oldName); err == nil { |
|
467 |
+ oldName = id |
|
468 |
+ } |
|
469 |
+ entity := runtime.containerGraph.Get(oldName) |
|
470 |
+ if entity == nil { |
|
471 |
+ return fmt.Errorf("Could not find entity for %s", oldName) |
|
472 |
+ } |
|
473 |
+ |
|
474 |
+ // This is not rename but adding a new link for the default name |
|
475 |
+ // Strip the leading '/' |
|
476 |
+ if entity.ID() == oldName[1:] { |
|
477 |
+ _, err := runtime.containerGraph.Set(newName, entity.ID()) |
|
478 |
+ return err |
|
479 |
+ } |
|
480 |
+ return runtime.containerGraph.Rename(oldName, newName) |
|
481 |
+} |
|
482 |
+ |
|
483 |
+func (runtime *Runtime) Link(parentName, childName, alias string) error { |
|
484 |
+ if id, err := runtime.idIndex.Get(parentName); err == nil { |
|
485 |
+ parentName = id |
|
486 |
+ } |
|
487 |
+ parent := runtime.containerGraph.Get(parentName) |
|
488 |
+ if parent == nil { |
|
489 |
+ return fmt.Errorf("Could not get container for %s", parentName) |
|
490 |
+ } |
|
491 |
+ if id, err := runtime.idIndex.Get(childName); err == nil { |
|
492 |
+ childName = id |
|
493 |
+ } |
|
494 |
+ child := runtime.containerGraph.Get(childName) |
|
495 |
+ if child == nil { |
|
496 |
+ return fmt.Errorf("Could not get container for %s", childName) |
|
497 |
+ } |
|
498 |
+ cc := runtime.Get(child.ID()) |
|
499 |
+ |
|
500 |
+ _, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID) |
|
501 |
+ return err |
|
502 |
+} |
|
503 |
+ |
|
431 | 504 |
// FIXME: harmonize with NewGraph() |
432 |
-func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { |
|
433 |
- runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) |
|
505 |
+func NewRuntime(config *DaemonConfig) (*Runtime, error) { |
|
506 |
+ runtime, err := NewRuntimeFromDirectory(config) |
|
434 | 507 |
if err != nil { |
435 | 508 |
return nil, err |
436 | 509 |
} |
437 |
- runtime.Dns = dns |
|
438 | 510 |
|
439 | 511 |
if k, err := utils.GetKernelVersion(); err != nil { |
440 | 512 |
log.Printf("WARNING: %s\n", err) |
... | ... |
@@ -447,34 +565,39 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e |
447 | 447 |
return runtime, nil |
448 | 448 |
} |
449 | 449 |
|
450 |
-func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { |
|
451 |
- runtimeRepo := path.Join(root, "containers") |
|
450 |
+func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { |
|
451 |
+ runtimeRepo := path.Join(config.GraphPath, "containers") |
|
452 | 452 |
|
453 | 453 |
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { |
454 | 454 |
return nil, err |
455 | 455 |
} |
456 | 456 |
|
457 |
- g, err := NewGraph(path.Join(root, "graph")) |
|
457 |
+ g, err := NewGraph(path.Join(config.GraphPath, "graph")) |
|
458 | 458 |
if err != nil { |
459 | 459 |
return nil, err |
460 | 460 |
} |
461 |
- volumes, err := NewGraph(path.Join(root, "volumes")) |
|
461 |
+ volumes, err := NewGraph(path.Join(config.GraphPath, "volumes")) |
|
462 | 462 |
if err != nil { |
463 | 463 |
return nil, err |
464 | 464 |
} |
465 |
- repositories, err := NewTagStore(path.Join(root, "repositories"), g) |
|
465 |
+ repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g) |
|
466 | 466 |
if err != nil { |
467 | 467 |
return nil, fmt.Errorf("Couldn't create Tag store: %s", err) |
468 | 468 |
} |
469 |
- if NetworkBridgeIface == "" { |
|
470 |
- NetworkBridgeIface = DefaultNetworkBridge |
|
469 |
+ if config.BridgeIface == "" { |
|
470 |
+ config.BridgeIface = DefaultNetworkBridge |
|
471 |
+ } |
|
472 |
+ netManager, err := newNetworkManager(config) |
|
473 |
+ if err != nil { |
|
474 |
+ return nil, err |
|
471 | 475 |
} |
472 |
- netManager, err := newNetworkManager(NetworkBridgeIface) |
|
476 |
+ |
|
477 |
+ graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db")) |
|
473 | 478 |
if err != nil { |
474 | 479 |
return nil, err |
475 | 480 |
} |
481 |
+ |
|
476 | 482 |
runtime := &Runtime{ |
477 |
- root: root, |
|
478 | 483 |
repository: runtimeRepo, |
479 | 484 |
containers: list.New(), |
480 | 485 |
networkManager: netManager, |
... | ... |
@@ -482,8 +605,9 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { |
482 | 482 |
repositories: repositories, |
483 | 483 |
idIndex: utils.NewTruncIndex(), |
484 | 484 |
capabilities: &Capabilities{}, |
485 |
- autoRestart: autoRestart, |
|
486 | 485 |
volumes: volumes, |
486 |
+ config: config, |
|
487 |
+ containerGraph: graph, |
|
487 | 488 |
} |
488 | 489 |
|
489 | 490 |
if err := runtime.restore(); err != nil { |
... | ... |
@@ -43,7 +43,7 @@ func nuke(runtime *Runtime) error { |
43 | 43 |
} |
44 | 44 |
wg.Wait() |
45 | 45 |
runtime.networkManager.Close() |
46 |
- return os.RemoveAll(runtime.root) |
|
46 |
+ return os.RemoveAll(runtime.config.GraphPath) |
|
47 | 47 |
} |
48 | 48 |
|
49 | 49 |
func cleanup(runtime *Runtime) error { |
... | ... |
@@ -85,8 +85,6 @@ func init() { |
85 | 85 |
log.Fatal("docker tests need to be run as root") |
86 | 86 |
} |
87 | 87 |
|
88 |
- NetworkBridgeIface = unitTestNetworkBridge |
|
89 |
- |
|
90 | 88 |
// Setup the base runtime, which will be duplicated for each test. |
91 | 89 |
// (no tests are run directly in the base) |
92 | 90 |
setupBaseImage() |
... | ... |
@@ -98,7 +96,12 @@ func init() { |
98 | 98 |
|
99 | 99 |
|
100 | 100 |
func setupBaseImage() { |
101 |
- runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) |
|
101 |
+ config := &DaemonConfig{ |
|
102 |
+ GraphPath: unitTestStoreBase, |
|
103 |
+ AutoRestart: false, |
|
104 |
+ BridgeIface: unitTestNetworkBridge, |
|
105 |
+ } |
|
106 |
+ runtime, err := NewRuntimeFromDirectory(config) |
|
102 | 107 |
if err != nil { |
103 | 108 |
log.Fatalf("Unable to create a runtime for tests:", err) |
104 | 109 |
} |
... | ... |
@@ -106,7 +109,6 @@ func setupBaseImage() { |
106 | 106 |
// Create the "Server" |
107 | 107 |
srv := &Server{ |
108 | 108 |
runtime: runtime, |
109 |
- enableCors: false, |
|
110 | 109 |
pullingPool: make(map[string]struct{}), |
111 | 110 |
pushingPool: make(map[string]struct{}), |
112 | 111 |
} |
... | ... |
@@ -129,7 +131,6 @@ func spawnGlobalDaemon() { |
129 | 129 |
globalRuntime = mkRuntime(log.New(os.Stderr, "", 0)) |
130 | 130 |
srv := &Server{ |
131 | 131 |
runtime: globalRuntime, |
132 |
- enableCors: false, |
|
133 | 132 |
pullingPool: make(map[string]struct{}), |
134 | 133 |
pushingPool: make(map[string]struct{}), |
135 | 134 |
} |
... | ... |
@@ -171,7 +172,7 @@ func TestRuntimeCreate(t *testing.T) { |
171 | 171 |
t.Errorf("Expected 0 containers, %v found", len(runtime.List())) |
172 | 172 |
} |
173 | 173 |
|
174 |
- container, err := runtime.Create(&Config{ |
|
174 |
+ container, _, err := runtime.Create(&Config{ |
|
175 | 175 |
Image: GetTestImage(runtime).ID, |
176 | 176 |
Cmd: []string{"ls", "-al"}, |
177 | 177 |
}, |
... | ... |
@@ -211,12 +212,12 @@ func TestRuntimeCreate(t *testing.T) { |
211 | 211 |
t.Errorf("Exists() returned false for a newly created container") |
212 | 212 |
} |
213 | 213 |
|
214 |
- // Make sure crete with bad parameters returns an error |
|
215 |
- if _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil { |
|
214 |
+ // Make sure create with bad parameters returns an error |
|
215 |
+ if _, _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil { |
|
216 | 216 |
t.Fatal("Builder.Create should throw an error when Cmd is missing") |
217 | 217 |
} |
218 | 218 |
|
219 |
- if _, err := runtime.Create( |
|
219 |
+ if _, _, err := runtime.Create( |
|
220 | 220 |
&Config{ |
221 | 221 |
Image: GetTestImage(runtime).ID, |
222 | 222 |
Cmd: []string{}, |
... | ... |
@@ -230,30 +231,19 @@ func TestRuntimeCreate(t *testing.T) { |
230 | 230 |
Cmd: []string{"/bin/ls"}, |
231 | 231 |
PortSpecs: []string{"80"}, |
232 | 232 |
} |
233 |
- container, err = runtime.Create(config) |
|
233 |
+ container, _, err = runtime.Create(config) |
|
234 | 234 |
|
235 |
- image, err := runtime.Commit(container, "testrepo", "testtag", "", "", config) |
|
235 |
+ _, err = runtime.Commit(container, "testrepo", "testtag", "", "", config) |
|
236 | 236 |
if err != nil { |
237 | 237 |
t.Error(err) |
238 | 238 |
} |
239 |
- |
|
240 |
- _, err = runtime.Create( |
|
241 |
- &Config{ |
|
242 |
- Image: image.ID, |
|
243 |
- PortSpecs: []string{"80000:80"}, |
|
244 |
- }, |
|
245 |
- ) |
|
246 |
- if err == nil { |
|
247 |
- t.Fatal("Builder.Create should throw an error when PortSpecs is invalid") |
|
248 |
- } |
|
249 |
- |
|
250 | 239 |
} |
251 | 240 |
|
252 | 241 |
func TestDestroy(t *testing.T) { |
253 | 242 |
runtime := mkRuntime(t) |
254 | 243 |
defer nuke(runtime) |
255 | 244 |
|
256 |
- container, err := runtime.Create(&Config{ |
|
245 |
+ container, _, err := runtime.Create(&Config{ |
|
257 | 246 |
Image: GetTestImage(runtime).ID, |
258 | 247 |
Cmd: []string{"ls", "-al"}, |
259 | 248 |
}) |
... | ... |
@@ -327,6 +317,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, |
327 | 327 |
strPort string |
328 | 328 |
runtime = mkRuntime(t) |
329 | 329 |
port = 5554 |
330 |
+ p Port |
|
330 | 331 |
) |
331 | 332 |
|
332 | 333 |
for { |
... | ... |
@@ -340,22 +331,34 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, |
340 | 340 |
} else { |
341 | 341 |
t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) |
342 | 342 |
} |
343 |
- container, err = runtime.Create(&Config{ |
|
344 |
- Image: GetTestImage(runtime).ID, |
|
345 |
- Cmd: []string{"sh", "-c", cmd}, |
|
346 |
- PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, |
|
343 |
+ ep := make(map[Port]struct{}, 1) |
|
344 |
+ p = Port(fmt.Sprintf("%s/%s", strPort, proto)) |
|
345 |
+ ep[p] = struct{}{} |
|
346 |
+ |
|
347 |
+ container, _, err = runtime.Create(&Config{ |
|
348 |
+ Image: GetTestImage(runtime).ID, |
|
349 |
+ Cmd: []string{"sh", "-c", cmd}, |
|
350 |
+ PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, |
|
351 |
+ ExposedPorts: ep, |
|
347 | 352 |
}) |
348 |
- if container != nil { |
|
349 |
- break |
|
350 |
- } |
|
351 | 353 |
if err != nil { |
352 | 354 |
nuke(runtime) |
353 | 355 |
t.Fatal(err) |
354 | 356 |
} |
357 |
+ |
|
358 |
+ if container != nil { |
|
359 |
+ break |
|
360 |
+ } |
|
355 | 361 |
t.Logf("Port %v already in use, trying another one", strPort) |
356 | 362 |
} |
357 | 363 |
|
358 |
- if err := container.Start(&HostConfig{}); err != nil { |
|
364 |
+ hostConfig := &HostConfig{ |
|
365 |
+ PortBindings: make(map[Port][]PortBinding), |
|
366 |
+ } |
|
367 |
+ hostConfig.PortBindings[p] = []PortBinding{ |
|
368 |
+ {}, |
|
369 |
+ } |
|
370 |
+ if err := container.Start(hostConfig); err != nil { |
|
359 | 371 |
nuke(runtime) |
360 | 372 |
t.Fatal(err) |
361 | 373 |
} |
... | ... |
@@ -369,7 +372,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, |
369 | 369 |
// Even if the state is running, lets give some time to lxc to spawn the process |
370 | 370 |
container.WaitTimeout(500 * time.Millisecond) |
371 | 371 |
|
372 |
- strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort] |
|
372 |
+ strPort = container.NetworkSettings.Ports[p][0].HostPort |
|
373 | 373 |
return runtime, container, strPort |
374 | 374 |
} |
375 | 375 |
|
... | ... |
@@ -501,7 +504,8 @@ func TestRestore(t *testing.T) { |
501 | 501 |
|
502 | 502 |
// Here are are simulating a docker restart - that is, reloading all containers |
503 | 503 |
// from scratch |
504 |
- runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) |
|
504 |
+ runtime1.config.AutoRestart = false |
|
505 |
+ runtime2, err := NewRuntimeFromDirectory(runtime1.config) |
|
505 | 506 |
if err != nil { |
506 | 507 |
t.Fatal(err) |
507 | 508 |
} |
... | ... |
@@ -528,3 +532,271 @@ func TestRestore(t *testing.T) { |
528 | 528 |
} |
529 | 529 |
container2.State.Running = false |
530 | 530 |
} |
531 |
+ |
|
532 |
+func TestReloadContainerLinks(t *testing.T) { |
|
533 |
+ runtime1 := mkRuntime(t) |
|
534 |
+ defer nuke(runtime1) |
|
535 |
+ // Create a container with one instance of docker |
|
536 |
+ container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t) |
|
537 |
+ defer runtime1.Destroy(container1) |
|
538 |
+ |
|
539 |
+ // Create a second container meant to be killed |
|
540 |
+ container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t) |
|
541 |
+ defer runtime1.Destroy(container2) |
|
542 |
+ |
|
543 |
+ // Start the container non blocking |
|
544 |
+ hostConfig := &HostConfig{} |
|
545 |
+ if err := container2.Start(hostConfig); err != nil { |
|
546 |
+ t.Fatal(err) |
|
547 |
+ } |
|
548 |
+ h1 := &HostConfig{} |
|
549 |
+ // Add a link to container 2 |
|
550 |
+ h1.Links = []string{utils.TruncateID(container2.ID) + ":first"} |
|
551 |
+ if err := container1.Start(h1); err != nil { |
|
552 |
+ t.Fatal(err) |
|
553 |
+ } |
|
554 |
+ |
|
555 |
+ if !container2.State.Running { |
|
556 |
+ t.Fatalf("Container %v should appear as running but isn't", container2.ID) |
|
557 |
+ } |
|
558 |
+ |
|
559 |
+ if !container1.State.Running { |
|
560 |
+ t.Fatalf("Container %s should appear as running bu isn't", container1.ID) |
|
561 |
+ } |
|
562 |
+ |
|
563 |
+ if len(runtime1.List()) != 2 { |
|
564 |
+ t.Errorf("Expected 2 container, %v found", len(runtime1.List())) |
|
565 |
+ } |
|
566 |
+ |
|
567 |
+ if !container2.State.Running { |
|
568 |
+ t.Fatalf("Container %v should appear as running but isn't", container2.ID) |
|
569 |
+ } |
|
570 |
+ |
|
571 |
+ // Here are are simulating a docker restart - that is, reloading all containers |
|
572 |
+ // from scratch |
|
573 |
+ runtime1.config.AutoRestart = true |
|
574 |
+ runtime2, err := NewRuntimeFromDirectory(runtime1.config) |
|
575 |
+ if err != nil { |
|
576 |
+ t.Fatal(err) |
|
577 |
+ } |
|
578 |
+ defer nuke(runtime2) |
|
579 |
+ if len(runtime2.List()) != 2 { |
|
580 |
+ t.Errorf("Expected 2 container, %v found", len(runtime2.List())) |
|
581 |
+ } |
|
582 |
+ runningCount := 0 |
|
583 |
+ for _, c := range runtime2.List() { |
|
584 |
+ if c.State.Running { |
|
585 |
+ t.Logf("Running container found: %v (%v)", c.ID, c.Path) |
|
586 |
+ runningCount++ |
|
587 |
+ } |
|
588 |
+ } |
|
589 |
+ if runningCount != 2 { |
|
590 |
+ t.Fatalf("Expected 2 container alive, %d found", runningCount) |
|
591 |
+ } |
|
592 |
+ |
|
593 |
+ // Make sure container 2 ( the child of container 1 ) was registered and started first |
|
594 |
+ // with the runtime |
|
595 |
+ first := runtime2.containers.Front() |
|
596 |
+ if first.Value.(*Container).ID != container2.ID { |
|
597 |
+ t.Fatalf("Container 2 %s should be registered first in the runtime", container2.ID) |
|
598 |
+ } |
|
599 |
+ |
|
600 |
+ t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine")) |
|
601 |
+ // Verify that the link is still registered in the runtime |
|
602 |
+ entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID)) |
|
603 |
+ if entity == nil { |
|
604 |
+ t.Fatal("Entity should not be nil") |
|
605 |
+ } |
|
606 |
+} |
|
607 |
+ |
|
608 |
+func TestDefaultContainerName(t *testing.T) { |
|
609 |
+ runtime := mkRuntime(t) |
|
610 |
+ defer nuke(runtime) |
|
611 |
+ srv := &Server{runtime: runtime} |
|
612 |
+ |
|
613 |
+ config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
614 |
+ if err != nil { |
|
615 |
+ t.Fatal(err) |
|
616 |
+ } |
|
617 |
+ |
|
618 |
+ shortId, _, err := srv.ContainerCreate(config) |
|
619 |
+ if err != nil { |
|
620 |
+ t.Fatal(err) |
|
621 |
+ } |
|
622 |
+ container := runtime.Get(shortId) |
|
623 |
+ containerID := container.ID |
|
624 |
+ |
|
625 |
+ paths := runtime.containerGraph.RefPaths(containerID) |
|
626 |
+ if paths == nil || len(paths) == 0 { |
|
627 |
+ t.Fatalf("Could not find edges for %s", containerID) |
|
628 |
+ } |
|
629 |
+ edge := paths[0] |
|
630 |
+ if edge.ParentID != "0" { |
|
631 |
+ t.Fatalf("Expected engine got %s", edge.ParentID) |
|
632 |
+ } |
|
633 |
+ if edge.EntityID != containerID { |
|
634 |
+ t.Fatalf("Expected %s got %s", containerID, edge.EntityID) |
|
635 |
+ } |
|
636 |
+ if edge.Name != containerID { |
|
637 |
+ t.Fatalf("Expected %s got %s", containerID, edge.Name) |
|
638 |
+ } |
|
639 |
+} |
|
640 |
+ |
|
641 |
+func TestDefaultContainerRename(t *testing.T) { |
|
642 |
+ runtime := mkRuntime(t) |
|
643 |
+ defer nuke(runtime) |
|
644 |
+ srv := &Server{runtime: runtime} |
|
645 |
+ |
|
646 |
+ config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
647 |
+ if err != nil { |
|
648 |
+ t.Fatal(err) |
|
649 |
+ } |
|
650 |
+ |
|
651 |
+ shortId, _, err := srv.ContainerCreate(config) |
|
652 |
+ if err != nil { |
|
653 |
+ t.Fatal(err) |
|
654 |
+ } |
|
655 |
+ container := runtime.Get(shortId) |
|
656 |
+ containerID := container.ID |
|
657 |
+ |
|
658 |
+ if err := runtime.RenameLink(fmt.Sprintf("/%s", containerID), "/webapp"); err != nil { |
|
659 |
+ t.Fatal(err) |
|
660 |
+ } |
|
661 |
+ |
|
662 |
+ webapp, err := runtime.GetByName("/webapp") |
|
663 |
+ if err != nil { |
|
664 |
+ t.Fatal(err) |
|
665 |
+ } |
|
666 |
+ |
|
667 |
+ if webapp.ID != container.ID { |
|
668 |
+ t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) |
|
669 |
+ } |
|
670 |
+} |
|
671 |
+ |
|
672 |
+func TestLinkChildContainer(t *testing.T) { |
|
673 |
+ runtime := mkRuntime(t) |
|
674 |
+ defer nuke(runtime) |
|
675 |
+ srv := &Server{runtime: runtime} |
|
676 |
+ |
|
677 |
+ config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
678 |
+ if err != nil { |
|
679 |
+ t.Fatal(err) |
|
680 |
+ } |
|
681 |
+ |
|
682 |
+ shortId, _, err := srv.ContainerCreate(config) |
|
683 |
+ if err != nil { |
|
684 |
+ t.Fatal(err) |
|
685 |
+ } |
|
686 |
+ container := runtime.Get(shortId) |
|
687 |
+ |
|
688 |
+ if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { |
|
689 |
+ t.Fatal(err) |
|
690 |
+ } |
|
691 |
+ |
|
692 |
+ webapp, err := runtime.GetByName("/webapp") |
|
693 |
+ if err != nil { |
|
694 |
+ t.Fatal(err) |
|
695 |
+ } |
|
696 |
+ |
|
697 |
+ if webapp.ID != container.ID { |
|
698 |
+ t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) |
|
699 |
+ } |
|
700 |
+ |
|
701 |
+ config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
702 |
+ if err != nil { |
|
703 |
+ t.Fatal(err) |
|
704 |
+ } |
|
705 |
+ |
|
706 |
+ shortId, _, err = srv.ContainerCreate(config) |
|
707 |
+ if err != nil { |
|
708 |
+ t.Fatal(err) |
|
709 |
+ } |
|
710 |
+ |
|
711 |
+ childContainer := runtime.Get(shortId) |
|
712 |
+ if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { |
|
713 |
+ t.Fatal(err) |
|
714 |
+ } |
|
715 |
+ |
|
716 |
+ if err := runtime.Link("/webapp", "/db", "db"); err != nil { |
|
717 |
+ t.Fatal(err) |
|
718 |
+ } |
|
719 |
+ |
|
720 |
+ // Get the child by it's new name |
|
721 |
+ db, err := runtime.GetByName("/webapp/db") |
|
722 |
+ if err != nil { |
|
723 |
+ t.Fatal(err) |
|
724 |
+ } |
|
725 |
+ if db.ID != childContainer.ID { |
|
726 |
+ t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID) |
|
727 |
+ } |
|
728 |
+} |
|
729 |
+ |
|
730 |
+func TestGetAllChildren(t *testing.T) { |
|
731 |
+ runtime := mkRuntime(t) |
|
732 |
+ defer nuke(runtime) |
|
733 |
+ srv := &Server{runtime: runtime} |
|
734 |
+ |
|
735 |
+ config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
736 |
+ if err != nil { |
|
737 |
+ t.Fatal(err) |
|
738 |
+ } |
|
739 |
+ |
|
740 |
+ shortId, _, err := srv.ContainerCreate(config) |
|
741 |
+ if err != nil { |
|
742 |
+ t.Fatal(err) |
|
743 |
+ } |
|
744 |
+ container := runtime.Get(shortId) |
|
745 |
+ |
|
746 |
+ if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { |
|
747 |
+ t.Fatal(err) |
|
748 |
+ } |
|
749 |
+ |
|
750 |
+ webapp, err := runtime.GetByName("/webapp") |
|
751 |
+ if err != nil { |
|
752 |
+ t.Fatal(err) |
|
753 |
+ } |
|
754 |
+ |
|
755 |
+ if webapp.ID != container.ID { |
|
756 |
+ t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) |
|
757 |
+ } |
|
758 |
+ |
|
759 |
+ config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) |
|
760 |
+ if err != nil { |
|
761 |
+ t.Fatal(err) |
|
762 |
+ } |
|
763 |
+ |
|
764 |
+ shortId, _, err = srv.ContainerCreate(config) |
|
765 |
+ if err != nil { |
|
766 |
+ t.Fatal(err) |
|
767 |
+ } |
|
768 |
+ |
|
769 |
+ childContainer := runtime.Get(shortId) |
|
770 |
+ if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { |
|
771 |
+ t.Fatal(err) |
|
772 |
+ } |
|
773 |
+ |
|
774 |
+ if err := runtime.Link("/webapp", "/db", "db"); err != nil { |
|
775 |
+ t.Fatal(err) |
|
776 |
+ } |
|
777 |
+ |
|
778 |
+ children, err := runtime.Children("/webapp") |
|
779 |
+ if err != nil { |
|
780 |
+ t.Fatal(err) |
|
781 |
+ } |
|
782 |
+ |
|
783 |
+ if children == nil { |
|
784 |
+ t.Fatal("Children should not be nil") |
|
785 |
+ } |
|
786 |
+ if len(children) == 0 { |
|
787 |
+ t.Fatal("Children should not be empty") |
|
788 |
+ } |
|
789 |
+ |
|
790 |
+ for key, value := range children { |
|
791 |
+ if key != "/webapp/db" { |
|
792 |
+ t.Fatalf("Expected /webapp/db got %s", key) |
|
793 |
+ } |
|
794 |
+ if value.ID != childContainer.ID { |
|
795 |
+ t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID) |
|
796 |
+ } |
|
797 |
+ } |
|
798 |
+} |
... | ... |
@@ -6,6 +6,7 @@ import ( |
6 | 6 |
"errors" |
7 | 7 |
"fmt" |
8 | 8 |
"github.com/dotcloud/docker/auth" |
9 |
+ "github.com/dotcloud/docker/gograph" |
|
9 | 10 |
"github.com/dotcloud/docker/registry" |
10 | 11 |
"github.com/dotcloud/docker/utils" |
11 | 12 |
"io" |
... | ... |
@@ -114,7 +115,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { |
114 | 114 |
} |
115 | 115 |
|
116 | 116 |
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { |
117 |
- r, err := registry.NewRegistry(srv.runtime.root, nil, srv.HTTPRequestFactory(nil)) |
|
117 |
+ r, err := registry.NewRegistry(srv.runtime.config.GraphPath, nil, srv.HTTPRequestFactory(nil)) |
|
118 | 118 |
if err != nil { |
119 | 119 |
return nil, err |
120 | 120 |
} |
... | ... |
@@ -151,7 +152,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. |
151 | 151 |
return "", err |
152 | 152 |
} |
153 | 153 |
|
154 |
- c, err := srv.runtime.Create(config) |
|
154 |
+ c, _, err := srv.runtime.Create(config) |
|
155 | 155 |
if err != nil { |
156 | 156 |
return "", err |
157 | 157 |
} |
... | ... |
@@ -369,7 +370,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) { |
369 | 369 |
func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers { |
370 | 370 |
var foundBefore bool |
371 | 371 |
var displayed int |
372 |
- retContainers := []APIContainers{} |
|
372 |
+ out := []APIContainers{} |
|
373 | 373 |
|
374 | 374 |
for _, container := range srv.runtime.List() { |
375 | 375 |
if !container.State.Running && !all && n == -1 && since == "" && before == "" { |
... | ... |
@@ -391,23 +392,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API |
391 | 391 |
break |
392 | 392 |
} |
393 | 393 |
displayed++ |
394 |
- |
|
395 |
- c := APIContainers{ |
|
396 |
- ID: container.ID, |
|
397 |
- } |
|
398 |
- c.Image = srv.runtime.repositories.ImageName(container.Image) |
|
399 |
- c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) |
|
400 |
- c.Created = container.Created.Unix() |
|
401 |
- c.Status = container.State.String() |
|
402 |
- c.Ports = container.NetworkSettings.PortMappingAPI() |
|
403 |
- if size { |
|
404 |
- c.SizeRw, c.SizeRootFs = container.GetSize() |
|
405 |
- } |
|
406 |
- retContainers = append(retContainers, c) |
|
394 |
+ c := createAPIContainer(container, size, srv.runtime) |
|
395 |
+ out = append(out, c) |
|
407 | 396 |
} |
408 |
- return retContainers |
|
397 |
+ return out |
|
409 | 398 |
} |
410 | 399 |
|
400 |
+func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers { |
|
401 |
+ c := APIContainers{ |
|
402 |
+ ID: container.ID, |
|
403 |
+ } |
|
404 |
+ names := []string{} |
|
405 |
+ runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { |
|
406 |
+ if e.ID() == container.ID { |
|
407 |
+ names = append(names, p) |
|
408 |
+ } |
|
409 |
+ return nil |
|
410 |
+ }, -1) |
|
411 |
+ c.Names = names |
|
412 |
+ |
|
413 |
+ c.Image = runtime.repositories.ImageName(container.Image) |
|
414 |
+ c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) |
|
415 |
+ c.Created = container.Created.Unix() |
|
416 |
+ c.Status = container.State.String() |
|
417 |
+ c.Ports = container.NetworkSettings.PortMappingAPI() |
|
418 |
+ if size { |
|
419 |
+ c.SizeRw, c.SizeRootFs = container.GetSize() |
|
420 |
+ } |
|
421 |
+ return c |
|
422 |
+} |
|
411 | 423 |
func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) { |
412 | 424 |
container := srv.runtime.Get(name) |
413 | 425 |
if container == nil { |
... | ... |
@@ -646,7 +659,7 @@ func (srv *Server) poolRemove(kind, key string) error { |
646 | 646 |
} |
647 | 647 |
|
648 | 648 |
func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error { |
649 |
- r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) |
|
649 |
+ r, err := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders)) |
|
650 | 650 |
if err != nil { |
651 | 651 |
return err |
652 | 652 |
} |
... | ... |
@@ -855,7 +868,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo |
855 | 855 |
|
856 | 856 |
out = utils.NewWriteFlusher(out) |
857 | 857 |
img, err := srv.runtime.graph.Get(localName) |
858 |
- r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) |
|
858 |
+ r, err2 := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders)) |
|
859 | 859 |
if err2 != nil { |
860 | 860 |
return err2 |
861 | 861 |
} |
... | ... |
@@ -920,10 +933,9 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write |
920 | 920 |
return nil |
921 | 921 |
} |
922 | 922 |
|
923 |
-func (srv *Server) ContainerCreate(config *Config) (string, error) { |
|
924 |
- |
|
923 |
+func (srv *Server) ContainerCreate(config *Config) (string, []string, error) { |
|
925 | 924 |
if config.Memory != 0 && config.Memory < 524288 { |
926 |
- return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") |
|
925 |
+ return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") |
|
927 | 926 |
} |
928 | 927 |
|
929 | 928 |
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { |
... | ... |
@@ -933,7 +945,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { |
933 | 933 |
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { |
934 | 934 |
config.MemorySwap = -1 |
935 | 935 |
} |
936 |
- container, err := srv.runtime.Create(config) |
|
936 |
+ container, buildWarnings, err := srv.runtime.Create(config) |
|
937 | 937 |
if err != nil { |
938 | 938 |
if srv.runtime.graph.IsNotExist(err) { |
939 | 939 |
|
... | ... |
@@ -942,12 +954,12 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { |
942 | 942 |
tag = DEFAULTTAG |
943 | 943 |
} |
944 | 944 |
|
945 |
- return "", fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) |
|
945 |
+ return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) |
|
946 | 946 |
} |
947 |
- return "", err |
|
947 |
+ return "", nil, err |
|
948 | 948 |
} |
949 | 949 |
srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) |
950 |
- return container.ShortID(), nil |
|
950 |
+ return container.ShortID(), buildWarnings, nil |
|
951 | 951 |
} |
952 | 952 |
|
953 | 953 |
func (srv *Server) ContainerRestart(name string, t int) error { |
... | ... |
@@ -962,7 +974,34 @@ func (srv *Server) ContainerRestart(name string, t int) error { |
962 | 962 |
return nil |
963 | 963 |
} |
964 | 964 |
|
965 |
-func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { |
|
965 |
+func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error { |
|
966 |
+ if removeLink { |
|
967 |
+ p := name |
|
968 |
+ if p[0] != '/' { |
|
969 |
+ p = "/" + p |
|
970 |
+ } |
|
971 |
+ parent, n := path.Split(p) |
|
972 |
+ l := len(parent) |
|
973 |
+ if parent[l-1] == '/' { |
|
974 |
+ parent = parent[:l-1] |
|
975 |
+ } |
|
976 |
+ |
|
977 |
+ pe := srv.runtime.containerGraph.Get(parent) |
|
978 |
+ parentContainer := srv.runtime.Get(pe.ID()) |
|
979 |
+ |
|
980 |
+ if parentContainer != nil && parentContainer.activeLinks != nil { |
|
981 |
+ if link, exists := parentContainer.activeLinks[n]; exists { |
|
982 |
+ link.Disable() |
|
983 |
+ } else { |
|
984 |
+ utils.Debugf("Could not find active link for %s", name) |
|
985 |
+ } |
|
986 |
+ } |
|
987 |
+ |
|
988 |
+ if err := srv.runtime.containerGraph.Delete(name); err != nil { |
|
989 |
+ return err |
|
990 |
+ } |
|
991 |
+ return nil |
|
992 |
+ } |
|
966 | 993 |
if container := srv.runtime.Get(name); container != nil { |
967 | 994 |
if container.State.Running { |
968 | 995 |
return fmt.Errorf("Impossible to remove a running container, please stop it first") |
... | ... |
@@ -1162,14 +1201,32 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) |
1162 | 1162 |
} |
1163 | 1163 |
|
1164 | 1164 |
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { |
1165 |
- if container := srv.runtime.Get(name); container != nil { |
|
1166 |
- if err := container.Start(hostConfig); err != nil { |
|
1167 |
- return fmt.Errorf("Error starting container %s: %s", name, err) |
|
1168 |
- } |
|
1169 |
- srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) |
|
1170 |
- } else { |
|
1165 |
+ runtime := srv.runtime |
|
1166 |
+ container := runtime.Get(name) |
|
1167 |
+ if container == nil { |
|
1171 | 1168 |
return fmt.Errorf("No such container: %s", name) |
1172 | 1169 |
} |
1170 |
+ |
|
1171 |
+ // Register links |
|
1172 |
+ if hostConfig != nil && hostConfig.Links != nil { |
|
1173 |
+ for _, l := range hostConfig.Links { |
|
1174 |
+ parts, err := parseLink(l) |
|
1175 |
+ if err != nil { |
|
1176 |
+ return err |
|
1177 |
+ } |
|
1178 |
+ |
|
1179 |
+ childName := parts["name"] |
|
1180 |
+ if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil { |
|
1181 |
+ return err |
|
1182 |
+ } |
|
1183 |
+ } |
|
1184 |
+ } |
|
1185 |
+ |
|
1186 |
+ if err := container.Start(hostConfig); err != nil { |
|
1187 |
+ return fmt.Errorf("Error starting container %s: %s", name, err) |
|
1188 |
+ } |
|
1189 |
+ srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image)) |
|
1190 |
+ |
|
1173 | 1191 |
return nil |
1174 | 1192 |
} |
1175 | 1193 |
|
... | ... |
@@ -1321,17 +1378,16 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er |
1321 | 1321 |
|
1322 | 1322 |
} |
1323 | 1323 |
|
1324 |
-func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { |
|
1324 |
+func NewServer(config *DaemonConfig) (*Server, error) { |
|
1325 | 1325 |
if runtime.GOARCH != "amd64" { |
1326 | 1326 |
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) |
1327 | 1327 |
} |
1328 |
- runtime, err := NewRuntime(flGraphPath, autoRestart, dns) |
|
1328 |
+ runtime, err := NewRuntime(config) |
|
1329 | 1329 |
if err != nil { |
1330 | 1330 |
return nil, err |
1331 | 1331 |
} |
1332 | 1332 |
srv := &Server{ |
1333 | 1333 |
runtime: runtime, |
1334 |
- enableCors: enableCors, |
|
1335 | 1334 |
pullingPool: make(map[string]struct{}), |
1336 | 1335 |
pushingPool: make(map[string]struct{}), |
1337 | 1336 |
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events |
... | ... |
@@ -1369,7 +1425,6 @@ func (srv *Server) LogEvent(action, id, from string) { |
1369 | 1369 |
type Server struct { |
1370 | 1370 |
sync.Mutex |
1371 | 1371 |
runtime *Runtime |
1372 |
- enableCors bool |
|
1373 | 1372 |
pullingPool map[string]struct{} |
1374 | 1373 |
pushingPool map[string]struct{} |
1375 | 1374 |
events []utils.JSONMessage |
... | ... |
@@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) { |
89 | 89 |
t.Fatal(err) |
90 | 90 |
} |
91 | 91 |
|
92 |
- id, err := srv.ContainerCreate(config) |
|
92 |
+ id, _, err := srv.ContainerCreate(config) |
|
93 | 93 |
if err != nil { |
94 | 94 |
t.Fatal(err) |
95 | 95 |
} |
... | ... |
@@ -98,7 +98,7 @@ func TestCreateRm(t *testing.T) { |
98 | 98 |
t.Errorf("Expected 1 container, %v found", len(runtime.List())) |
99 | 99 |
} |
100 | 100 |
|
101 |
- if err = srv.ContainerDestroy(id, true); err != nil { |
|
101 |
+ if err = srv.ContainerDestroy(id, true, false); err != nil { |
|
102 | 102 |
t.Fatal(err) |
103 | 103 |
} |
104 | 104 |
|
... | ... |
@@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) { |
119 | 119 |
t.Fatal(err) |
120 | 120 |
} |
121 | 121 |
|
122 |
- id, err := srv.ContainerCreate(config) |
|
122 |
+ id, _, err := srv.ContainerCreate(config) |
|
123 | 123 |
if err != nil { |
124 | 124 |
t.Fatal(err) |
125 | 125 |
} |
... | ... |
@@ -138,7 +138,7 @@ func TestCreateRmVolumes(t *testing.T) { |
138 | 138 |
t.Fatal(err) |
139 | 139 |
} |
140 | 140 |
|
141 |
- if err = srv.ContainerDestroy(id, true); err != nil { |
|
141 |
+ if err = srv.ContainerDestroy(id, true, false); err != nil { |
|
142 | 142 |
t.Fatal(err) |
143 | 143 |
} |
144 | 144 |
|
... | ... |
@@ -158,7 +158,7 @@ func TestCommit(t *testing.T) { |
158 | 158 |
t.Fatal(err) |
159 | 159 |
} |
160 | 160 |
|
161 |
- id, err := srv.ContainerCreate(config) |
|
161 |
+ id, _, err := srv.ContainerCreate(config) |
|
162 | 162 |
if err != nil { |
163 | 163 |
t.Fatal(err) |
164 | 164 |
} |
... | ... |
@@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { |
179 | 179 |
t.Fatal(err) |
180 | 180 |
} |
181 | 181 |
|
182 |
- id, err := srv.ContainerCreate(config) |
|
182 |
+ id, _, err := srv.ContainerCreate(config) |
|
183 | 183 |
if err != nil { |
184 | 184 |
t.Fatal(err) |
185 | 185 |
} |
... | ... |
@@ -209,7 +209,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { |
209 | 209 |
} |
210 | 210 |
|
211 | 211 |
// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty") |
212 |
- if err := srv.ContainerDestroy(id, true); err != nil { |
|
212 |
+ if err := srv.ContainerDestroy(id, true, false); err != nil { |
|
213 | 213 |
t.Fatal(err) |
214 | 214 |
} |
215 | 215 |
|
... | ... |
@@ -224,7 +224,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { |
224 | 224 |
defer nuke(runtime) |
225 | 225 |
|
226 | 226 |
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. |
227 |
- if _, err := (*Server).ContainerCreate(&Server{runtime: runtime}, |
|
227 |
+ if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime}, |
|
228 | 228 |
&Config{ |
229 | 229 |
Image: GetTestImage(runtime).ID, |
230 | 230 |
Memory: 524287, |
... | ... |
@@ -397,7 +397,7 @@ func TestRmi(t *testing.T) { |
397 | 397 |
t.Fatal(err) |
398 | 398 |
} |
399 | 399 |
|
400 |
- containerID, err := srv.ContainerCreate(config) |
|
400 |
+ containerID, _, err := srv.ContainerCreate(config) |
|
401 | 401 |
if err != nil { |
402 | 402 |
t.Fatal(err) |
403 | 403 |
} |
... | ... |
@@ -418,7 +418,7 @@ func TestRmi(t *testing.T) { |
418 | 418 |
t.Fatal(err) |
419 | 419 |
} |
420 | 420 |
|
421 |
- containerID, err = srv.ContainerCreate(config) |
|
421 |
+ containerID, _, err = srv.ContainerCreate(config) |
|
422 | 422 |
if err != nil { |
423 | 423 |
t.Fatal(err) |
424 | 424 |
} |
... | ... |
@@ -34,3 +34,72 @@ func sortImagesByCreationAndTag(images []APIImages) { |
34 | 34 |
|
35 | 35 |
sort.Sort(sorter) |
36 | 36 |
} |
37 |
+ |
|
38 |
+type portSorter struct { |
|
39 |
+ ports []Port |
|
40 |
+ by func(i, j Port) bool |
|
41 |
+} |
|
42 |
+ |
|
43 |
+func (s *portSorter) Len() int { |
|
44 |
+ return len(s.ports) |
|
45 |
+} |
|
46 |
+ |
|
47 |
+func (s *portSorter) Swap(i, j int) { |
|
48 |
+ s.ports[i], s.ports[j] = s.ports[j], s.ports[i] |
|
49 |
+} |
|
50 |
+ |
|
51 |
+func (s *portSorter) Less(i, j int) bool { |
|
52 |
+ ip := s.ports[i] |
|
53 |
+ jp := s.ports[j] |
|
54 |
+ |
|
55 |
+ return s.by(ip, jp) |
|
56 |
+} |
|
57 |
+ |
|
58 |
+func sortPorts(ports []Port, predicate func(i, j Port) bool) { |
|
59 |
+ s := &portSorter{ports, predicate} |
|
60 |
+ sort.Sort(s) |
|
61 |
+} |
|
62 |
+ |
|
63 |
+type containerSorter struct { |
|
64 |
+ containers []*Container |
|
65 |
+ by func(i, j *Container) bool |
|
66 |
+} |
|
67 |
+ |
|
68 |
+func (s *containerSorter) Len() int { |
|
69 |
+ return len(s.containers) |
|
70 |
+} |
|
71 |
+ |
|
72 |
+func (s *containerSorter) Swap(i, j int) { |
|
73 |
+ s.containers[i], s.containers[j] = s.containers[j], s.containers[i] |
|
74 |
+} |
|
75 |
+ |
|
76 |
+func (s *containerSorter) Less(i, j int) bool { |
|
77 |
+ return s.by(s.containers[i], s.containers[j]) |
|
78 |
+} |
|
79 |
+ |
|
80 |
+func sortContainers(containers []*Container, predicate func(i, j *Container) bool) { |
|
81 |
+ s := &containerSorter{containers, predicate} |
|
82 |
+ sort.Sort(s) |
|
83 |
+} |
|
84 |
+ |
|
85 |
+type apiLinkSorter struct { |
|
86 |
+ links []APILink |
|
87 |
+ by func(i, j APILink) bool |
|
88 |
+} |
|
89 |
+ |
|
90 |
+func (s *apiLinkSorter) Len() int { |
|
91 |
+ return len(s.links) |
|
92 |
+} |
|
93 |
+ |
|
94 |
+func (s *apiLinkSorter) Swap(i, j int) { |
|
95 |
+ s.links[i], s.links[j] = s.links[j], s.links[i] |
|
96 |
+} |
|
97 |
+ |
|
98 |
+func (s *apiLinkSorter) Less(i, j int) bool { |
|
99 |
+ return s.by(s.links[i], s.links[j]) |
|
100 |
+} |
|
101 |
+ |
|
102 |
+func sortLinks(links []APILink, predicate func(i, j APILink) bool) { |
|
103 |
+ s := &apiLinkSorter{links, predicate} |
|
104 |
+ sort.Sort(s) |
|
105 |
+} |
... | ... |
@@ -1,6 +1,7 @@ |
1 | 1 |
package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ "fmt" |
|
4 | 5 |
"testing" |
5 | 6 |
) |
6 | 7 |
|
... | ... |
@@ -55,3 +56,38 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { |
55 | 55 |
t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.") |
56 | 56 |
} |
57 | 57 |
} |
58 |
+ |
|
59 |
+func TestSortUniquePorts(t *testing.T) { |
|
60 |
+ ports := []Port{ |
|
61 |
+ Port("6379/tcp"), |
|
62 |
+ Port("22/tcp"), |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ sortPorts(ports, func(ip, jp Port) bool { |
|
66 |
+ return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") |
|
67 |
+ }) |
|
68 |
+ |
|
69 |
+ first := ports[0] |
|
70 |
+ if fmt.Sprint(first) != "22/tcp" { |
|
71 |
+ t.Log(fmt.Sprint(first)) |
|
72 |
+ t.Fail() |
|
73 |
+ } |
|
74 |
+} |
|
75 |
+ |
|
76 |
+func TestSortSamePortWithDifferentProto(t *testing.T) { |
|
77 |
+ ports := []Port{ |
|
78 |
+ Port("8888/tcp"), |
|
79 |
+ Port("8888/udp"), |
|
80 |
+ Port("6379/tcp"), |
|
81 |
+ Port("6379/udp"), |
|
82 |
+ } |
|
83 |
+ |
|
84 |
+ sortPorts(ports, func(ip, jp Port) bool { |
|
85 |
+ return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") |
|
86 |
+ }) |
|
87 |
+ |
|
88 |
+ first := ports[0] |
|
89 |
+ if fmt.Sprint(first) != "6379/tcp" { |
|
90 |
+ t.Fail() |
|
91 |
+ } |
|
92 |
+} |
... | ... |
@@ -2,6 +2,8 @@ package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
+ "github.com/dotcloud/docker/utils" |
|
6 |
+ "strconv" |
|
5 | 7 |
"strings" |
6 | 8 |
) |
7 | 9 |
|
... | ... |
@@ -27,6 +29,7 @@ func CompareConfig(a, b *Config) bool { |
27 | 27 |
len(a.Dns) != len(b.Dns) || |
28 | 28 |
len(a.Env) != len(b.Env) || |
29 | 29 |
len(a.PortSpecs) != len(b.PortSpecs) || |
30 |
+ len(a.ExposedPorts) != len(b.ExposedPorts) || |
|
30 | 31 |
len(a.Entrypoint) != len(b.Entrypoint) || |
31 | 32 |
len(a.Volumes) != len(b.Volumes) { |
32 | 33 |
return false |
... | ... |
@@ -52,6 +55,11 @@ func CompareConfig(a, b *Config) bool { |
52 | 52 |
return false |
53 | 53 |
} |
54 | 54 |
} |
55 |
+ for k := range a.ExposedPorts { |
|
56 |
+ if _, exists := b.ExposedPorts[k]; !exists { |
|
57 |
+ return false |
|
58 |
+ } |
|
59 |
+ } |
|
55 | 60 |
for i := 0; i < len(a.Entrypoint); i++ { |
56 | 61 |
if a.Entrypoint[i] != b.Entrypoint[i] { |
57 | 62 |
return false |
... | ... |
@@ -78,26 +86,38 @@ func MergeConfig(userConf, imageConf *Config) error { |
78 | 78 |
if userConf.CpuShares == 0 { |
79 | 79 |
userConf.CpuShares = imageConf.CpuShares |
80 | 80 |
} |
81 |
- if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { |
|
82 |
- userConf.PortSpecs = imageConf.PortSpecs |
|
83 |
- } else { |
|
84 |
- for _, imagePortSpec := range imageConf.PortSpecs { |
|
85 |
- found := false |
|
86 |
- imageNat, err := parseNat(imagePortSpec) |
|
87 |
- if err != nil { |
|
88 |
- return err |
|
89 |
- } |
|
90 |
- for _, userPortSpec := range userConf.PortSpecs { |
|
91 |
- userNat, err := parseNat(userPortSpec) |
|
92 |
- if err != nil { |
|
93 |
- return err |
|
94 |
- } |
|
95 |
- if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { |
|
96 |
- found = true |
|
97 |
- } |
|
81 |
+ if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { |
|
82 |
+ userConf.ExposedPorts = imageConf.ExposedPorts |
|
83 |
+ } |
|
84 |
+ |
|
85 |
+ if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { |
|
86 |
+ if userConf.ExposedPorts == nil { |
|
87 |
+ userConf.ExposedPorts = make(map[Port]struct{}) |
|
88 |
+ } |
|
89 |
+ ports, _, err := parsePortSpecs(userConf.PortSpecs) |
|
90 |
+ if err != nil { |
|
91 |
+ return err |
|
92 |
+ } |
|
93 |
+ for port := range ports { |
|
94 |
+ if _, exists := userConf.ExposedPorts[port]; !exists { |
|
95 |
+ userConf.ExposedPorts[port] = struct{}{} |
|
98 | 96 |
} |
99 |
- if !found { |
|
100 |
- userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec) |
|
97 |
+ } |
|
98 |
+ userConf.PortSpecs = nil |
|
99 |
+ } |
|
100 |
+ if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { |
|
101 |
+ utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) |
|
102 |
+ if userConf.ExposedPorts == nil { |
|
103 |
+ userConf.ExposedPorts = make(map[Port]struct{}) |
|
104 |
+ } |
|
105 |
+ |
|
106 |
+ ports, _, err := parsePortSpecs(imageConf.PortSpecs) |
|
107 |
+ if err != nil { |
|
108 |
+ return err |
|
109 |
+ } |
|
110 |
+ for port := range ports { |
|
111 |
+ if _, exists := userConf.ExposedPorts[port]; !exists { |
|
112 |
+ userConf.ExposedPorts[port] = struct{}{} |
|
101 | 113 |
} |
102 | 114 |
} |
103 | 115 |
} |
... | ... |
@@ -174,3 +194,98 @@ func parseLxcOpt(opt string) (string, string, error) { |
174 | 174 |
} |
175 | 175 |
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil |
176 | 176 |
} |
177 |
+ |
|
178 |
+// We will receive port specs in the format of ip:public:private/proto and these need to be |
|
179 |
+// parsed in the internal types |
|
180 |
+func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { |
|
181 |
+ exposedPorts := make(map[Port]struct{}, len(ports)) |
|
182 |
+ bindings := make(map[Port][]PortBinding) |
|
183 |
+ |
|
184 |
+ for _, rawPort := range ports { |
|
185 |
+ proto := "tcp" |
|
186 |
+ if i := strings.LastIndex(rawPort, "/"); i != -1 { |
|
187 |
+ proto = rawPort[i+1:] |
|
188 |
+ rawPort = rawPort[:i] |
|
189 |
+ } |
|
190 |
+ if !strings.Contains(rawPort, ":") { |
|
191 |
+ rawPort = fmt.Sprintf("::%s", rawPort) |
|
192 |
+ } else if len(strings.Split(rawPort, ":")) == 2 { |
|
193 |
+ rawPort = fmt.Sprintf(":%s", rawPort) |
|
194 |
+ } |
|
195 |
+ |
|
196 |
+ parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort) |
|
197 |
+ if err != nil { |
|
198 |
+ return nil, nil, err |
|
199 |
+ } |
|
200 |
+ containerPort := parts["containerPort"] |
|
201 |
+ rawIp := parts["ip"] |
|
202 |
+ hostPort := parts["hostPort"] |
|
203 |
+ |
|
204 |
+ if containerPort == "" { |
|
205 |
+ return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort) |
|
206 |
+ } |
|
207 |
+ |
|
208 |
+ port := NewPort(proto, containerPort) |
|
209 |
+ if _, exists := exposedPorts[port]; !exists { |
|
210 |
+ exposedPorts[port] = struct{}{} |
|
211 |
+ } |
|
212 |
+ |
|
213 |
+ binding := PortBinding{ |
|
214 |
+ HostIp: rawIp, |
|
215 |
+ HostPort: hostPort, |
|
216 |
+ } |
|
217 |
+ bslice, exists := bindings[port] |
|
218 |
+ if !exists { |
|
219 |
+ bslice = []PortBinding{} |
|
220 |
+ } |
|
221 |
+ bindings[port] = append(bslice, binding) |
|
222 |
+ } |
|
223 |
+ return exposedPorts, bindings, nil |
|
224 |
+} |
|
225 |
+ |
|
226 |
+// Splits a port in the format of port/proto |
|
227 |
+func splitProtoPort(rawPort string) (string, string) { |
|
228 |
+ parts := strings.Split(rawPort, "/") |
|
229 |
+ l := len(parts) |
|
230 |
+ if l == 0 { |
|
231 |
+ return "", "" |
|
232 |
+ } |
|
233 |
+ if l == 1 { |
|
234 |
+ return "tcp", rawPort |
|
235 |
+ } |
|
236 |
+ return parts[0], parts[1] |
|
237 |
+} |
|
238 |
+ |
|
239 |
+func parsePort(rawPort string) (int, error) { |
|
240 |
+ port, err := strconv.ParseUint(rawPort, 10, 16) |
|
241 |
+ if err != nil { |
|
242 |
+ return 0, err |
|
243 |
+ } |
|
244 |
+ return int(port), nil |
|
245 |
+} |
|
246 |
+ |
|
247 |
+func migratePortMappings(config *Config) error { |
|
248 |
+ if config.PortSpecs != nil { |
|
249 |
+ // We don't have to worry about migrating the bindings to the host |
|
250 |
+ // This is our breaking change |
|
251 |
+ ports, _, err := parsePortSpecs(config.PortSpecs) |
|
252 |
+ if err != nil { |
|
253 |
+ return err |
|
254 |
+ } |
|
255 |
+ config.PortSpecs = nil |
|
256 |
+ |
|
257 |
+ if config.ExposedPorts == nil { |
|
258 |
+ config.ExposedPorts = make(map[Port]struct{}, len(ports)) |
|
259 |
+ } |
|
260 |
+ for k, v := range ports { |
|
261 |
+ config.ExposedPorts[k] = v |
|
262 |
+ } |
|
263 |
+ } |
|
264 |
+ return nil |
|
265 |
+} |
|
266 |
+ |
|
267 |
+// Links come in the format of |
|
268 |
+// name:alias |
|
269 |
+func parseLink(rawLink string) (map[string]string, error) { |
|
270 |
+ return utils.PartParser("name:alias", rawLink) |
|
271 |
+} |
... | ... |
@@ -1044,3 +1044,22 @@ func IsClosedError(err error) bool { |
1044 | 1044 |
*/ |
1045 | 1045 |
return strings.HasSuffix(err.Error(), "use of closed network connection") |
1046 | 1046 |
} |
1047 |
+ |
|
1048 |
+func PartParser(template, data string) (map[string]string, error) { |
|
1049 |
+ // ip:public:private |
|
1050 |
+ templateParts := strings.Split(template, ":") |
|
1051 |
+ parts := strings.Split(data, ":") |
|
1052 |
+ if len(parts) != len(templateParts) { |
|
1053 |
+ return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) |
|
1054 |
+ } |
|
1055 |
+ out := make(map[string]string, len(templateParts)) |
|
1056 |
+ |
|
1057 |
+ for i, t := range templateParts { |
|
1058 |
+ value := "" |
|
1059 |
+ if len(parts) > i { |
|
1060 |
+ value = parts[i] |
|
1061 |
+ } |
|
1062 |
+ out[t] = value |
|
1063 |
+ } |
|
1064 |
+ return out, nil |
|
1065 |
+} |
... | ... |
@@ -424,3 +424,23 @@ func TestDependencyGraph(t *testing.T) { |
424 | 424 |
t.Fatalf("Expected [d], found %v instead", res[2]) |
425 | 425 |
} |
426 | 426 |
} |
427 |
+ |
|
428 |
+func TestParsePortMapping(t *testing.T) { |
|
429 |
+ data, err := PartParser("ip:public:private", "192.168.1.1:80:8080") |
|
430 |
+ if err != nil { |
|
431 |
+ t.Fatal(err) |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ if len(data) != 3 { |
|
435 |
+ t.FailNow() |
|
436 |
+ } |
|
437 |
+ if data["ip"] != "192.168.1.1" { |
|
438 |
+ t.Fail() |
|
439 |
+ } |
|
440 |
+ if data["public"] != "80" { |
|
441 |
+ t.Fail() |
|
442 |
+ } |
|
443 |
+ if data["private"] != "8080" { |
|
444 |
+ t.Fail() |
|
445 |
+ } |
|
446 |
+} |
... | ... |
@@ -66,7 +66,11 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) { |
66 | 66 |
return nil, err |
67 | 67 |
} |
68 | 68 |
|
69 |
- runtime, err = NewRuntimeFromDirectory(root, false) |
|
69 |
+ config := &DaemonConfig{ |
|
70 |
+ GraphPath: root, |
|
71 |
+ AutoRestart: false, |
|
72 |
+ } |
|
73 |
+ runtime, err = NewRuntimeFromDirectory(config) |
|
70 | 74 |
if err != nil { |
71 | 75 |
return nil, err |
72 | 76 |
} |
... | ... |
@@ -125,7 +129,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf |
125 | 125 |
if config.Image == "_" { |
126 | 126 |
config.Image = GetTestImage(r).ID |
127 | 127 |
} |
128 |
- c, err := r.Create(config) |
|
128 |
+ c, _, err := r.Create(config) |
|
129 | 129 |
if err != nil { |
130 | 130 |
return nil, nil, err |
131 | 131 |
} |
... | ... |
@@ -253,12 +257,12 @@ func TestMergeConfig(t *testing.T) { |
253 | 253 |
} |
254 | 254 |
} |
255 | 255 |
|
256 |
- if len(configUser.PortSpecs) != 3 { |
|
257 |
- t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) |
|
256 |
+ if len(configUser.ExposedPorts) != 3 { |
|
257 |
+ t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs)) |
|
258 | 258 |
} |
259 |
- for _, portSpecs := range configUser.PortSpecs { |
|
260 |
- if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { |
|
261 |
- t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) |
|
259 |
+ for portSpecs := range configUser.ExposedPorts { |
|
260 |
+ if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { |
|
261 |
+ t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs) |
|
262 | 262 |
} |
263 | 263 |
} |
264 | 264 |
if len(configUser.Env) != 3 { |
... | ... |
@@ -284,60 +288,144 @@ func TestMergeConfig(t *testing.T) { |
284 | 284 |
} |
285 | 285 |
} |
286 | 286 |
|
287 |
-func TestMergeConfigPublicPortNotHonored(t *testing.T) { |
|
288 |
- volumesImage := make(map[string]struct{}) |
|
289 |
- volumesImage["/test1"] = struct{}{} |
|
290 |
- volumesImage["/test2"] = struct{}{} |
|
291 |
- configImage := &Config{ |
|
292 |
- Dns: []string{"1.1.1.1", "2.2.2.2"}, |
|
293 |
- PortSpecs: []string{"1111", "2222"}, |
|
294 |
- Env: []string{"VAR1=1", "VAR2=2"}, |
|
295 |
- Volumes: volumesImage, |
|
296 |
- } |
|
287 |
+func TestParseLxcConfOpt(t *testing.T) { |
|
288 |
+ opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} |
|
297 | 289 |
|
298 |
- volumesUser := make(map[string]struct{}) |
|
299 |
- volumesUser["/test3"] = struct{}{} |
|
300 |
- configUser := &Config{ |
|
301 |
- Dns: []string{"3.3.3.3"}, |
|
302 |
- PortSpecs: []string{"1111:3333"}, |
|
303 |
- Env: []string{"VAR2=3", "VAR3=3"}, |
|
304 |
- Volumes: volumesUser, |
|
290 |
+ for _, o := range opts { |
|
291 |
+ k, v, err := parseLxcOpt(o) |
|
292 |
+ if err != nil { |
|
293 |
+ t.FailNow() |
|
294 |
+ } |
|
295 |
+ if k != "lxc.utsname" { |
|
296 |
+ t.Fail() |
|
297 |
+ } |
|
298 |
+ if v != "docker" { |
|
299 |
+ t.Fail() |
|
300 |
+ } |
|
305 | 301 |
} |
302 |
+} |
|
306 | 303 |
|
307 |
- MergeConfig(configUser, configImage) |
|
308 |
- |
|
309 |
- contains := func(a []string, expect string) bool { |
|
310 |
- for _, p := range a { |
|
311 |
- if p == expect { |
|
312 |
- return true |
|
313 |
- } |
|
304 |
+func TestParseNetworkOptsPrivateOnly(t *testing.T) { |
|
305 |
+ ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"}) |
|
306 |
+ if err != nil { |
|
307 |
+ t.Fatal(err) |
|
308 |
+ } |
|
309 |
+ if len(ports) != 1 { |
|
310 |
+ t.Logf("Expected 1 got %d", len(ports)) |
|
311 |
+ t.FailNow() |
|
312 |
+ } |
|
313 |
+ if len(bindings) != 1 { |
|
314 |
+ t.Logf("Expected 1 got %d", len(bindings)) |
|
315 |
+ t.FailNow() |
|
316 |
+ } |
|
317 |
+ for k := range ports { |
|
318 |
+ if k.Proto() != "tcp" { |
|
319 |
+ t.Logf("Expected tcp got %s", k.Proto()) |
|
320 |
+ t.Fail() |
|
321 |
+ } |
|
322 |
+ if k.Port() != "80" { |
|
323 |
+ t.Logf("Expected 80 got %s", k.Port()) |
|
324 |
+ t.Fail() |
|
325 |
+ } |
|
326 |
+ b, exists := bindings[k] |
|
327 |
+ if !exists { |
|
328 |
+ t.Log("Binding does not exist") |
|
329 |
+ t.FailNow() |
|
330 |
+ } |
|
331 |
+ if len(b) != 1 { |
|
332 |
+ t.Logf("Expected 1 got %d", len(b)) |
|
333 |
+ t.FailNow() |
|
334 |
+ } |
|
335 |
+ s := b[0] |
|
336 |
+ if s.HostPort != "" { |
|
337 |
+ t.Logf("Expected \"\" got %s", s.HostPort) |
|
338 |
+ t.Fail() |
|
339 |
+ } |
|
340 |
+ if s.HostIp != "192.168.1.100" { |
|
341 |
+ t.Fail() |
|
314 | 342 |
} |
315 |
- return false |
|
316 | 343 |
} |
344 |
+} |
|
317 | 345 |
|
318 |
- if !contains(configUser.PortSpecs, "2222") { |
|
319 |
- t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs) |
|
320 |
- t.Fail() |
|
346 |
+func TestParseNetworkOptsPublic(t *testing.T) { |
|
347 |
+ ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"}) |
|
348 |
+ if err != nil { |
|
349 |
+ t.Fatal(err) |
|
321 | 350 |
} |
322 |
- |
|
323 |
- if !contains(configUser.PortSpecs, "1111:3333") { |
|
324 |
- t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs) |
|
325 |
- t.Fail() |
|
351 |
+ if len(ports) != 1 { |
|
352 |
+ t.Logf("Expected 1 got %d", len(ports)) |
|
353 |
+ t.FailNow() |
|
354 |
+ } |
|
355 |
+ if len(bindings) != 1 { |
|
356 |
+ t.Logf("Expected 1 got %d", len(bindings)) |
|
357 |
+ t.FailNow() |
|
358 |
+ } |
|
359 |
+ for k := range ports { |
|
360 |
+ if k.Proto() != "tcp" { |
|
361 |
+ t.Logf("Expected tcp got %s", k.Proto()) |
|
362 |
+ t.Fail() |
|
363 |
+ } |
|
364 |
+ if k.Port() != "80" { |
|
365 |
+ t.Logf("Expected 80 got %s", k.Port()) |
|
366 |
+ t.Fail() |
|
367 |
+ } |
|
368 |
+ b, exists := bindings[k] |
|
369 |
+ if !exists { |
|
370 |
+ t.Log("Binding does not exist") |
|
371 |
+ t.FailNow() |
|
372 |
+ } |
|
373 |
+ if len(b) != 1 { |
|
374 |
+ t.Logf("Expected 1 got %d", len(b)) |
|
375 |
+ t.FailNow() |
|
376 |
+ } |
|
377 |
+ s := b[0] |
|
378 |
+ if s.HostPort != "8080" { |
|
379 |
+ t.Logf("Expected 8080 got %s", s.HostPort) |
|
380 |
+ t.Fail() |
|
381 |
+ } |
|
382 |
+ if s.HostIp != "192.168.1.100" { |
|
383 |
+ t.Fail() |
|
384 |
+ } |
|
326 | 385 |
} |
327 | 386 |
} |
328 | 387 |
|
329 |
-func TestParseLxcConfOpt(t *testing.T) { |
|
330 |
- opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} |
|
331 |
- |
|
332 |
- for _, o := range opts { |
|
333 |
- k, v, err := parseLxcOpt(o) |
|
334 |
- if err != nil { |
|
388 |
+func TestParseNetworkOptsUdp(t *testing.T) { |
|
389 |
+ ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"}) |
|
390 |
+ if err != nil { |
|
391 |
+ t.Fatal(err) |
|
392 |
+ } |
|
393 |
+ if len(ports) != 1 { |
|
394 |
+ t.Logf("Expected 1 got %d", len(ports)) |
|
395 |
+ t.FailNow() |
|
396 |
+ } |
|
397 |
+ if len(bindings) != 1 { |
|
398 |
+ t.Logf("Expected 1 got %d", len(bindings)) |
|
399 |
+ t.FailNow() |
|
400 |
+ } |
|
401 |
+ for k := range ports { |
|
402 |
+ if k.Proto() != "udp" { |
|
403 |
+ t.Logf("Expected udp got %s", k.Proto()) |
|
404 |
+ t.Fail() |
|
405 |
+ } |
|
406 |
+ if k.Port() != "6000" { |
|
407 |
+ t.Logf("Expected 6000 got %s", k.Port()) |
|
408 |
+ t.Fail() |
|
409 |
+ } |
|
410 |
+ b, exists := bindings[k] |
|
411 |
+ if !exists { |
|
412 |
+ t.Log("Binding does not exist") |
|
335 | 413 |
t.FailNow() |
336 | 414 |
} |
337 |
- if k != "lxc.utsname" { |
|
415 |
+ if len(b) != 1 { |
|
416 |
+ t.Logf("Expected 1 got %d", len(b)) |
|
417 |
+ t.FailNow() |
|
418 |
+ } |
|
419 |
+ s := b[0] |
|
420 |
+ if s.HostPort != "" { |
|
421 |
+ t.Logf("Expected \"\" got %s", s.HostPort) |
|
338 | 422 |
t.Fail() |
339 | 423 |
} |
340 |
- if v != "docker" { |
|
424 |
+ if s.HostIp != "192.168.1.100" { |
|
341 | 425 |
t.Fail() |
342 | 426 |
} |
343 | 427 |
} |
344 | 428 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,404 @@ |
0 |
+// Copyright 2010 The Go Authors. All rights reserved. |
|
1 |
+// Use of this source code is governed by a BSD-style |
|
2 |
+// license that can be found in the LICENSE file. |
|
3 |
+ |
|
4 |
+// Package sqlite provides access to the SQLite library, version 3. |
|
5 |
+package sqlite |
|
6 |
+ |
|
7 |
+/* |
|
8 |
+#cgo LDFLAGS: -lsqlite3 |
|
9 |
+ |
|
10 |
+#include <sqlite3.h> |
|
11 |
+#include <stdlib.h> |
|
12 |
+ |
|
13 |
+// These wrappers are necessary because SQLITE_TRANSIENT |
|
14 |
+// is a pointer constant, and cgo doesn't translate them correctly. |
|
15 |
+// The definition in sqlite3.h is: |
|
16 |
+// |
|
17 |
+// typedef void (*sqlite3_destructor_type)(void*); |
|
18 |
+// #define SQLITE_STATIC ((sqlite3_destructor_type)0) |
|
19 |
+// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) |
|
20 |
+ |
|
21 |
+static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { |
|
22 |
+ return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); |
|
23 |
+} |
|
24 |
+static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { |
|
25 |
+ return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); |
|
26 |
+} |
|
27 |
+ |
|
28 |
+*/ |
|
29 |
+import "C" |
|
30 |
+ |
|
31 |
+import ( |
|
32 |
+ "errors" |
|
33 |
+ "fmt" |
|
34 |
+ "reflect" |
|
35 |
+ "strconv" |
|
36 |
+ "time" |
|
37 |
+ "unsafe" |
|
38 |
+) |
|
39 |
+ |
|
40 |
+type Errno int |
|
41 |
+ |
|
42 |
+func (e Errno) Error() string { |
|
43 |
+ s := errText[e] |
|
44 |
+ if s == "" { |
|
45 |
+ return fmt.Sprintf("errno %d", int(e)) |
|
46 |
+ } |
|
47 |
+ return s |
|
48 |
+} |
|
49 |
+ |
|
50 |
+var ( |
|
51 |
+ ErrError error = Errno(1) // /* SQL error or missing database */ |
|
52 |
+ ErrInternal error = Errno(2) // /* Internal logic error in SQLite */ |
|
53 |
+ ErrPerm error = Errno(3) // /* Access permission denied */ |
|
54 |
+ ErrAbort error = Errno(4) // /* Callback routine requested an abort */ |
|
55 |
+ ErrBusy error = Errno(5) // /* The database file is locked */ |
|
56 |
+ ErrLocked error = Errno(6) // /* A table in the database is locked */ |
|
57 |
+ ErrNoMem error = Errno(7) // /* A malloc() failed */ |
|
58 |
+ ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */ |
|
59 |
+ ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/ |
|
60 |
+ ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */ |
|
61 |
+ ErrCorrupt error = Errno(11) // /* The database disk image is malformed */ |
|
62 |
+ ErrFull error = Errno(13) // /* Insertion failed because database is full */ |
|
63 |
+ ErrCantOpen error = Errno(14) // /* Unable to open the database file */ |
|
64 |
+ ErrEmpty error = Errno(16) // /* Database is empty */ |
|
65 |
+ ErrSchema error = Errno(17) // /* The database schema changed */ |
|
66 |
+ ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */ |
|
67 |
+ ErrConstraint error = Errno(19) // /* Abort due to constraint violation */ |
|
68 |
+ ErrMismatch error = Errno(20) // /* Data type mismatch */ |
|
69 |
+ ErrMisuse error = Errno(21) // /* Library used incorrectly */ |
|
70 |
+ ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */ |
|
71 |
+ ErrAuth error = Errno(23) // /* Authorization denied */ |
|
72 |
+ ErrFormat error = Errno(24) // /* Auxiliary database format error */ |
|
73 |
+ ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */ |
|
74 |
+ ErrNotDB error = Errno(26) // /* File opened that is not a database file */ |
|
75 |
+ Row = Errno(100) // /* sqlite3_step() has another row ready */ |
|
76 |
+ Done = Errno(101) // /* sqlite3_step() has finished executing */ |
|
77 |
+) |
|
78 |
+ |
|
79 |
+var errText = map[Errno]string{ |
|
80 |
+ 1: "SQL error or missing database", |
|
81 |
+ 2: "Internal logic error in SQLite", |
|
82 |
+ 3: "Access permission denied", |
|
83 |
+ 4: "Callback routine requested an abort", |
|
84 |
+ 5: "The database file is locked", |
|
85 |
+ 6: "A table in the database is locked", |
|
86 |
+ 7: "A malloc() failed", |
|
87 |
+ 8: "Attempt to write a readonly database", |
|
88 |
+ 9: "Operation terminated by sqlite3_interrupt()*/", |
|
89 |
+ 10: "Some kind of disk I/O error occurred", |
|
90 |
+ 11: "The database disk image is malformed", |
|
91 |
+ 12: "NOT USED. Table or record not found", |
|
92 |
+ 13: "Insertion failed because database is full", |
|
93 |
+ 14: "Unable to open the database file", |
|
94 |
+ 15: "NOT USED. Database lock protocol error", |
|
95 |
+ 16: "Database is empty", |
|
96 |
+ 17: "The database schema changed", |
|
97 |
+ 18: "String or BLOB exceeds size limit", |
|
98 |
+ 19: "Abort due to constraint violation", |
|
99 |
+ 20: "Data type mismatch", |
|
100 |
+ 21: "Library used incorrectly", |
|
101 |
+ 22: "Uses OS features not supported on host", |
|
102 |
+ 23: "Authorization denied", |
|
103 |
+ 24: "Auxiliary database format error", |
|
104 |
+ 25: "2nd parameter to sqlite3_bind out of range", |
|
105 |
+ 26: "File opened that is not a database file", |
|
106 |
+ 100: "sqlite3_step() has another row ready", |
|
107 |
+ 101: "sqlite3_step() has finished executing", |
|
108 |
+} |
|
109 |
+ |
|
110 |
+func (c *Conn) error(rv C.int) error { |
|
111 |
+ if c == nil || c.db == nil { |
|
112 |
+ return errors.New("nil sqlite database") |
|
113 |
+ } |
|
114 |
+ if rv == 0 { |
|
115 |
+ return nil |
|
116 |
+ } |
|
117 |
+ if rv == 21 { // misuse |
|
118 |
+ return Errno(rv) |
|
119 |
+ } |
|
120 |
+ return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) |
|
121 |
+} |
|
122 |
+ |
|
123 |
+type Conn struct { |
|
124 |
+ db *C.sqlite3 |
|
125 |
+} |
|
126 |
+ |
|
127 |
+func Version() string { |
|
128 |
+ p := C.sqlite3_libversion() |
|
129 |
+ return C.GoString(p) |
|
130 |
+} |
|
131 |
+ |
|
132 |
+func Open(filename string) (*Conn, error) { |
|
133 |
+ if C.sqlite3_threadsafe() == 0 { |
|
134 |
+ return nil, errors.New("sqlite library was not compiled for thread-safe operation") |
|
135 |
+ } |
|
136 |
+ |
|
137 |
+ var db *C.sqlite3 |
|
138 |
+ name := C.CString(filename) |
|
139 |
+ defer C.free(unsafe.Pointer(name)) |
|
140 |
+ rv := C.sqlite3_open_v2(name, &db, |
|
141 |
+ C.SQLITE_OPEN_FULLMUTEX| |
|
142 |
+ C.SQLITE_OPEN_READWRITE| |
|
143 |
+ C.SQLITE_OPEN_CREATE, |
|
144 |
+ nil) |
|
145 |
+ if rv != 0 { |
|
146 |
+ return nil, Errno(rv) |
|
147 |
+ } |
|
148 |
+ if db == nil { |
|
149 |
+ return nil, errors.New("sqlite succeeded without returning a database") |
|
150 |
+ } |
|
151 |
+ return &Conn{db}, nil |
|
152 |
+} |
|
153 |
+ |
|
154 |
+func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) { |
|
155 |
+ dname := C.CString(dstTable) |
|
156 |
+ sname := C.CString(srcTable) |
|
157 |
+ defer C.free(unsafe.Pointer(dname)) |
|
158 |
+ defer C.free(unsafe.Pointer(sname)) |
|
159 |
+ |
|
160 |
+ sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname) |
|
161 |
+ if sb == nil { |
|
162 |
+ return nil, dst.error(C.sqlite3_errcode(dst.db)) |
|
163 |
+ } |
|
164 |
+ return &Backup{sb, dst, src}, nil |
|
165 |
+} |
|
166 |
+ |
|
167 |
+type Backup struct { |
|
168 |
+ sb *C.sqlite3_backup |
|
169 |
+ dst, src *Conn |
|
170 |
+} |
|
171 |
+ |
|
172 |
+func (b *Backup) Step(npage int) error { |
|
173 |
+ rv := C.sqlite3_backup_step(b.sb, C.int(npage)) |
|
174 |
+ if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked { |
|
175 |
+ return nil |
|
176 |
+ } |
|
177 |
+ return Errno(rv) |
|
178 |
+} |
|
179 |
+ |
|
180 |
+type BackupStatus struct { |
|
181 |
+ Remaining int |
|
182 |
+ PageCount int |
|
183 |
+} |
|
184 |
+ |
|
185 |
+func (b *Backup) Status() BackupStatus { |
|
186 |
+ return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))} |
|
187 |
+} |
|
188 |
+ |
|
189 |
+func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error { |
|
190 |
+ var err error |
|
191 |
+ for { |
|
192 |
+ err = b.Step(npage) |
|
193 |
+ if err != nil { |
|
194 |
+ break |
|
195 |
+ } |
|
196 |
+ if c != nil { |
|
197 |
+ c <- b.Status() |
|
198 |
+ } |
|
199 |
+ time.Sleep(period) |
|
200 |
+ } |
|
201 |
+ return b.dst.error(C.sqlite3_errcode(b.dst.db)) |
|
202 |
+} |
|
203 |
+ |
|
204 |
+func (b *Backup) Close() error { |
|
205 |
+ if b.sb == nil { |
|
206 |
+ return errors.New("backup already closed") |
|
207 |
+ } |
|
208 |
+ C.sqlite3_backup_finish(b.sb) |
|
209 |
+ b.sb = nil |
|
210 |
+ return nil |
|
211 |
+} |
|
212 |
+ |
|
213 |
+func (c *Conn) BusyTimeout(ms int) error { |
|
214 |
+ rv := C.sqlite3_busy_timeout(c.db, C.int(ms)) |
|
215 |
+ if rv == 0 { |
|
216 |
+ return nil |
|
217 |
+ } |
|
218 |
+ return Errno(rv) |
|
219 |
+} |
|
220 |
+ |
|
221 |
+func (c *Conn) Exec(cmd string, args ...interface{}) error { |
|
222 |
+ s, err := c.Prepare(cmd) |
|
223 |
+ if err != nil { |
|
224 |
+ return err |
|
225 |
+ } |
|
226 |
+ defer s.Finalize() |
|
227 |
+ err = s.Exec(args...) |
|
228 |
+ if err != nil { |
|
229 |
+ return err |
|
230 |
+ } |
|
231 |
+ rv := C.sqlite3_step(s.stmt) |
|
232 |
+ if Errno(rv) != Done { |
|
233 |
+ return c.error(rv) |
|
234 |
+ } |
|
235 |
+ return nil |
|
236 |
+} |
|
237 |
+ |
|
238 |
+type Stmt struct { |
|
239 |
+ c *Conn |
|
240 |
+ stmt *C.sqlite3_stmt |
|
241 |
+ err error |
|
242 |
+ t0 time.Time |
|
243 |
+ sql string |
|
244 |
+ args string |
|
245 |
+} |
|
246 |
+ |
|
247 |
+func (c *Conn) Prepare(cmd string) (*Stmt, error) { |
|
248 |
+ if c == nil || c.db == nil { |
|
249 |
+ return nil, errors.New("nil sqlite database") |
|
250 |
+ } |
|
251 |
+ cmdstr := C.CString(cmd) |
|
252 |
+ defer C.free(unsafe.Pointer(cmdstr)) |
|
253 |
+ var stmt *C.sqlite3_stmt |
|
254 |
+ var tail *C.char |
|
255 |
+ rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail) |
|
256 |
+ if rv != 0 { |
|
257 |
+ return nil, c.error(rv) |
|
258 |
+ } |
|
259 |
+ return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil |
|
260 |
+} |
|
261 |
+ |
|
262 |
+func (s *Stmt) Exec(args ...interface{}) error { |
|
263 |
+ s.args = fmt.Sprintf(" %v", []interface{}(args)) |
|
264 |
+ rv := C.sqlite3_reset(s.stmt) |
|
265 |
+ if rv != 0 { |
|
266 |
+ return s.c.error(rv) |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ n := int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
270 |
+ if n != len(args) { |
|
271 |
+ return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n)) |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ for i, v := range args { |
|
275 |
+ var str string |
|
276 |
+ switch v := v.(type) { |
|
277 |
+ case []byte: |
|
278 |
+ var p *byte |
|
279 |
+ if len(v) > 0 { |
|
280 |
+ p = &v[0] |
|
281 |
+ } |
|
282 |
+ if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { |
|
283 |
+ return s.c.error(rv) |
|
284 |
+ } |
|
285 |
+ continue |
|
286 |
+ |
|
287 |
+ case bool: |
|
288 |
+ if v { |
|
289 |
+ str = "1" |
|
290 |
+ } else { |
|
291 |
+ str = "0" |
|
292 |
+ } |
|
293 |
+ |
|
294 |
+ default: |
|
295 |
+ str = fmt.Sprint(v) |
|
296 |
+ } |
|
297 |
+ |
|
298 |
+ cstr := C.CString(str) |
|
299 |
+ rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) |
|
300 |
+ C.free(unsafe.Pointer(cstr)) |
|
301 |
+ if rv != 0 { |
|
302 |
+ return s.c.error(rv) |
|
303 |
+ } |
|
304 |
+ } |
|
305 |
+ return nil |
|
306 |
+} |
|
307 |
+ |
|
308 |
+func (s *Stmt) Error() error { |
|
309 |
+ return s.err |
|
310 |
+} |
|
311 |
+ |
|
312 |
+func (s *Stmt) Next() bool { |
|
313 |
+ rv := C.sqlite3_step(s.stmt) |
|
314 |
+ err := Errno(rv) |
|
315 |
+ if err == Row { |
|
316 |
+ return true |
|
317 |
+ } |
|
318 |
+ if err != Done { |
|
319 |
+ s.err = s.c.error(rv) |
|
320 |
+ } |
|
321 |
+ return false |
|
322 |
+} |
|
323 |
+ |
|
324 |
+func (s *Stmt) Reset() error { |
|
325 |
+ C.sqlite3_reset(s.stmt) |
|
326 |
+ return nil |
|
327 |
+} |
|
328 |
+ |
|
329 |
+func (s *Stmt) Scan(args ...interface{}) error { |
|
330 |
+ n := int(C.sqlite3_column_count(s.stmt)) |
|
331 |
+ if n != len(args) { |
|
332 |
+ return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n)) |
|
333 |
+ } |
|
334 |
+ |
|
335 |
+ for i, v := range args { |
|
336 |
+ n := C.sqlite3_column_bytes(s.stmt, C.int(i)) |
|
337 |
+ p := C.sqlite3_column_blob(s.stmt, C.int(i)) |
|
338 |
+ if p == nil && n > 0 { |
|
339 |
+ return errors.New("got nil blob") |
|
340 |
+ } |
|
341 |
+ var data []byte |
|
342 |
+ if n > 0 { |
|
343 |
+ data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n] |
|
344 |
+ } |
|
345 |
+ switch v := v.(type) { |
|
346 |
+ case *[]byte: |
|
347 |
+ *v = data |
|
348 |
+ case *string: |
|
349 |
+ *v = string(data) |
|
350 |
+ case *bool: |
|
351 |
+ *v = string(data) == "1" |
|
352 |
+ case *int: |
|
353 |
+ x, err := strconv.Atoi(string(data)) |
|
354 |
+ if err != nil { |
|
355 |
+ return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error()) |
|
356 |
+ } |
|
357 |
+ *v = x |
|
358 |
+ case *int64: |
|
359 |
+ x, err := strconv.ParseInt(string(data), 10, 64) |
|
360 |
+ if err != nil { |
|
361 |
+ return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error()) |
|
362 |
+ } |
|
363 |
+ *v = x |
|
364 |
+ case *float64: |
|
365 |
+ x, err := strconv.ParseFloat(string(data), 64) |
|
366 |
+ if err != nil { |
|
367 |
+ return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error()) |
|
368 |
+ } |
|
369 |
+ *v = x |
|
370 |
+ default: |
|
371 |
+ return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String()) |
|
372 |
+ } |
|
373 |
+ } |
|
374 |
+ return nil |
|
375 |
+} |
|
376 |
+ |
|
377 |
+func (s *Stmt) SQL() string { |
|
378 |
+ return s.sql + s.args |
|
379 |
+} |
|
380 |
+ |
|
381 |
+func (s *Stmt) Nanoseconds() int64 { |
|
382 |
+ return time.Now().Sub(s.t0).Nanoseconds() |
|
383 |
+} |
|
384 |
+ |
|
385 |
+func (s *Stmt) Finalize() error { |
|
386 |
+ rv := C.sqlite3_finalize(s.stmt) |
|
387 |
+ if rv != 0 { |
|
388 |
+ return s.c.error(rv) |
|
389 |
+ } |
|
390 |
+ return nil |
|
391 |
+} |
|
392 |
+ |
|
393 |
+func (c *Conn) Close() error { |
|
394 |
+ if c == nil || c.db == nil { |
|
395 |
+ return errors.New("nil sqlite database") |
|
396 |
+ } |
|
397 |
+ rv := C.sqlite3_close(c.db) |
|
398 |
+ if rv != 0 { |
|
399 |
+ return c.error(rv) |
|
400 |
+ } |
|
401 |
+ c.db = nil |
|
402 |
+ return nil |
|
403 |
+} |
0 | 404 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,498 @@ |
0 |
+// Copyright 2010 The Go Authors. All rights reserved. |
|
1 |
+// Use of this source code is governed by a BSD-style |
|
2 |
+// license that can be found in the LICENSE file. |
|
3 |
+ |
|
4 |
+// Package sqlite3 provides access to the SQLite library, version 3. |
|
5 |
+// |
|
6 |
+// The package has no exported API. |
|
7 |
+// It registers a driver for the standard Go database/sql package. |
|
8 |
+// |
|
9 |
+// import _ "code.google.com/p/gosqlite/sqlite3" |
|
10 |
+// |
|
11 |
+// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.) |
|
12 |
+package sqlite |
|
13 |
+ |
|
14 |
+/* |
|
15 |
+#cgo LDFLAGS: -lsqlite3 |
|
16 |
+ |
|
17 |
+#include <sqlite3.h> |
|
18 |
+#include <stdlib.h> |
|
19 |
+ |
|
20 |
+// These wrappers are necessary because SQLITE_TRANSIENT |
|
21 |
+// is a pointer constant, and cgo doesn't translate them correctly. |
|
22 |
+// The definition in sqlite3.h is: |
|
23 |
+// |
|
24 |
+// typedef void (*sqlite3_destructor_type)(void*); |
|
25 |
+// #define SQLITE_STATIC ((sqlite3_destructor_type)0) |
|
26 |
+// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) |
|
27 |
+ |
|
28 |
+static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { |
|
29 |
+ return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); |
|
30 |
+} |
|
31 |
+static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { |
|
32 |
+ return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); |
|
33 |
+} |
|
34 |
+ |
|
35 |
+*/ |
|
36 |
+import "C" |
|
37 |
+ |
|
38 |
+import ( |
|
39 |
+ "database/sql" |
|
40 |
+ "database/sql/driver" |
|
41 |
+ "errors" |
|
42 |
+ "fmt" |
|
43 |
+ "io" |
|
44 |
+ "strings" |
|
45 |
+ "time" |
|
46 |
+ "unsafe" |
|
47 |
+) |
|
48 |
+ |
|
49 |
+func init() { |
|
50 |
+ sql.Register("sqlite3", impl{}) |
|
51 |
+} |
|
52 |
+ |
|
53 |
+type errno int |
|
54 |
+ |
|
55 |
+func (e errno) Error() string { |
|
56 |
+ s := errText[e] |
|
57 |
+ if s == "" { |
|
58 |
+ return fmt.Sprintf("errno %d", int(e)) |
|
59 |
+ } |
|
60 |
+ return s |
|
61 |
+} |
|
62 |
+ |
|
63 |
+var ( |
|
64 |
+ errError error = errno(1) // /* SQL error or missing database */ |
|
65 |
+ errInternal error = errno(2) // /* Internal logic error in SQLite */ |
|
66 |
+ errPerm error = errno(3) // /* Access permission denied */ |
|
67 |
+ errAbort error = errno(4) // /* Callback routine requested an abort */ |
|
68 |
+ errBusy error = errno(5) // /* The database file is locked */ |
|
69 |
+ errLocked error = errno(6) // /* A table in the database is locked */ |
|
70 |
+ errNoMem error = errno(7) // /* A malloc() failed */ |
|
71 |
+ errReadOnly error = errno(8) // /* Attempt to write a readonly database */ |
|
72 |
+ errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/ |
|
73 |
+ errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */ |
|
74 |
+ errCorrupt error = errno(11) // /* The database disk image is malformed */ |
|
75 |
+ errFull error = errno(13) // /* Insertion failed because database is full */ |
|
76 |
+ errCantOpen error = errno(14) // /* Unable to open the database file */ |
|
77 |
+ errEmpty error = errno(16) // /* Database is empty */ |
|
78 |
+ errSchema error = errno(17) // /* The database schema changed */ |
|
79 |
+ errTooBig error = errno(18) // /* String or BLOB exceeds size limit */ |
|
80 |
+ errConstraint error = errno(19) // /* Abort due to constraint violation */ |
|
81 |
+ errMismatch error = errno(20) // /* Data type mismatch */ |
|
82 |
+ errMisuse error = errno(21) // /* Library used incorrectly */ |
|
83 |
+ errNolfs error = errno(22) // /* Uses OS features not supported on host */ |
|
84 |
+ errAuth error = errno(23) // /* Authorization denied */ |
|
85 |
+ errFormat error = errno(24) // /* Auxiliary database format error */ |
|
86 |
+ errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */ |
|
87 |
+ errNotDB error = errno(26) // /* File opened that is not a database file */ |
|
88 |
+ stepRow = errno(100) // /* sqlite3_step() has another row ready */ |
|
89 |
+ stepDone = errno(101) // /* sqlite3_step() has finished executing */ |
|
90 |
+) |
|
91 |
+ |
|
92 |
+var errText = map[errno]string{ |
|
93 |
+ 1: "SQL error or missing database", |
|
94 |
+ 2: "Internal logic error in SQLite", |
|
95 |
+ 3: "Access permission denied", |
|
96 |
+ 4: "Callback routine requested an abort", |
|
97 |
+ 5: "The database file is locked", |
|
98 |
+ 6: "A table in the database is locked", |
|
99 |
+ 7: "A malloc() failed", |
|
100 |
+ 8: "Attempt to write a readonly database", |
|
101 |
+ 9: "Operation terminated by sqlite3_interrupt()*/", |
|
102 |
+ 10: "Some kind of disk I/O error occurred", |
|
103 |
+ 11: "The database disk image is malformed", |
|
104 |
+ 12: "NOT USED. Table or record not found", |
|
105 |
+ 13: "Insertion failed because database is full", |
|
106 |
+ 14: "Unable to open the database file", |
|
107 |
+ 15: "NOT USED. Database lock protocol error", |
|
108 |
+ 16: "Database is empty", |
|
109 |
+ 17: "The database schema changed", |
|
110 |
+ 18: "String or BLOB exceeds size limit", |
|
111 |
+ 19: "Abort due to constraint violation", |
|
112 |
+ 20: "Data type mismatch", |
|
113 |
+ 21: "Library used incorrectly", |
|
114 |
+ 22: "Uses OS features not supported on host", |
|
115 |
+ 23: "Authorization denied", |
|
116 |
+ 24: "Auxiliary database format error", |
|
117 |
+ 25: "2nd parameter to sqlite3_bind out of range", |
|
118 |
+ 26: "File opened that is not a database file", |
|
119 |
+ 100: "sqlite3_step() has another row ready", |
|
120 |
+ 101: "sqlite3_step() has finished executing", |
|
121 |
+} |
|
122 |
+ |
|
123 |
+type impl struct{} |
|
124 |
+ |
|
125 |
+func (impl) Open(name string) (driver.Conn, error) { |
|
126 |
+ if C.sqlite3_threadsafe() == 0 { |
|
127 |
+ return nil, errors.New("sqlite library was not compiled for thread-safe operation") |
|
128 |
+ } |
|
129 |
+ |
|
130 |
+ var db *C.sqlite3 |
|
131 |
+ cname := C.CString(name) |
|
132 |
+ defer C.free(unsafe.Pointer(cname)) |
|
133 |
+ rv := C.sqlite3_open_v2(cname, &db, |
|
134 |
+ C.SQLITE_OPEN_FULLMUTEX| |
|
135 |
+ C.SQLITE_OPEN_READWRITE| |
|
136 |
+ C.SQLITE_OPEN_CREATE, |
|
137 |
+ nil) |
|
138 |
+ if rv != 0 { |
|
139 |
+ return nil, errno(rv) |
|
140 |
+ } |
|
141 |
+ if db == nil { |
|
142 |
+ return nil, errors.New("sqlite succeeded without returning a database") |
|
143 |
+ } |
|
144 |
+ return &conn{db: db}, nil |
|
145 |
+} |
|
146 |
+ |
|
147 |
+type conn struct { |
|
148 |
+ db *C.sqlite3 |
|
149 |
+ closed bool |
|
150 |
+ tx bool |
|
151 |
+} |
|
152 |
+ |
|
153 |
+func (c *conn) error(rv C.int) error { |
|
154 |
+ if rv == 0 { |
|
155 |
+ return nil |
|
156 |
+ } |
|
157 |
+ if rv == 21 || c.closed { |
|
158 |
+ return errno(rv) |
|
159 |
+ } |
|
160 |
+ return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) |
|
161 |
+} |
|
162 |
+ |
|
163 |
+func (c *conn) Prepare(cmd string) (driver.Stmt, error) { |
|
164 |
+ if c.closed { |
|
165 |
+ panic("database/sql/driver: misuse of sqlite driver: Prepare after Close") |
|
166 |
+ } |
|
167 |
+ cmdstr := C.CString(cmd) |
|
168 |
+ defer C.free(unsafe.Pointer(cmdstr)) |
|
169 |
+ var s *C.sqlite3_stmt |
|
170 |
+ var tail *C.char |
|
171 |
+ rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail) |
|
172 |
+ if rv != 0 { |
|
173 |
+ return nil, c.error(rv) |
|
174 |
+ } |
|
175 |
+ return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil |
|
176 |
+} |
|
177 |
+ |
|
178 |
+func (c *conn) Close() error { |
|
179 |
+ if c.closed { |
|
180 |
+ panic("database/sql/driver: misuse of sqlite driver: multiple Close") |
|
181 |
+ } |
|
182 |
+ c.closed = true |
|
183 |
+ rv := C.sqlite3_close(c.db) |
|
184 |
+ c.db = nil |
|
185 |
+ return c.error(rv) |
|
186 |
+} |
|
187 |
+ |
|
188 |
+func (c *conn) exec(cmd string) error { |
|
189 |
+ cstring := C.CString(cmd) |
|
190 |
+ defer C.free(unsafe.Pointer(cstring)) |
|
191 |
+ rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil) |
|
192 |
+ return c.error(rv) |
|
193 |
+} |
|
194 |
+ |
|
195 |
+func (c *conn) Begin() (driver.Tx, error) { |
|
196 |
+ if c.tx { |
|
197 |
+ panic("database/sql/driver: misuse of sqlite driver: multiple Tx") |
|
198 |
+ } |
|
199 |
+ if err := c.exec("BEGIN TRANSACTION"); err != nil { |
|
200 |
+ return nil, err |
|
201 |
+ } |
|
202 |
+ c.tx = true |
|
203 |
+ return &tx{c}, nil |
|
204 |
+} |
|
205 |
+ |
|
206 |
+type tx struct { |
|
207 |
+ c *conn |
|
208 |
+} |
|
209 |
+ |
|
210 |
+func (t *tx) Commit() error { |
|
211 |
+ if t.c == nil || !t.c.tx { |
|
212 |
+ panic("database/sql/driver: misuse of sqlite driver: extra Commit") |
|
213 |
+ } |
|
214 |
+ t.c.tx = false |
|
215 |
+ err := t.c.exec("COMMIT TRANSACTION") |
|
216 |
+ t.c = nil |
|
217 |
+ return err |
|
218 |
+} |
|
219 |
+ |
|
220 |
+func (t *tx) Rollback() error { |
|
221 |
+ if t.c == nil || !t.c.tx { |
|
222 |
+ panic("database/sql/driver: misuse of sqlite driver: extra Rollback") |
|
223 |
+ } |
|
224 |
+ t.c.tx = false |
|
225 |
+ err := t.c.exec("ROLLBACK") |
|
226 |
+ t.c = nil |
|
227 |
+ return err |
|
228 |
+} |
|
229 |
+ |
|
230 |
+type stmt struct { |
|
231 |
+ c *conn |
|
232 |
+ stmt *C.sqlite3_stmt |
|
233 |
+ err error |
|
234 |
+ t0 time.Time |
|
235 |
+ sql string |
|
236 |
+ args string |
|
237 |
+ closed bool |
|
238 |
+ rows bool |
|
239 |
+ colnames []string |
|
240 |
+ coltypes []string |
|
241 |
+} |
|
242 |
+ |
|
243 |
+func (s *stmt) Close() error { |
|
244 |
+ if s.rows { |
|
245 |
+ panic("database/sql/driver: misuse of sqlite driver: Close with active Rows") |
|
246 |
+ } |
|
247 |
+ if s.closed { |
|
248 |
+ panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt") |
|
249 |
+ } |
|
250 |
+ s.closed = true |
|
251 |
+ rv := C.sqlite3_finalize(s.stmt) |
|
252 |
+ if rv != 0 { |
|
253 |
+ return s.c.error(rv) |
|
254 |
+ } |
|
255 |
+ return nil |
|
256 |
+} |
|
257 |
+ |
|
258 |
+func (s *stmt) NumInput() int { |
|
259 |
+ if s.closed { |
|
260 |
+ panic("database/sql/driver: misuse of sqlite driver: NumInput after Close") |
|
261 |
+ } |
|
262 |
+ return int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
263 |
+} |
|
264 |
+ |
|
265 |
+func (s *stmt) reset() error { |
|
266 |
+ return s.c.error(C.sqlite3_reset(s.stmt)) |
|
267 |
+} |
|
268 |
+ |
|
269 |
+func (s *stmt) start(args []driver.Value) error { |
|
270 |
+ if err := s.reset(); err != nil { |
|
271 |
+ return err |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ n := int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
275 |
+ if n != len(args) { |
|
276 |
+ return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n) |
|
277 |
+ } |
|
278 |
+ |
|
279 |
+ for i, v := range args { |
|
280 |
+ var str string |
|
281 |
+ switch v := v.(type) { |
|
282 |
+ case nil: |
|
283 |
+ if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 { |
|
284 |
+ return s.c.error(rv) |
|
285 |
+ } |
|
286 |
+ continue |
|
287 |
+ |
|
288 |
+ case float64: |
|
289 |
+ if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 { |
|
290 |
+ return s.c.error(rv) |
|
291 |
+ } |
|
292 |
+ continue |
|
293 |
+ |
|
294 |
+ case int64: |
|
295 |
+ if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 { |
|
296 |
+ return s.c.error(rv) |
|
297 |
+ } |
|
298 |
+ continue |
|
299 |
+ |
|
300 |
+ case []byte: |
|
301 |
+ var p *byte |
|
302 |
+ if len(v) > 0 { |
|
303 |
+ p = &v[0] |
|
304 |
+ } |
|
305 |
+ if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { |
|
306 |
+ return s.c.error(rv) |
|
307 |
+ } |
|
308 |
+ continue |
|
309 |
+ |
|
310 |
+ case bool: |
|
311 |
+ var vi int64 |
|
312 |
+ if v { |
|
313 |
+ vi = 1 |
|
314 |
+ } |
|
315 |
+ if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 { |
|
316 |
+ return s.c.error(rv) |
|
317 |
+ } |
|
318 |
+ continue |
|
319 |
+ |
|
320 |
+ case time.Time: |
|
321 |
+ str = v.UTC().Format(timefmt[0]) |
|
322 |
+ |
|
323 |
+ case string: |
|
324 |
+ str = v |
|
325 |
+ |
|
326 |
+ default: |
|
327 |
+ str = fmt.Sprint(v) |
|
328 |
+ } |
|
329 |
+ |
|
330 |
+ cstr := C.CString(str) |
|
331 |
+ rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) |
|
332 |
+ C.free(unsafe.Pointer(cstr)) |
|
333 |
+ if rv != 0 { |
|
334 |
+ return s.c.error(rv) |
|
335 |
+ } |
|
336 |
+ } |
|
337 |
+ |
|
338 |
+ return nil |
|
339 |
+} |
|
340 |
+ |
|
341 |
+func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { |
|
342 |
+ if s.closed { |
|
343 |
+ panic("database/sql/driver: misuse of sqlite driver: Exec after Close") |
|
344 |
+ } |
|
345 |
+ if s.rows { |
|
346 |
+ panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows") |
|
347 |
+ } |
|
348 |
+ |
|
349 |
+ err := s.start(args) |
|
350 |
+ if err != nil { |
|
351 |
+ return nil, err |
|
352 |
+ } |
|
353 |
+ |
|
354 |
+ rv := C.sqlite3_step(s.stmt) |
|
355 |
+ if errno(rv) != stepDone { |
|
356 |
+ if rv == 0 { |
|
357 |
+ rv = 21 // errMisuse |
|
358 |
+ } |
|
359 |
+ return nil, s.c.error(rv) |
|
360 |
+ } |
|
361 |
+ |
|
362 |
+ id := int64(C.sqlite3_last_insert_rowid(s.c.db)) |
|
363 |
+ rows := int64(C.sqlite3_changes(s.c.db)) |
|
364 |
+ return &result{id, rows}, nil |
|
365 |
+} |
|
366 |
+ |
|
367 |
+func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { |
|
368 |
+ if s.closed { |
|
369 |
+ panic("database/sql/driver: misuse of sqlite driver: Query after Close") |
|
370 |
+ } |
|
371 |
+ if s.rows { |
|
372 |
+ panic("database/sql/driver: misuse of sqlite driver: Query with active Rows") |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ err := s.start(args) |
|
376 |
+ if err != nil { |
|
377 |
+ return nil, err |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ s.rows = true |
|
381 |
+ if s.colnames == nil { |
|
382 |
+ n := int64(C.sqlite3_column_count(s.stmt)) |
|
383 |
+ s.colnames = make([]string, n) |
|
384 |
+ s.coltypes = make([]string, n) |
|
385 |
+ for i := range s.colnames { |
|
386 |
+ s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i))) |
|
387 |
+ s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i)))) |
|
388 |
+ } |
|
389 |
+ } |
|
390 |
+ return &rows{s}, nil |
|
391 |
+} |
|
392 |
+ |
|
393 |
+type rows struct { |
|
394 |
+ s *stmt |
|
395 |
+} |
|
396 |
+ |
|
397 |
+func (r *rows) Columns() []string { |
|
398 |
+ if r.s == nil { |
|
399 |
+ panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows") |
|
400 |
+ } |
|
401 |
+ return r.s.colnames |
|
402 |
+} |
|
403 |
+ |
|
404 |
+const maxslice = 1<<31 - 1 |
|
405 |
+ |
|
406 |
+var timefmt = []string{ |
|
407 |
+ "2006-01-02 15:04:05.999999999", |
|
408 |
+ "2006-01-02T15:04:05.999999999", |
|
409 |
+ "2006-01-02 15:04:05", |
|
410 |
+ "2006-01-02T15:04:05", |
|
411 |
+ "2006-01-02 15:04", |
|
412 |
+ "2006-01-02T15:04", |
|
413 |
+ "2006-01-02", |
|
414 |
+} |
|
415 |
+ |
|
416 |
+func (r *rows) Next(dst []driver.Value) error { |
|
417 |
+ if r.s == nil { |
|
418 |
+ panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows") |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ rv := C.sqlite3_step(r.s.stmt) |
|
422 |
+ if errno(rv) != stepRow { |
|
423 |
+ if errno(rv) == stepDone { |
|
424 |
+ return io.EOF |
|
425 |
+ } |
|
426 |
+ if rv == 0 { |
|
427 |
+ rv = 21 |
|
428 |
+ } |
|
429 |
+ return r.s.c.error(rv) |
|
430 |
+ } |
|
431 |
+ |
|
432 |
+ for i := range dst { |
|
433 |
+ switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ { |
|
434 |
+ default: |
|
435 |
+ return fmt.Errorf("unexpected sqlite3 column type %d", typ) |
|
436 |
+ case C.SQLITE_INTEGER: |
|
437 |
+ val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i))) |
|
438 |
+ switch r.s.coltypes[i] { |
|
439 |
+ case "timestamp", "datetime": |
|
440 |
+ dst[i] = time.Unix(val, 0).UTC() |
|
441 |
+ case "boolean": |
|
442 |
+ dst[i] = val > 0 |
|
443 |
+ default: |
|
444 |
+ dst[i] = val |
|
445 |
+ } |
|
446 |
+ |
|
447 |
+ case C.SQLITE_FLOAT: |
|
448 |
+ dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i))) |
|
449 |
+ |
|
450 |
+ case C.SQLITE_BLOB, C.SQLITE_TEXT: |
|
451 |
+ n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i))) |
|
452 |
+ var b []byte |
|
453 |
+ if n > 0 { |
|
454 |
+ p := C.sqlite3_column_blob(r.s.stmt, C.int(i)) |
|
455 |
+ b = (*[maxslice]byte)(unsafe.Pointer(p))[:n] |
|
456 |
+ } |
|
457 |
+ dst[i] = b |
|
458 |
+ switch r.s.coltypes[i] { |
|
459 |
+ case "timestamp", "datetime": |
|
460 |
+ dst[i] = time.Time{} |
|
461 |
+ s := string(b) |
|
462 |
+ for _, f := range timefmt { |
|
463 |
+ if t, err := time.Parse(f, s); err == nil { |
|
464 |
+ dst[i] = t |
|
465 |
+ break |
|
466 |
+ } |
|
467 |
+ } |
|
468 |
+ } |
|
469 |
+ |
|
470 |
+ case C.SQLITE_NULL: |
|
471 |
+ dst[i] = nil |
|
472 |
+ } |
|
473 |
+ } |
|
474 |
+ return nil |
|
475 |
+} |
|
476 |
+ |
|
477 |
+func (r *rows) Close() error { |
|
478 |
+ if r.s == nil { |
|
479 |
+ panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows") |
|
480 |
+ } |
|
481 |
+ r.s.rows = false |
|
482 |
+ r.s = nil |
|
483 |
+ return nil |
|
484 |
+} |
|
485 |
+ |
|
486 |
+type result struct { |
|
487 |
+ id int64 |
|
488 |
+ rows int64 |
|
489 |
+} |
|
490 |
+ |
|
491 |
+func (r *result) LastInsertId() (int64, error) { |
|
492 |
+ return r.id, nil |
|
493 |
+} |
|
494 |
+ |
|
495 |
+func (r *result) RowsAffected() (int64, error) { |
|
496 |
+ return r.rows, nil |
|
497 |
+} |