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