Browse code

Show shorthand container IDs for convenience. Shorthand IDs (or any non-conflicting prefix) can be used to lookup containers

Solomon Hykes authored on 2013/03/31 18:02:01
Showing 5 changed files
... ...
@@ -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
+}