... | ... |
@@ -226,7 +226,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string |
226 | 226 |
if err := container.Stop(); err != nil { |
227 | 227 |
return err |
228 | 228 |
} |
229 |
- fmt.Fprintln(stdout, container.Id) |
|
229 |
+ fmt.Fprintln(stdout, container.ShortId()) |
|
230 | 230 |
} else { |
231 | 231 |
return fmt.Errorf("No such container: %s", name) |
232 | 232 |
} |
... | ... |
@@ -248,7 +248,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str |
248 | 248 |
if err := container.Restart(); err != nil { |
249 | 249 |
return err |
250 | 250 |
} |
251 |
- fmt.Fprintln(stdout, container.Id) |
|
251 |
+ fmt.Fprintln(stdout, container.ShortId()) |
|
252 | 252 |
} else { |
253 | 253 |
return fmt.Errorf("No such container: %s", name) |
254 | 254 |
} |
... | ... |
@@ -270,7 +270,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin |
270 | 270 |
if err := container.Start(); err != nil { |
271 | 271 |
return err |
272 | 272 |
} |
273 |
- fmt.Fprintln(stdout, container.Id) |
|
273 |
+ fmt.Fprintln(stdout, container.ShortId()) |
|
274 | 274 |
} else { |
275 | 275 |
return fmt.Errorf("No such container: %s", name) |
276 | 276 |
} |
... | ... |
@@ -659,7 +659,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) |
659 | 659 |
command = Trunc(command, 20) |
660 | 660 |
} |
661 | 661 |
for idx, field := range []string{ |
662 |
- /* ID */ container.Id, |
|
662 |
+ /* ID */ container.ShortId(), |
|
663 | 663 |
/* IMAGE */ srv.runtime.repositories.ImageName(container.Image), |
664 | 664 |
/* COMMAND */ command, |
665 | 665 |
/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", |
... | ... |
@@ -674,7 +674,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) |
674 | 674 |
} |
675 | 675 |
w.Write([]byte{'\n'}) |
676 | 676 |
} else { |
677 |
- stdout.Write([]byte(container.Id + "\n")) |
|
677 |
+ stdout.Write([]byte(container.ShortId() + "\n")) |
|
678 | 678 |
} |
679 | 679 |
} |
680 | 680 |
if !*quiet { |
... | ... |
@@ -965,7 +965,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) |
965 | 965 |
if err := container.Start(); err != nil { |
966 | 966 |
return err |
967 | 967 |
} |
968 |
- fmt.Fprintln(stdout, container.Id) |
|
968 |
+ fmt.Fprintln(stdout, container.ShortId()) |
|
969 | 969 |
} |
970 | 970 |
return nil |
971 | 971 |
} |
... | ... |
@@ -555,6 +555,18 @@ func (container *Container) Unmount() error { |
555 | 555 |
return Unmount(container.RootfsPath()) |
556 | 556 |
} |
557 | 557 |
|
558 |
+// ShortId returns a shorthand version of the container's id for convenience. |
|
559 |
+// A collision with other container shorthands is very unlikely, but possible. |
|
560 |
+// In case of a collision a lookup with Runtime.Get() will fail, and the caller |
|
561 |
+// will need to use a langer prefix, or the full-length container Id. |
|
562 |
+func (container *Container) ShortId() string { |
|
563 |
+ shortLen := 12 |
|
564 |
+ if len(container.Id) < shortLen { |
|
565 |
+ shortLen = len(container.Id) |
|
566 |
+ } |
|
567 |
+ return container.Id[:shortLen] |
|
568 |
+} |
|
569 |
+ |
|
558 | 570 |
func (container *Container) logPath(name string) string { |
559 | 571 |
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name)) |
560 | 572 |
} |
... | ... |
@@ -21,6 +21,7 @@ type Runtime struct { |
21 | 21 |
graph *Graph |
22 | 22 |
repositories *TagStore |
23 | 23 |
authConfig *auth.AuthConfig |
24 |
+ idIndex *TruncIndex |
|
24 | 25 |
} |
25 | 26 |
|
26 | 27 |
var sysInitPath string |
... | ... |
@@ -47,7 +48,11 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { |
47 | 47 |
return nil |
48 | 48 |
} |
49 | 49 |
|
50 |
-func (runtime *Runtime) Get(id string) *Container { |
|
50 |
+func (runtime *Runtime) Get(name string) *Container { |
|
51 |
+ id, err := runtime.idIndex.Get(name) |
|
52 |
+ if err != nil { |
|
53 |
+ return nil |
|
54 |
+ } |
|
51 | 55 |
e := runtime.getContainerElement(id) |
52 | 56 |
if e == nil { |
53 | 57 |
return nil |
... | ... |
@@ -72,6 +77,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { |
72 | 72 |
// Generate id |
73 | 73 |
id := GenerateId() |
74 | 74 |
// Generate default hostname |
75 |
+ // FIXME: the lxc template no longer needs to set a default hostname |
|
75 | 76 |
if config.Hostname == "" { |
76 | 77 |
config.Hostname = id[:12] |
77 | 78 |
} |
... | ... |
@@ -142,6 +148,7 @@ func (runtime *Runtime) Register(container *Container) error { |
142 | 142 |
} |
143 | 143 |
// done |
144 | 144 |
runtime.containers.PushBack(container) |
145 |
+ runtime.idIndex.Add(container.Id) |
|
145 | 146 |
return nil |
146 | 147 |
} |
147 | 148 |
|
... | ... |
@@ -171,6 +178,7 @@ func (runtime *Runtime) Destroy(container *Container) error { |
171 | 171 |
} |
172 | 172 |
} |
173 | 173 |
// Deregister the container before removing its directory, to avoid race conditions |
174 |
+ runtime.idIndex.Delete(container.Id) |
|
174 | 175 |
runtime.containers.Remove(element) |
175 | 176 |
if err := os.RemoveAll(container.root); err != nil { |
176 | 177 |
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err) |
... | ... |
@@ -222,6 +230,7 @@ func (runtime *Runtime) restore() error { |
222 | 222 |
return nil |
223 | 223 |
} |
224 | 224 |
|
225 |
+// FIXME: harmonize with NewGraph() |
|
225 | 226 |
func NewRuntime() (*Runtime, error) { |
226 | 227 |
return NewRuntimeFromDirectory("/var/lib/docker") |
227 | 228 |
} |
... | ... |
@@ -259,6 +268,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { |
259 | 259 |
graph: g, |
260 | 260 |
repositories: repositories, |
261 | 261 |
authConfig: authConfig, |
262 |
+ idIndex: NewTruncIndex(), |
|
262 | 263 |
} |
263 | 264 |
|
264 | 265 |
if err := runtime.restore(); err != nil { |
... | ... |
@@ -6,6 +6,7 @@ import ( |
6 | 6 |
"errors" |
7 | 7 |
"fmt" |
8 | 8 |
"github.com/dotcloud/docker/rcli" |
9 |
+ "index/suffixarray" |
|
9 | 10 |
"io" |
10 | 11 |
"io/ioutil" |
11 | 12 |
"net/http" |
... | ... |
@@ -270,3 +271,66 @@ func getTotalUsedFds() int { |
270 | 270 |
} |
271 | 271 |
return -1 |
272 | 272 |
} |
273 |
+ |
|
274 |
+// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. |
|
275 |
+// This is used to retrieve image and container IDs by more convenient shorthand prefixes. |
|
276 |
+type TruncIndex struct { |
|
277 |
+ index *suffixarray.Index |
|
278 |
+ ids map[string]bool |
|
279 |
+ bytes []byte |
|
280 |
+} |
|
281 |
+ |
|
282 |
+func NewTruncIndex() *TruncIndex { |
|
283 |
+ return &TruncIndex{ |
|
284 |
+ index: suffixarray.New([]byte{' '}), |
|
285 |
+ ids: make(map[string]bool), |
|
286 |
+ bytes: []byte{' '}, |
|
287 |
+ } |
|
288 |
+} |
|
289 |
+ |
|
290 |
+func (idx *TruncIndex) Add(id string) error { |
|
291 |
+ if strings.Contains(id, " ") { |
|
292 |
+ return fmt.Errorf("Illegal character: ' '") |
|
293 |
+ } |
|
294 |
+ if _, exists := idx.ids[id]; exists { |
|
295 |
+ return fmt.Errorf("Id already exists: %s", id) |
|
296 |
+ } |
|
297 |
+ idx.ids[id] = true |
|
298 |
+ idx.bytes = append(idx.bytes, []byte(id+" ")...) |
|
299 |
+ idx.index = suffixarray.New(idx.bytes) |
|
300 |
+ return nil |
|
301 |
+} |
|
302 |
+ |
|
303 |
+func (idx *TruncIndex) Delete(id string) error { |
|
304 |
+ if _, exists := idx.ids[id]; !exists { |
|
305 |
+ return fmt.Errorf("No such id: %s", id) |
|
306 |
+ } |
|
307 |
+ before, after, err := idx.lookup(id) |
|
308 |
+ if err != nil { |
|
309 |
+ return err |
|
310 |
+ } |
|
311 |
+ delete(idx.ids, id) |
|
312 |
+ idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...) |
|
313 |
+ idx.index = suffixarray.New(idx.bytes) |
|
314 |
+ return nil |
|
315 |
+} |
|
316 |
+ |
|
317 |
+func (idx *TruncIndex) lookup(s string) (int, int, error) { |
|
318 |
+ offsets := idx.index.Lookup([]byte(" "+s), -1) |
|
319 |
+ //log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes()) |
|
320 |
+ if offsets == nil || len(offsets) == 0 || len(offsets) > 1 { |
|
321 |
+ return -1, -1, fmt.Errorf("No such id: %s", s) |
|
322 |
+ } |
|
323 |
+ offsetBefore := offsets[0] + 1 |
|
324 |
+ offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ") |
|
325 |
+ return offsetBefore, offsetAfter, nil |
|
326 |
+} |
|
327 |
+ |
|
328 |
+func (idx *TruncIndex) Get(s string) (string, error) { |
|
329 |
+ before, after, err := idx.lookup(s) |
|
330 |
+ //log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after) |
|
331 |
+ if err != nil { |
|
332 |
+ return "", err |
|
333 |
+ } |
|
334 |
+ return string(idx.bytes[before:after]), err |
|
335 |
+} |
... | ... |
@@ -124,3 +124,85 @@ func TestWriteBroadcaster(t *testing.T) { |
124 | 124 |
|
125 | 125 |
writer.Close() |
126 | 126 |
} |
127 |
+ |
|
128 |
+// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix. |
|
129 |
+func TestTruncIndex(t *testing.T) { |
|
130 |
+ index := NewTruncIndex() |
|
131 |
+ // Get on an empty index |
|
132 |
+ if _, err := index.Get("foobar"); err == nil { |
|
133 |
+ t.Fatal("Get on an empty index should return an error") |
|
134 |
+ } |
|
135 |
+ |
|
136 |
+ // Spaces should be illegal in an id |
|
137 |
+ if err := index.Add("I have a space"); err == nil { |
|
138 |
+ t.Fatalf("Adding an id with ' ' should return an error") |
|
139 |
+ } |
|
140 |
+ |
|
141 |
+ id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96" |
|
142 |
+ // Add an id |
|
143 |
+ if err := index.Add(id); err != nil { |
|
144 |
+ t.Fatal(err) |
|
145 |
+ } |
|
146 |
+ // Get a non-existing id |
|
147 |
+ assertIndexGet(t, index, "abracadabra", "", true) |
|
148 |
+ // Get the exact id |
|
149 |
+ assertIndexGet(t, index, id, id, false) |
|
150 |
+ // The first letter should match |
|
151 |
+ assertIndexGet(t, index, id[:1], id, false) |
|
152 |
+ // The first half should match |
|
153 |
+ assertIndexGet(t, index, id[:len(id)/2], id, false) |
|
154 |
+ // The second half should NOT match |
|
155 |
+ assertIndexGet(t, index, id[len(id)/2:], "", true) |
|
156 |
+ |
|
157 |
+ id2 := id[:6] + "blabla" |
|
158 |
+ // Add an id |
|
159 |
+ if err := index.Add(id2); err != nil { |
|
160 |
+ t.Fatal(err) |
|
161 |
+ } |
|
162 |
+ // Both exact IDs should work |
|
163 |
+ assertIndexGet(t, index, id, id, false) |
|
164 |
+ assertIndexGet(t, index, id2, id2, false) |
|
165 |
+ |
|
166 |
+ // 6 characters or less should conflict |
|
167 |
+ assertIndexGet(t, index, id[:6], "", true) |
|
168 |
+ assertIndexGet(t, index, id[:4], "", true) |
|
169 |
+ assertIndexGet(t, index, id[:1], "", true) |
|
170 |
+ |
|
171 |
+ // 7 characters should NOT conflict |
|
172 |
+ assertIndexGet(t, index, id[:7], id, false) |
|
173 |
+ assertIndexGet(t, index, id2[:7], id2, false) |
|
174 |
+ |
|
175 |
+ // Deleting a non-existing id should return an error |
|
176 |
+ if err := index.Delete("non-existing"); err == nil { |
|
177 |
+ t.Fatalf("Deleting a non-existing id should return an error") |
|
178 |
+ } |
|
179 |
+ |
|
180 |
+ // Deleting id2 should remove conflicts |
|
181 |
+ if err := index.Delete(id2); err != nil { |
|
182 |
+ t.Fatal(err) |
|
183 |
+ } |
|
184 |
+ // id2 should no longer work |
|
185 |
+ assertIndexGet(t, index, id2, "", true) |
|
186 |
+ assertIndexGet(t, index, id2[:7], "", true) |
|
187 |
+ assertIndexGet(t, index, id2[:11], "", true) |
|
188 |
+ |
|
189 |
+ // conflicts between id and id2 should be gone |
|
190 |
+ assertIndexGet(t, index, id[:6], id, false) |
|
191 |
+ assertIndexGet(t, index, id[:4], id, false) |
|
192 |
+ assertIndexGet(t, index, id[:1], id, false) |
|
193 |
+ |
|
194 |
+ // non-conflicting substrings should still not conflict |
|
195 |
+ assertIndexGet(t, index, id[:7], id, false) |
|
196 |
+ assertIndexGet(t, index, id[:15], id, false) |
|
197 |
+ assertIndexGet(t, index, id, id, false) |
|
198 |
+} |
|
199 |
+ |
|
200 |
+func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) { |
|
201 |
+ if result, err := index.Get(input); err != nil && !expectError { |
|
202 |
+ t.Fatalf("Unexpected error getting '%s': %s", input, err) |
|
203 |
+ } else if err == nil && expectError { |
|
204 |
+ t.Fatalf("Getting '%s' should return an error", input) |
|
205 |
+ } else if result != expectedResult { |
|
206 |
+ t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) |
|
207 |
+ } |
|
208 |
+} |