github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/metadata/containers.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package metadata
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  
    25  	"github.com/boltdb/bolt"
    26  
    27  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    28  )
    29  
    30  var (
    31  	containersBucket   = []byte("containers")
    32  	containerKeyPrefix = []byte("containers/")
    33  )
    34  
    35  func containerKey(containerID string) []byte {
    36  	return append(containerKeyPrefix, []byte(containerID)...)
    37  }
    38  
    39  type containerMeta struct {
    40  	client *boltClient
    41  	id     string
    42  }
    43  
    44  // GetID returns ID of the container managed by this object
    45  func (m containerMeta) GetID() string {
    46  	return m.id
    47  }
    48  
    49  // Retrieve loads from DB and returns container data bound to the object
    50  func (m containerMeta) Retrieve() (*types.ContainerInfo, error) {
    51  	if m.GetID() == "" {
    52  		return nil, errors.New("Container ID cannot be empty")
    53  	}
    54  	var ci *types.ContainerInfo
    55  	err := m.client.db.View(func(tx *bolt.Tx) error {
    56  		bucket := tx.Bucket(containersBucket)
    57  		if bucket == nil {
    58  			return nil
    59  		}
    60  		data := bucket.Get([]byte(m.GetID()))
    61  		if data == nil {
    62  			return nil
    63  		}
    64  		return json.Unmarshal(data, &ci)
    65  	})
    66  	return ci, err
    67  }
    68  
    69  // Save allows to create/modify/delete container data bound to the object.
    70  // Supplied handler gets current ContainerInfo value (nil if doesn't exist) and returns new structure
    71  // value to be saved or nil to delete. If error value is returned from the handler, the transaction is
    72  // rolled back and returned error becomes the result of the function
    73  func (m containerMeta) Save(updater func(*types.ContainerInfo) (*types.ContainerInfo, error)) error {
    74  	if m.GetID() == "" {
    75  		return errors.New("Container ID cannot be empty")
    76  	}
    77  	return m.client.db.Update(func(tx *bolt.Tx) error {
    78  		bucket, err := tx.CreateBucketIfNotExists(containersBucket)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		var current *types.ContainerInfo
    83  		var oldPodID string
    84  		data := bucket.Get([]byte(m.GetID()))
    85  		if data != nil {
    86  			if err = json.Unmarshal(data, &current); err != nil {
    87  				return err
    88  			}
    89  			oldPodID = current.Config.PodSandboxID
    90  		}
    91  		newData, err := updater(current)
    92  		if err != nil {
    93  			return err
    94  		}
    95  
    96  		if current == nil && newData == nil {
    97  			return nil
    98  		}
    99  
   100  		if newData == nil {
   101  			if oldPodID != "" {
   102  				if err = removeContainerFromSandbox(tx, m.GetID(), oldPodID); err != nil {
   103  					return err
   104  				}
   105  			}
   106  			return bucket.Delete([]byte(m.GetID()))
   107  		}
   108  		newData.Id = m.GetID()
   109  		data, err = json.Marshal(newData)
   110  		if err != nil {
   111  			return err
   112  		}
   113  
   114  		if oldPodID != newData.Config.PodSandboxID {
   115  			if oldPodID != "" {
   116  				if err = removeContainerFromSandbox(tx, m.GetID(), oldPodID); err != nil {
   117  					return err
   118  				}
   119  			}
   120  			if newData.Config.PodSandboxID != "" {
   121  				if err = addContainerToSandbox(tx, m.GetID(), newData.Config.PodSandboxID); err != nil {
   122  					return err
   123  				}
   124  			}
   125  		}
   126  		return bucket.Put([]byte(m.GetID()), data)
   127  	})
   128  }
   129  
   130  func addContainerToSandbox(tx *bolt.Tx, containerID, sandboxID string) error {
   131  	bucket, err := getSandboxBucket(tx, sandboxID, true, false)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	return bucket.Put(containerKey(containerID), []byte{})
   136  }
   137  
   138  func removeContainerFromSandbox(tx *bolt.Tx, containerID, sandboxID string) error {
   139  	bucket, err := getSandboxBucket(tx, sandboxID, false, true)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if bucket == nil {
   144  		return nil
   145  	}
   146  	return bucket.Delete(containerKey(containerID))
   147  }
   148  
   149  // Container returns interface instance which manages container with given ID
   150  func (b *boltClient) Container(containerID string) ContainerMetadata {
   151  	return &containerMeta{id: containerID, client: b}
   152  }
   153  
   154  // ListPodContainers returns a list of containers that belong to the pod with given ID value
   155  func (b *boltClient) ListPodContainers(podID string) ([]ContainerMetadata, error) {
   156  	if podID == "" {
   157  		return nil, errors.New("Pod sandbox ID cannot be empty")
   158  	}
   159  	var result []ContainerMetadata
   160  	err := b.db.View(func(tx *bolt.Tx) error {
   161  		bucket, err := getSandboxBucket(tx, podID, false, false)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		c := bucket.Cursor()
   166  		for k, _ := c.Seek(containerKeyPrefix); k != nil && bytes.HasPrefix(k, containerKeyPrefix); k, _ = c.Next() {
   167  			result = append(result, b.Container(string(k[len(containerKeyPrefix):])))
   168  		}
   169  		return nil
   170  	})
   171  	return result, err
   172  }
   173  
   174  // ImagesInUse returns a set of images in use by containers in the store.
   175  // The keys of the returned map are image names and the values are always true.
   176  func (b *boltClient) ImagesInUse() (map[string]bool, error) {
   177  	result := make(map[string]bool)
   178  	if err := b.db.View(func(tx *bolt.Tx) error {
   179  		c := tx.Cursor()
   180  		for k, _ := c.Seek(sandboxKeyPrefix); k != nil && bytes.HasPrefix(k, sandboxKeyPrefix); k, _ = c.Next() {
   181  			containers, err := b.ListPodContainers(string(k[len(sandboxKeyPrefix):]))
   182  			if err != nil {
   183  				return err
   184  			}
   185  			for _, containerMeta := range containers {
   186  				ci, err := containerMeta.Retrieve()
   187  				if err != nil {
   188  					return err
   189  				}
   190  				if ci == nil {
   191  					return fmt.Errorf("containerInfo of container %q not found in Virtlet metadata store", containerMeta.GetID())
   192  				}
   193  				result[ci.Config.Image] = true
   194  			}
   195  		}
   196  		return nil
   197  	}); err != nil {
   198  		return nil, err
   199  	}
   200  	return result, nil
   201  }