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