Fix bugs identified by unit tests

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Closes: #268
Approved by: rhatdan
This commit is contained in:
Matthew Heon
2018-02-07 15:05:17 -05:00
committed by Atomic Bot
parent 4bc9a6d633
commit cb28a1d284
4 changed files with 163 additions and 24 deletions

View File

@ -123,6 +123,17 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
return errors.Wrapf(ErrInvalidArg, "cannot add a container that is in a pod with AddContainer, use AddContainerToPod") return errors.Wrapf(ErrInvalidArg, "cannot add a container that is in a pod with AddContainer, use AddContainerToPod")
} }
// There are potential race conditions with this
// But in-memory state is intended purely for testing and not production
// use, so this should be fine.
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs {
_, ok = s.containers[depCtr]
if !ok {
return errors.Wrapf(ErrNoSuchCtr, "cannot depend on nonexistent container %s", depCtr)
}
}
if err := s.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil { if err := s.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil {
return errors.Wrapf(err, "error registering container name %s", ctr.Name()) return errors.Wrapf(err, "error registering container name %s", ctr.Name())
} }
@ -135,7 +146,6 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
s.containers[ctr.ID()] = ctr s.containers[ctr.ID()] = ctr
// Add containers this container depends on // Add containers this container depends on
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs { for _, depCtr := range depCtrs {
s.addCtrToDependsMap(ctr.ID(), depCtr) s.addCtrToDependsMap(ctr.ID(), depCtr)
} }
@ -157,6 +167,7 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
} }
if _, ok := s.containers[ctr.ID()]; !ok { if _, ok := s.containers[ctr.ID()]; !ok {
ctr.valid = false
return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID()) return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID())
} }
@ -301,6 +312,10 @@ func (s *InMemoryState) PodHasContainer(pod *Pod, ctrID string) (bool, error) {
return false, errors.Wrapf(ErrPodRemoved, "pod %s is not valid") return false, errors.Wrapf(ErrPodRemoved, "pod %s is not valid")
} }
if ctrID == "" {
return false, ErrEmptyID
}
podCtrs, ok := s.podContainers[pod.ID()] podCtrs, ok := s.podContainers[pod.ID()]
if !ok { if !ok {
pod.valid = false pod.valid = false
@ -398,10 +413,12 @@ func (s *InMemoryState) RemovePod(pod *Pod) error {
// pods out of the state // pods out of the state
if _, ok := s.pods[pod.ID()]; !ok { if _, ok := s.pods[pod.ID()]; !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
} }
podCtrs, ok := s.podContainers[pod.ID()] podCtrs, ok := s.podContainers[pod.ID()]
if !ok { if !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
} }
if len(podCtrs) != 0 { if len(podCtrs) != 0 {
@ -430,16 +447,14 @@ func (s *InMemoryState) RemovePodContainers(pod *Pod) error {
// Get pod containers // Get pod containers
podCtrs, ok := s.podContainers[pod.ID()] podCtrs, ok := s.podContainers[pod.ID()]
if !ok { if !ok {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
} }
// Go through container dependencies. Check to see if any are outside the pod. // Go through container dependencies. Check to see if any are outside the pod.
for ctr := range podCtrs { for ctr := range podCtrs {
ctrDeps, ok := s.ctrDepends[ctr] ctrDeps, ok := s.ctrDepends[ctr]
if !ok { if ok {
return errors.Wrapf(ErrInternal, "cannot find dependencies for container %s", ctr)
}
for _, dep := range ctrDeps { for _, dep := range ctrDeps {
_, ok := podCtrs[dep] _, ok := podCtrs[dep]
if !ok { if !ok {
@ -447,6 +462,7 @@ func (s *InMemoryState) RemovePodContainers(pod *Pod) error {
} }
} }
} }
}
// All dependencies are OK to remove // All dependencies are OK to remove
// Remove all containers // Remove all containers
@ -490,6 +506,17 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in pod %s", ctr.ID(), pod.ID()) return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in pod %s", ctr.ID(), pod.ID())
} }
// There are potential race conditions with this
// But in-memory state is intended purely for testing and not production
// use, so this should be fine.
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs {
_, ok = s.containers[depCtr]
if !ok {
return errors.Wrapf(ErrNoSuchCtr, "cannot depend on nonexistent container %s", depCtr)
}
}
// Add container to state // Add container to state
_, ok = s.containers[ctr.ID()] _, ok = s.containers[ctr.ID()]
if ok { if ok {
@ -510,6 +537,11 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error {
// Add container to pod containers // Add container to pod containers
podCtrs[ctr.ID()] = ctr podCtrs[ctr.ID()] = ctr
// Add containers this container depends on
for _, depCtr := range depCtrs {
s.addCtrToDependsMap(ctr.ID(), depCtr)
}
return nil return nil
} }
@ -523,6 +555,13 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be removed from the pod", ctr.ID()) return errors.Wrapf(ErrCtrRemoved, "container %s is not valid and cannot be removed from the pod", ctr.ID())
} }
// Ensure we don't remove a container which other containers depend on
deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 {
depsStr := strings.Join(deps, ", ")
return errors.Wrapf(ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr)
}
// Retrieve pod containers // Retrieve pod containers
podCtrs, ok := s.podContainers[pod.ID()] podCtrs, ok := s.podContainers[pod.ID()]
if !ok { if !ok {
@ -530,6 +569,12 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrPodRemoved, "pod %s has been removed", pod.ID()) return errors.Wrapf(ErrPodRemoved, "pod %s has been removed", pod.ID())
} }
// Does the container exist?
if _, ok := s.containers[ctr.ID()]; !ok {
ctr.valid = false
return errors.Wrapf(ErrNoSuchCtr, "container %s does not exist in state", ctr.ID())
}
// Is the container in the pod? // Is the container in the pod?
if _, ok := podCtrs[ctr.ID()]; !ok { if _, ok := podCtrs[ctr.ID()]; !ok {
return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in pod %s", ctr.ID(), pod.ID()) return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in pod %s", ctr.ID(), pod.ID())
@ -549,6 +594,12 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
// Remove the container from the pod // Remove the container from the pod
delete(podCtrs, ctr.ID()) delete(podCtrs, ctr.ID())
// Remove us from container dependencies
depCtrs := ctr.Dependencies()
for _, depCtr := range depCtrs {
s.removeCtrFromDependsMap(ctr.ID(), depCtr)
}
return nil return nil
} }

View File

@ -45,6 +45,7 @@ func newPod(lockDir string, runtime *Runtime) (*Pod, error) {
pod := new(Pod) pod := new(Pod)
pod.id = stringid.GenerateNonCryptoID() pod.id = stringid.GenerateNonCryptoID()
pod.name = namesgenerator.GetRandomName(0) pod.name = namesgenerator.GetRandomName(0)
pod.labels = make(map[string]string)
pod.runtime = runtime pod.runtime = runtime
// Path our lock file will reside at // Path our lock file will reside at

View File

@ -14,7 +14,7 @@ import (
// DBSchema is the current DB schema version // DBSchema is the current DB schema version
// Increments every time a change is made to the database's tables // Increments every time a change is made to the database's tables
const DBSchema = 9 const DBSchema = 10
// SQLState is a state implementation backed by a persistent SQLite3 database // SQLState is a state implementation backed by a persistent SQLite3 database
type SQLState struct { type SQLState struct {
@ -252,7 +252,7 @@ func (s *SQLState) AddContainer(ctr *Container) (err error) {
return errors.Wrapf(ErrPodExists, "cannot add container that belongs to a pod, use AddContainerToPod instead") return errors.Wrapf(ErrPodExists, "cannot add container that belongs to a pod, use AddContainerToPod instead")
} }
return s.addContainer(ctr) return s.addContainer(ctr, nil)
} }
// UpdateContainer updates a container's state from the database // UpdateContainer updates a container's state from the database
@ -381,7 +381,7 @@ func (s *SQLState) UpdateContainer(ctr *Container) error {
} }
// SaveContainer updates a container's state in the database // SaveContainer updates a container's state in the database
func (s *SQLState) SaveContainer(ctr *Container) error { func (s *SQLState) SaveContainer(ctr *Container) (err error) {
const update = `UPDATE containerState SET const update = `UPDATE containerState SET
State=?, State=?,
ConfigPath=?, ConfigPath=?,
@ -508,7 +508,7 @@ func (s *SQLState) RemoveContainer(ctr *Container) error {
return errors.Wrapf(ErrPodExists, "container %s belongs to a pod, use RemoveContainerFromPod", ctr.ID()) return errors.Wrapf(ErrPodExists, "container %s belongs to a pod, use RemoveContainerFromPod", ctr.ID())
} }
return s.removeContainer(ctr) return s.removeContainer(ctr, nil)
} }
// AllContainers retrieves all the containers presently in the state // AllContainers retrieves all the containers presently in the state
@ -801,7 +801,7 @@ func (s *SQLState) AddPod(pod *Pod) (err error) {
// RemovePod removes a pod from the state // RemovePod removes a pod from the state
// Only empty pods can be removed // Only empty pods can be removed
func (s *SQLState) RemovePod(pod *Pod) error { func (s *SQLState) RemovePod(pod *Pod) (err error) {
const ( const (
removePod = "DELETE FROM pods WHERE ID=?;" removePod = "DELETE FROM pods WHERE ID=?;"
removeRegistry = "DELETE FROM registry WHERE Id=?;" removeRegistry = "DELETE FROM registry WHERE Id=?;"
@ -832,6 +832,7 @@ func (s *SQLState) RemovePod(pod *Pod) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "error retrieving number of rows in transaction removing pod %s", pod.ID()) return errors.Wrapf(err, "error retrieving number of rows in transaction removing pod %s", pod.ID())
} else if rows == 0 { } else if rows == 0 {
pod.valid = false
return ErrNoSuchPod return ErrNoSuchPod
} }
@ -851,11 +852,11 @@ func (s *SQLState) RemovePod(pod *Pod) error {
// This can avoid issues with dependencies within the pod // This can avoid issues with dependencies within the pod
// The operation will fail if any container in the pod has a dependency from // The operation will fail if any container in the pod has a dependency from
// outside the pod // outside the pod
func (s *SQLState) RemovePodContainers(pod *Pod) error { func (s *SQLState) RemovePodContainers(pod *Pod) (err error) {
const ( const (
getPodCtrs = "SELECT Id FROM containers WHERE pod=?;" getPodCtrs = "SELECT Id FROM containers WHERE pod=?;"
removeCtr = "DELETE FROM containers WHERE pod=?;" removeCtr = "DELETE FROM containers WHERE pod=?;"
removeCtrState = "DELETE FROM containerState WHERE ID=ANY(SELECT Id FROM containers WHERE pod=?);" removeCtrState = "DELETE FROM containerState WHERE ID IN (SELECT Id FROM containers WHERE pod=?);"
) )
if !s.valid { if !s.valid {
@ -880,6 +881,16 @@ func (s *SQLState) RemovePodContainers(pod *Pod) error {
} }
}() }()
// Check if the pod exists
exists, err := podExistsTx(pod.ID(), tx)
if err != nil {
return err
}
if !exists {
pod.valid = false
return ErrNoSuchPod
}
// First get all containers in the pod // First get all containers in the pod
rows, err := tx.Query(getPodCtrs, pod.ID()) rows, err := tx.Query(getPodCtrs, pod.ID())
if err != nil { if err != nil {
@ -920,7 +931,7 @@ func (s *SQLState) RemovePodContainers(pod *Pod) error {
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return errors.Wrapf(err, "error committing transaction to add pod %s", pod.ID()) return errors.Wrapf(err, "error committing transaction remove pod %s containers", pod.ID())
} }
committed = true committed = true
@ -963,7 +974,7 @@ func (s *SQLState) AddContainerToPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrInvalidArg, "container's pod ID does not match given pod's ID") return errors.Wrapf(ErrInvalidArg, "container's pod ID does not match given pod's ID")
} }
return s.addContainer(ctr) return s.addContainer(ctr, pod)
} }
// RemoveContainerFromPod removes a container from the given pod // RemoveContainerFromPod removes a container from the given pod
@ -972,7 +983,7 @@ func (s *SQLState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
return errors.Wrapf(ErrInvalidArg, "container %s is not in pod %s", ctr.ID(), pod.ID()) return errors.Wrapf(ErrInvalidArg, "container %s is not in pod %s", ctr.ID(), pod.ID())
} }
return s.removeContainer(ctr) return s.removeContainer(ctr, pod)
} }
// AllPods retrieves all pods presently in the state // AllPods retrieves all pods presently in the state

View File

@ -37,6 +37,7 @@ const (
FROM containers FROM containers
INNER JOIN INNER JOIN
containerState ON containers.Id = containerState.Id ` containerState ON containers.Id = containerState.Id `
ExistsQuery = "SELECT 1 FROM pods WHERE Id=?;"
) )
// Checks that the DB configuration matches the runtime's configuration // Checks that the DB configuration matches the runtime's configuration
@ -326,9 +327,7 @@ func prepareDB(db *sql.DB) (err error) {
// Check if given pod exists // Check if given pod exists
// Internal-only version of hasPod // Internal-only version of hasPod
func (s *SQLState) podExists(id string) (bool, error) { func (s *SQLState) podExists(id string) (bool, error) {
const query = "SELECT 1 FROM pods WHERE Id=?;" row := s.db.QueryRow(ExistsQuery, id)
row := s.db.QueryRow(query, id)
var check int var check int
err := row.Scan(&check) err := row.Scan(&check)
@ -343,7 +342,26 @@ func (s *SQLState) podExists(id string) (bool, error) {
} }
return true, nil return true, nil
}
// Check if given pod exists within a transaction
// Transaction-based version of podExists
func podExistsTx(id string, tx *sql.Tx) (bool, error) {
row := tx.QueryRow(ExistsQuery, id)
var check int
err := row.Scan(&check)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, errors.Wrapf(err, "error questing database for existence of pod %s", id)
} else if check != 1 {
return false, errors.Wrapf(ErrInternal, "check digit for podExists query incorrect")
}
return true, nil
} }
// Get filename for OCI spec on disk // Get filename for OCI spec on disk
@ -717,7 +735,7 @@ func (s *SQLState) podFromScannable(row scannable) (*Pod, error) {
} }
// Internal function for adding containers // Internal function for adding containers
func (s *SQLState) addContainer(ctr *Container) (err error) { func (s *SQLState) addContainer(ctr *Container, pod *Pod) (err error) {
const ( const (
addCtr = `INSERT INTO containers VALUES ( addCtr = `INSERT INTO containers VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
@ -800,6 +818,19 @@ func (s *SQLState) addContainer(ctr *Container) (err error) {
} }
}() }()
// First check if pod exists, if one is given
if pod != nil {
exists, err := podExistsTx(pod.ID(), tx)
if err != nil {
return err
}
if !exists {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist in state, cannot add container to it", pod.ID())
}
}
// Add container to registry // Add container to registry
if _, err := tx.Exec(addRegistry, ctr.ID(), ctr.Name()); err != nil { if _, err := tx.Exec(addRegistry, ctr.ID(), ctr.Name()); err != nil {
return errors.Wrapf(err, "error adding container %s to name/ID registry", ctr.ID()) return errors.Wrapf(err, "error adding container %s to name/ID registry", ctr.ID())
@ -905,11 +936,13 @@ func (s *SQLState) addContainer(ctr *Container) (err error) {
} }
// Internal functions for removing containers // Internal functions for removing containers
func (s *SQLState) removeContainer(ctr *Container) error { func (s *SQLState) removeContainer(ctr *Container, pod *Pod) (err error) {
const ( const (
removeCtr = "DELETE FROM containers WHERE Id=?;" removeCtr = "DELETE FROM containers WHERE Id=?;"
removeState = "DELETE FROM containerState WHERE Id=?;" removeState = "DELETE FROM containerState WHERE Id=?;"
removeRegistry = "DELETE FROM registry WHERE Id=?;" removeRegistry = "DELETE FROM registry WHERE Id=?;"
existsInPod = "SELECT 1 FROM containers WHERE Id=? AND Pod=?;"
ctrExists = "SELECT 1 FROM containers WHERE Id=?;"
) )
if !s.valid { if !s.valid {
@ -930,6 +963,48 @@ func (s *SQLState) removeContainer(ctr *Container) error {
} }
}() }()
if pod != nil {
// Check to see if the pod exists
exists, err := podExistsTx(pod.ID(), tx)
if err != nil {
return err
}
if !exists {
pod.valid = false
return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist in state, cannot add container to it", pod.ID())
}
var check int
// Check to see if the container exists
row := tx.QueryRow(ctrExists, ctr.ID())
err = row.Scan(&check)
if err != nil {
if err == sql.ErrNoRows {
ctr.valid = false
return errors.Wrapf(ErrNoSuchCtr, "container %s does not exist", ctr.ID())
}
return errors.Wrapf(err, "error querying database for container %s existence", ctr.ID())
} else if check != 1 {
return errors.Wrapf(ErrInternal, "check digit for ctr exists query incorrect")
}
// Check to see if the container is in the pod
row = tx.QueryRow(existsInPod, ctr.ID(), pod.ID())
err = row.Scan(&check)
if err != nil {
if err == sql.ErrNoRows {
return errors.Wrapf(ErrNoSuchCtr, "container %s is not in pod %s", ctr.ID(), pod.ID())
}
return errors.Wrapf(err, "error querying database for container %s existence", ctr.ID())
} else if check != 1 {
return errors.Wrapf(ErrInternal, "check digit for ctr exists in pod query incorrect")
}
}
// Check rows acted on for the first transaction, verify we actually removed something // Check rows acted on for the first transaction, verify we actually removed something
result, err := tx.Exec(removeCtr, ctr.ID()) result, err := tx.Exec(removeCtr, ctr.ID())
if err != nil { if err != nil {
@ -939,6 +1014,7 @@ func (s *SQLState) removeContainer(ctr *Container) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "error retrieving number of rows in transaction removing container %s", ctr.ID()) return errors.Wrapf(err, "error retrieving number of rows in transaction removing container %s", ctr.ID())
} else if rows == 0 { } else if rows == 0 {
ctr.valid = false
return ErrNoSuchCtr return ErrNoSuchCtr
} }