... | ... |
@@ -218,24 +218,55 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques |
218 | 218 |
} |
219 | 219 |
|
220 | 220 |
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
221 |
- events := make(chan utils.JSONMessage) |
|
222 |
- srv.Lock() |
|
223 |
- srv.events[r.RemoteAddr] = events |
|
224 |
- srv.Unlock() |
|
225 |
- w.Header().Set("Content-Type", "application/json") |
|
226 |
- wf := utils.NewWriteFlusher(w) |
|
227 |
- for { |
|
228 |
- event := <-events |
|
221 |
+ sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (bool, error) { |
|
229 | 222 |
b, err := json.Marshal(event) |
230 | 223 |
if err != nil { |
231 |
- continue |
|
224 |
+ return true, nil |
|
232 | 225 |
} |
233 | 226 |
_, err = wf.Write(b) |
234 | 227 |
if err != nil { |
235 | 228 |
utils.Debugf("%s", err) |
236 | 229 |
srv.Lock() |
237 |
- delete(srv.events, r.RemoteAddr) |
|
230 |
+ delete(srv.listeners, r.RemoteAddr) |
|
238 | 231 |
srv.Unlock() |
232 |
+ return false, err |
|
233 |
+ } |
|
234 |
+ return false, nil |
|
235 |
+ } |
|
236 |
+ |
|
237 |
+ if err := parseForm(r); err != nil { |
|
238 |
+ return err |
|
239 |
+ } |
|
240 |
+ listener := make(chan utils.JSONMessage) |
|
241 |
+ srv.Lock() |
|
242 |
+ srv.listeners[r.RemoteAddr] = listener |
|
243 |
+ srv.Unlock() |
|
244 |
+ since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0) |
|
245 |
+ if err != nil { |
|
246 |
+ since = 0 |
|
247 |
+ } |
|
248 |
+ w.Header().Set("Content-Type", "application/json") |
|
249 |
+ wf := utils.NewWriteFlusher(w) |
|
250 |
+ if since != 0 { |
|
251 |
+ for _, event := range srv.events { |
|
252 |
+ if event.Time >= since { |
|
253 |
+ skip, err := sendEvent(wf, &event) |
|
254 |
+ if skip { |
|
255 |
+ continue |
|
256 |
+ } |
|
257 |
+ if err != nil { |
|
258 |
+ return err |
|
259 |
+ } |
|
260 |
+ } |
|
261 |
+ } |
|
262 |
+ } |
|
263 |
+ for { |
|
264 |
+ event := <-listener |
|
265 |
+ skip, err := sendEvent(wf, &event) |
|
266 |
+ if skip { |
|
267 |
+ continue |
|
268 |
+ } |
|
269 |
+ if err != nil { |
|
239 | 270 |
return err |
240 | 271 |
} |
241 | 272 |
} |
... | ... |
@@ -1058,7 +1058,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error { |
1058 | 1058 |
} |
1059 | 1059 |
|
1060 | 1060 |
func (cli *DockerCli) CmdEvents(args ...string) error { |
1061 |
- cmd := Subcmd("events", "", "Get real time events from the server") |
|
1061 |
+ cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server") |
|
1062 |
+ since := cmd.String("since", "", "Show events previously created (used for polling).") |
|
1062 | 1063 |
if err := cmd.Parse(args); err != nil { |
1063 | 1064 |
return nil |
1064 | 1065 |
} |
... | ... |
@@ -1068,7 +1069,12 @@ func (cli *DockerCli) CmdEvents(args ...string) error { |
1068 | 1068 |
return nil |
1069 | 1069 |
} |
1070 | 1070 |
|
1071 |
- if err := cli.stream("GET", "/events", nil, cli.out); err != nil { |
|
1071 |
+ v := url.Values{} |
|
1072 |
+ if *since != "" { |
|
1073 |
+ v.Set("since", *since) |
|
1074 |
+ } |
|
1075 |
+ |
|
1076 |
+ if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil { |
|
1072 | 1077 |
return err |
1073 | 1078 |
} |
1074 | 1079 |
return nil |
... | ... |
@@ -35,7 +35,7 @@ func (srv *Server) ContainerKill(name string) error { |
35 | 35 |
if err := container.Kill(); err != nil { |
36 | 36 |
return fmt.Errorf("Error killing container %s: %s", name, err) |
37 | 37 |
} |
38 |
- srv.SendEvent("kill", name) |
|
38 |
+ srv.LogEvent("kill", name) |
|
39 | 39 |
} else { |
40 | 40 |
return fmt.Errorf("No such container: %s", name) |
41 | 41 |
} |
... | ... |
@@ -54,7 +54,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { |
54 | 54 |
if _, err := io.Copy(out, data); err != nil { |
55 | 55 |
return err |
56 | 56 |
} |
57 |
- srv.SendEvent("export", name) |
|
57 |
+ srv.LogEvent("export", name) |
|
58 | 58 |
return nil |
59 | 59 |
} |
60 | 60 |
return fmt.Errorf("No such container: %s", name) |
... | ... |
@@ -814,7 +814,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { |
814 | 814 |
} |
815 | 815 |
return "", err |
816 | 816 |
} |
817 |
- srv.SendEvent("create", container.ShortID()) |
|
817 |
+ srv.LogEvent("create", container.ShortID()) |
|
818 | 818 |
return container.ShortID(), nil |
819 | 819 |
} |
820 | 820 |
|
... | ... |
@@ -823,7 +823,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { |
823 | 823 |
if err := container.Restart(t); err != nil { |
824 | 824 |
return fmt.Errorf("Error restarting container %s: %s", name, err) |
825 | 825 |
} |
826 |
- srv.SendEvent("restart", name) |
|
826 |
+ srv.LogEvent("restart", name) |
|
827 | 827 |
} else { |
828 | 828 |
return fmt.Errorf("No such container: %s", name) |
829 | 829 |
} |
... | ... |
@@ -843,7 +843,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { |
843 | 843 |
if err := srv.runtime.Destroy(container); err != nil { |
844 | 844 |
return fmt.Errorf("Error destroying container %s: %s", name, err) |
845 | 845 |
} |
846 |
- srv.SendEvent("destroy", name) |
|
846 |
+ srv.LogEvent("destroy", name) |
|
847 | 847 |
|
848 | 848 |
if removeVolume { |
849 | 849 |
// Retrieve all volumes from all remaining containers |
... | ... |
@@ -910,7 +910,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { |
910 | 910 |
return err |
911 | 911 |
} |
912 | 912 |
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) |
913 |
- srv.SendEvent("delete", utils.TruncateID(id)) |
|
913 |
+ srv.LogEvent("delete", utils.TruncateID(id)) |
|
914 | 914 |
return nil |
915 | 915 |
} |
916 | 916 |
return nil |
... | ... |
@@ -954,7 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro |
954 | 954 |
} |
955 | 955 |
if tagDeleted { |
956 | 956 |
imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) |
957 |
- srv.SendEvent("untag", img.ShortID()) |
|
957 |
+ srv.LogEvent("untag", img.ShortID()) |
|
958 | 958 |
} |
959 | 959 |
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { |
960 | 960 |
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { |
... | ... |
@@ -1027,7 +1027,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { |
1027 | 1027 |
if err := container.Start(hostConfig); err != nil { |
1028 | 1028 |
return fmt.Errorf("Error starting container %s: %s", name, err) |
1029 | 1029 |
} |
1030 |
- srv.SendEvent("start", name) |
|
1030 |
+ srv.LogEvent("start", name) |
|
1031 | 1031 |
} else { |
1032 | 1032 |
return fmt.Errorf("No such container: %s", name) |
1033 | 1033 |
} |
... | ... |
@@ -1039,7 +1039,7 @@ func (srv *Server) ContainerStop(name string, t int) error { |
1039 | 1039 |
if err := container.Stop(t); err != nil { |
1040 | 1040 |
return fmt.Errorf("Error stopping container %s: %s", name, err) |
1041 | 1041 |
} |
1042 |
- srv.SendEvent("stop", name) |
|
1042 |
+ srv.LogEvent("stop", name) |
|
1043 | 1043 |
} else { |
1044 | 1044 |
return fmt.Errorf("No such container: %s", name) |
1045 | 1045 |
} |
... | ... |
@@ -1173,15 +1173,19 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( |
1173 | 1173 |
enableCors: enableCors, |
1174 | 1174 |
pullingPool: make(map[string]struct{}), |
1175 | 1175 |
pushingPool: make(map[string]struct{}), |
1176 |
- events: make(map[string]chan utils.JSONMessage), |
|
1176 |
+ events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events |
|
1177 |
+ listeners: make(map[string]chan utils.JSONMessage), |
|
1177 | 1178 |
} |
1178 | 1179 |
runtime.srv = srv |
1179 | 1180 |
return srv, nil |
1180 | 1181 |
} |
1181 | 1182 |
|
1182 |
-func (srv *Server) SendEvent(action, id string) { |
|
1183 |
- for _, c := range srv.events { |
|
1184 |
- c <- utils.JSONMessage{Status: action, ID: id, Time: time.Now().Unix()} |
|
1183 |
+func (srv *Server) LogEvent(action, id string) { |
|
1184 |
+ now := time.Now().Unix() |
|
1185 |
+ jm := utils.JSONMessage{Status: action, ID: id, Time: now} |
|
1186 |
+ srv.events = append(srv.events, jm) |
|
1187 |
+ for _, c := range srv.listeners { |
|
1188 |
+ c <- jm |
|
1185 | 1189 |
} |
1186 | 1190 |
} |
1187 | 1191 |
|
... | ... |
@@ -1191,5 +1195,6 @@ type Server struct { |
1191 | 1191 |
enableCors bool |
1192 | 1192 |
pullingPool map[string]struct{} |
1193 | 1193 |
pushingPool map[string]struct{} |
1194 |
- events map[string]chan utils.JSONMessage |
|
1194 |
+ events []utils.JSONMessage |
|
1195 |
+ listeners map[string]chan utils.JSONMessage |
|
1195 | 1196 |
} |