github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/container/view.go (about) 1 package container 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/network" 11 "github.com/docker/go-connections/nat" 12 "github.com/hashicorp/go-memdb" 13 "github.com/sirupsen/logrus" 14 ) 15 16 const ( 17 memdbContainersTable = "containers" 18 memdbNamesTable = "names" 19 20 memdbIDIndex = "id" 21 memdbContainerIDIndex = "containerid" 22 ) 23 24 var ( 25 // ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved 26 ErrNameReserved = errors.New("name is reserved") 27 // ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved 28 ErrNameNotReserved = errors.New("name is not reserved") 29 ) 30 31 // Snapshot is a read only view for Containers. It holds all information necessary to serve container queries in a 32 // versioned ACID in-memory store. 33 type Snapshot struct { 34 types.Container 35 36 // additional info queries need to filter on 37 // preserve nanosec resolution for queries 38 CreatedAt time.Time 39 StartedAt time.Time 40 Name string 41 Pid int 42 ExitCode int 43 Running bool 44 Paused bool 45 Managed bool 46 ExposedPorts nat.PortSet 47 PortBindings nat.PortSet 48 Health string 49 HostConfig struct { 50 Isolation string 51 } 52 } 53 54 // nameAssociation associates a container id with a name. 55 type nameAssociation struct { 56 // name is the name to associate. Note that name is the primary key 57 // ("id" in memdb). 58 name string 59 containerID string 60 } 61 62 // ViewDB provides an in-memory transactional (ACID) container Store 63 type ViewDB interface { 64 Snapshot() View 65 Save(*Container) error 66 Delete(*Container) error 67 68 ReserveName(name, containerID string) error 69 ReleaseName(name string) error 70 } 71 72 // View can be used by readers to avoid locking 73 type View interface { 74 All() ([]Snapshot, error) 75 Get(id string) (*Snapshot, error) 76 77 GetID(name string) (string, error) 78 GetAllNames() map[string][]string 79 } 80 81 var schema = &memdb.DBSchema{ 82 Tables: map[string]*memdb.TableSchema{ 83 memdbContainersTable: { 84 Name: memdbContainersTable, 85 Indexes: map[string]*memdb.IndexSchema{ 86 memdbIDIndex: { 87 Name: memdbIDIndex, 88 Unique: true, 89 Indexer: &containerByIDIndexer{}, 90 }, 91 }, 92 }, 93 memdbNamesTable: { 94 Name: memdbNamesTable, 95 Indexes: map[string]*memdb.IndexSchema{ 96 // Used for names, because "id" is the primary key in memdb. 97 memdbIDIndex: { 98 Name: memdbIDIndex, 99 Unique: true, 100 Indexer: &namesByNameIndexer{}, 101 }, 102 memdbContainerIDIndex: { 103 Name: memdbContainerIDIndex, 104 Indexer: &namesByContainerIDIndexer{}, 105 }, 106 }, 107 }, 108 }, 109 } 110 111 type memDB struct { 112 store *memdb.MemDB 113 } 114 115 // NoSuchContainerError indicates that the container wasn't found in the 116 // database. 117 type NoSuchContainerError struct { 118 id string 119 } 120 121 // Error satisfies the error interface. 122 func (e NoSuchContainerError) Error() string { 123 return "no such container " + e.id 124 } 125 126 // NewViewDB provides the default implementation, with the default schema 127 func NewViewDB() (ViewDB, error) { 128 store, err := memdb.NewMemDB(schema) 129 if err != nil { 130 return nil, err 131 } 132 return &memDB{store: store}, nil 133 } 134 135 // Snapshot provides a consistent read-only View of the database 136 func (db *memDB) Snapshot() View { 137 return &memdbView{ 138 txn: db.store.Txn(false), 139 } 140 } 141 142 func (db *memDB) withTxn(cb func(*memdb.Txn) error) error { 143 txn := db.store.Txn(true) 144 err := cb(txn) 145 if err != nil { 146 txn.Abort() 147 return err 148 } 149 txn.Commit() 150 return nil 151 } 152 153 // Save atomically updates the in-memory store state for a Container. 154 // Only read only (deep) copies of containers may be passed in. 155 func (db *memDB) Save(c *Container) error { 156 return db.withTxn(func(txn *memdb.Txn) error { 157 return txn.Insert(memdbContainersTable, c) 158 }) 159 } 160 161 // Delete removes an item by ID 162 func (db *memDB) Delete(c *Container) error { 163 return db.withTxn(func(txn *memdb.Txn) error { 164 view := &memdbView{txn: txn} 165 names := view.getNames(c.ID) 166 167 for _, name := range names { 168 txn.Delete(memdbNamesTable, nameAssociation{name: name}) 169 } 170 171 // Ignore error - the container may not actually exist in the 172 // db, but we still need to clean up associated names. 173 txn.Delete(memdbContainersTable, NewBaseContainer(c.ID, c.Root)) 174 return nil 175 }) 176 } 177 178 // ReserveName registers a container ID to a name 179 // ReserveName is idempotent 180 // Attempting to reserve a container ID to a name that already exists results in an `ErrNameReserved` 181 // A name reservation is globally unique 182 func (db *memDB) ReserveName(name, containerID string) error { 183 return db.withTxn(func(txn *memdb.Txn) error { 184 s, err := txn.First(memdbNamesTable, memdbIDIndex, name) 185 if err != nil { 186 return err 187 } 188 if s != nil { 189 if s.(nameAssociation).containerID != containerID { 190 return ErrNameReserved 191 } 192 return nil 193 } 194 195 if err := txn.Insert(memdbNamesTable, nameAssociation{name: name, containerID: containerID}); err != nil { 196 return err 197 } 198 return nil 199 }) 200 } 201 202 // ReleaseName releases the reserved name 203 // Once released, a name can be reserved again 204 func (db *memDB) ReleaseName(name string) error { 205 return db.withTxn(func(txn *memdb.Txn) error { 206 return txn.Delete(memdbNamesTable, nameAssociation{name: name}) 207 }) 208 } 209 210 type memdbView struct { 211 txn *memdb.Txn 212 } 213 214 // All returns a all items in this snapshot. Returned objects must never be modified. 215 func (v *memdbView) All() ([]Snapshot, error) { 216 var all []Snapshot 217 iter, err := v.txn.Get(memdbContainersTable, memdbIDIndex) 218 if err != nil { 219 return nil, err 220 } 221 for { 222 item := iter.Next() 223 if item == nil { 224 break 225 } 226 snapshot := v.transform(item.(*Container)) 227 all = append(all, *snapshot) 228 } 229 return all, nil 230 } 231 232 // Get returns an item by id. Returned objects must never be modified. 233 func (v *memdbView) Get(id string) (*Snapshot, error) { 234 s, err := v.txn.First(memdbContainersTable, memdbIDIndex, id) 235 if err != nil { 236 return nil, err 237 } 238 if s == nil { 239 return nil, NoSuchContainerError{id: id} 240 } 241 return v.transform(s.(*Container)), nil 242 } 243 244 // getNames lists all the reserved names for the given container ID. 245 func (v *memdbView) getNames(containerID string) []string { 246 iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex, containerID) 247 if err != nil { 248 return nil 249 } 250 251 var names []string 252 for { 253 item := iter.Next() 254 if item == nil { 255 break 256 } 257 names = append(names, item.(nameAssociation).name) 258 } 259 260 return names 261 } 262 263 // GetID returns the container ID that the passed in name is reserved to. 264 func (v *memdbView) GetID(name string) (string, error) { 265 s, err := v.txn.First(memdbNamesTable, memdbIDIndex, name) 266 if err != nil { 267 return "", err 268 } 269 if s == nil { 270 return "", ErrNameNotReserved 271 } 272 return s.(nameAssociation).containerID, nil 273 } 274 275 // GetAllNames returns all registered names. 276 func (v *memdbView) GetAllNames() map[string][]string { 277 iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex) 278 if err != nil { 279 return nil 280 } 281 282 out := make(map[string][]string) 283 for { 284 item := iter.Next() 285 if item == nil { 286 break 287 } 288 assoc := item.(nameAssociation) 289 out[assoc.containerID] = append(out[assoc.containerID], assoc.name) 290 } 291 292 return out 293 } 294 295 // transform maps a (deep) copied Container object to what queries need. 296 // A lock on the Container is not held because these are immutable deep copies. 297 func (v *memdbView) transform(container *Container) *Snapshot { 298 snapshot := &Snapshot{ 299 Container: types.Container{ 300 ID: container.ID, 301 Names: v.getNames(container.ID), 302 ImageID: container.ImageID.String(), 303 Ports: []types.Port{}, 304 Mounts: container.GetMountPoints(), 305 State: container.State.StateString(), 306 Status: container.State.String(), 307 Created: container.Created.Unix(), 308 }, 309 CreatedAt: container.Created, 310 StartedAt: container.StartedAt, 311 Name: container.Name, 312 Pid: container.Pid, 313 Managed: container.Managed, 314 ExposedPorts: make(nat.PortSet), 315 PortBindings: make(nat.PortSet), 316 Health: container.HealthString(), 317 Running: container.Running, 318 Paused: container.Paused, 319 ExitCode: container.ExitCode(), 320 } 321 322 if snapshot.Names == nil { 323 // Dead containers will often have no name, so make sure the response isn't null 324 snapshot.Names = []string{} 325 } 326 327 if container.HostConfig != nil { 328 snapshot.Container.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode) 329 snapshot.HostConfig.Isolation = string(container.HostConfig.Isolation) 330 for binding := range container.HostConfig.PortBindings { 331 snapshot.PortBindings[binding] = struct{}{} 332 } 333 } 334 335 if container.Config != nil { 336 snapshot.Image = container.Config.Image 337 snapshot.Labels = container.Config.Labels 338 for exposed := range container.Config.ExposedPorts { 339 snapshot.ExposedPorts[exposed] = struct{}{} 340 } 341 } 342 343 if len(container.Args) > 0 { 344 args := []string{} 345 for _, arg := range container.Args { 346 if strings.Contains(arg, " ") { 347 args = append(args, fmt.Sprintf("'%s'", arg)) 348 } else { 349 args = append(args, arg) 350 } 351 } 352 argsAsString := strings.Join(args, " ") 353 snapshot.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 354 } else { 355 snapshot.Command = container.Path 356 } 357 358 snapshot.Ports = []types.Port{} 359 networks := make(map[string]*network.EndpointSettings) 360 if container.NetworkSettings != nil { 361 for name, netw := range container.NetworkSettings.Networks { 362 if netw == nil || netw.EndpointSettings == nil { 363 continue 364 } 365 networks[name] = &network.EndpointSettings{ 366 EndpointID: netw.EndpointID, 367 Gateway: netw.Gateway, 368 IPAddress: netw.IPAddress, 369 IPPrefixLen: netw.IPPrefixLen, 370 IPv6Gateway: netw.IPv6Gateway, 371 GlobalIPv6Address: netw.GlobalIPv6Address, 372 GlobalIPv6PrefixLen: netw.GlobalIPv6PrefixLen, 373 MacAddress: netw.MacAddress, 374 NetworkID: netw.NetworkID, 375 } 376 if netw.IPAMConfig != nil { 377 networks[name].IPAMConfig = &network.EndpointIPAMConfig{ 378 IPv4Address: netw.IPAMConfig.IPv4Address, 379 IPv6Address: netw.IPAMConfig.IPv6Address, 380 } 381 } 382 } 383 for port, bindings := range container.NetworkSettings.Ports { 384 p, err := nat.ParsePort(port.Port()) 385 if err != nil { 386 logrus.Warnf("invalid port map %+v", err) 387 continue 388 } 389 if len(bindings) == 0 { 390 snapshot.Ports = append(snapshot.Ports, types.Port{ 391 PrivatePort: uint16(p), 392 Type: port.Proto(), 393 }) 394 continue 395 } 396 for _, binding := range bindings { 397 h, err := nat.ParsePort(binding.HostPort) 398 if err != nil { 399 logrus.Warnf("invalid host port map %+v", err) 400 continue 401 } 402 snapshot.Ports = append(snapshot.Ports, types.Port{ 403 PrivatePort: uint16(p), 404 PublicPort: uint16(h), 405 Type: port.Proto(), 406 IP: binding.HostIP, 407 }) 408 } 409 } 410 } 411 snapshot.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks} 412 413 return snapshot 414 } 415 416 // containerByIDIndexer is used to extract the ID field from Container types. 417 // memdb.StringFieldIndex can not be used since ID is a field from an embedded struct. 418 type containerByIDIndexer struct{} 419 420 // FromObject implements the memdb.SingleIndexer interface for Container objects 421 func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error) { 422 c, ok := obj.(*Container) 423 if !ok { 424 return false, nil, fmt.Errorf("%T is not a Container", obj) 425 } 426 // Add the null character as a terminator 427 v := c.ID + "\x00" 428 return true, []byte(v), nil 429 } 430 431 // FromArgs implements the memdb.Indexer interface 432 func (e *containerByIDIndexer) FromArgs(args ...interface{}) ([]byte, error) { 433 if len(args) != 1 { 434 return nil, fmt.Errorf("must provide only a single argument") 435 } 436 arg, ok := args[0].(string) 437 if !ok { 438 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 439 } 440 // Add the null character as a terminator 441 arg += "\x00" 442 return []byte(arg), nil 443 } 444 445 // namesByNameIndexer is used to index container name associations by name. 446 type namesByNameIndexer struct{} 447 448 func (e *namesByNameIndexer) FromObject(obj interface{}) (bool, []byte, error) { 449 n, ok := obj.(nameAssociation) 450 if !ok { 451 return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj) 452 } 453 454 // Add the null character as a terminator 455 return true, []byte(n.name + "\x00"), nil 456 } 457 458 func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) { 459 if len(args) != 1 { 460 return nil, fmt.Errorf("must provide only a single argument") 461 } 462 arg, ok := args[0].(string) 463 if !ok { 464 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 465 } 466 // Add the null character as a terminator 467 arg += "\x00" 468 return []byte(arg), nil 469 } 470 471 // namesByContainerIDIndexer is used to index container names by container ID. 472 type namesByContainerIDIndexer struct{} 473 474 func (e *namesByContainerIDIndexer) FromObject(obj interface{}) (bool, []byte, error) { 475 n, ok := obj.(nameAssociation) 476 if !ok { 477 return false, nil, fmt.Errorf(`%T does not have type "nameAssocation"`, obj) 478 } 479 480 // Add the null character as a terminator 481 return true, []byte(n.containerID + "\x00"), nil 482 } 483 484 func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error) { 485 if len(args) != 1 { 486 return nil, fmt.Errorf("must provide only a single argument") 487 } 488 arg, ok := args[0].(string) 489 if !ok { 490 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 491 } 492 // Add the null character as a terminator 493 arg += "\x00" 494 return []byte(arg), nil 495 }