
Client should react. Also, dial down the chattiness of errors for pods which do not exist and stop processing NotFound earlier in the podinfo chain
219 lines
6.8 KiB
Go
219 lines
6.8 KiB
Go
/*
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package kubelet
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
"github.com/fsouza/go-dockerclient"
|
|
)
|
|
|
|
// DockerContainerData is the structured representation of the JSON object returned by Docker inspect
|
|
type DockerContainerData struct {
|
|
state struct {
|
|
Running bool
|
|
}
|
|
}
|
|
|
|
// DockerInterface is an abstract interface for testability. It abstracts the interface of docker.Client.
|
|
type DockerInterface interface {
|
|
ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error)
|
|
InspectContainer(id string) (*docker.Container, error)
|
|
CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
|
|
StartContainer(id string, hostConfig *docker.HostConfig) error
|
|
StopContainer(id string, timeout uint) error
|
|
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
|
}
|
|
|
|
// DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids
|
|
type DockerID string
|
|
|
|
// DockerPuller is an abstract interface for testability. It abstracts image pull operations.
|
|
type DockerPuller interface {
|
|
Pull(image string) error
|
|
}
|
|
|
|
// dockerPuller is the default implementation of DockerPuller.
|
|
type dockerPuller struct {
|
|
client DockerInterface
|
|
}
|
|
|
|
// NewDockerPuller creates a new instance of the default implementation of DockerPuller.
|
|
func NewDockerPuller(client DockerInterface) DockerPuller {
|
|
return dockerPuller{
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
func (p dockerPuller) Pull(image string) error {
|
|
image, tag := parseImageName(image)
|
|
opts := docker.PullImageOptions{
|
|
Repository: image,
|
|
Tag: tag,
|
|
}
|
|
return p.client.PullImage(opts, docker.AuthConfiguration{})
|
|
}
|
|
|
|
// DockerContainers is a map of containers
|
|
type DockerContainers map[DockerID]*docker.APIContainers
|
|
|
|
func (c DockerContainers) FindPodContainer(manifestID, containerName string) (*docker.APIContainers, bool) {
|
|
for _, dockerContainer := range c {
|
|
dockerManifestID, dockerContainerName := parseDockerName(dockerContainer.Names[0])
|
|
if dockerManifestID == manifestID && dockerContainerName == containerName {
|
|
return dockerContainer, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (c DockerContainers) FindContainersByPodFullName(manifestID string) map[string]*docker.APIContainers {
|
|
containers := make(map[string]*docker.APIContainers)
|
|
|
|
for _, dockerContainer := range c {
|
|
dockerManifestID, dockerContainerName := parseDockerName(dockerContainer.Names[0])
|
|
if dockerManifestID == manifestID {
|
|
containers[dockerContainerName] = dockerContainer
|
|
}
|
|
}
|
|
return containers
|
|
}
|
|
|
|
// GetKubeletDockerContainers returns a map of docker containers that we manage. The map key is the docker container ID
|
|
func getKubeletDockerContainers(client DockerInterface) (DockerContainers, error) {
|
|
result := make(DockerContainers)
|
|
containers, err := client.ListContainers(docker.ListContainersOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range containers {
|
|
container := &containers[i]
|
|
// Skip containers that we didn't create to allow users to manually
|
|
// spin up their own containers if they want.
|
|
if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") {
|
|
continue
|
|
}
|
|
result[DockerID(container.ID)] = container
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ErrNoContainersInPod is returned when there are no running containers for a given pod
|
|
var ErrNoContainersInPod = errors.New("no containers exist for this pod")
|
|
|
|
// GetDockerPodInfo returns docker info for all containers in the pod/manifest.
|
|
func getDockerPodInfo(client DockerInterface, manifestID string) (api.PodInfo, error) {
|
|
info := api.PodInfo{}
|
|
|
|
containers, err := client.ListContainers(docker.ListContainersOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, value := range containers {
|
|
dockerManifestID, dockerContainerName := parseDockerName(value.Names[0])
|
|
if dockerManifestID != manifestID {
|
|
continue
|
|
}
|
|
inspectResult, err := client.InspectContainer(value.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if inspectResult == nil {
|
|
// Why did we not get an error?
|
|
info[dockerContainerName] = docker.Container{}
|
|
} else {
|
|
info[dockerContainerName] = *inspectResult
|
|
}
|
|
}
|
|
if len(info) == 0 {
|
|
return nil, ErrNoContainersInPod
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// Converts "-" to "_-_" and "_" to "___" so that we can use "--" to meaningfully separate parts of a docker name.
|
|
func escapeDash(in string) (out string) {
|
|
out = strings.Replace(in, "_", "___", -1)
|
|
out = strings.Replace(out, "-", "_-_", -1)
|
|
return
|
|
}
|
|
|
|
// Reverses the transformation of escapeDash.
|
|
func unescapeDash(in string) (out string) {
|
|
out = strings.Replace(in, "_-_", "-", -1)
|
|
out = strings.Replace(out, "___", "_", -1)
|
|
return
|
|
}
|
|
|
|
const containerNamePrefix = "k8s"
|
|
|
|
// Creates a name which can be reversed to identify both manifest id and container name.
|
|
func buildDockerName(manifest *api.ContainerManifest, container *api.Container) string {
|
|
// Note, manifest.ID could be blank.
|
|
return fmt.Sprintf("%s--%s--%s--%08x", containerNamePrefix, escapeDash(container.Name), escapeDash(manifest.ID), rand.Uint32())
|
|
}
|
|
|
|
// Upacks a container name, returning the manifest id and container name we would have used to
|
|
// construct the docker name. If the docker name isn't one we created, we may return empty strings.
|
|
func parseDockerName(name string) (manifestID, containerName string) {
|
|
// For some reason docker appears to be appending '/' to names.
|
|
// If its there, strip it.
|
|
if name[0] == '/' {
|
|
name = name[1:]
|
|
}
|
|
parts := strings.Split(name, "--")
|
|
if len(parts) == 0 || parts[0] != containerNamePrefix {
|
|
return
|
|
}
|
|
if len(parts) > 1 {
|
|
containerName = unescapeDash(parts[1])
|
|
}
|
|
if len(parts) > 2 {
|
|
manifestID = unescapeDash(parts[2])
|
|
}
|
|
return
|
|
}
|
|
|
|
// Parses image name including an tag and returns image name and tag
|
|
// TODO: Future Docker versions can parse the tag on daemon side, see:
|
|
// https://github.com/dotcloud/docker/issues/6876
|
|
// So this can be deprecated at some point.
|
|
func parseImageName(image string) (string, string) {
|
|
tag := ""
|
|
parts := strings.SplitN(image, "/", 2)
|
|
repo := ""
|
|
if len(parts) == 2 {
|
|
repo = parts[0]
|
|
image = parts[1]
|
|
}
|
|
parts = strings.SplitN(image, ":", 2)
|
|
if len(parts) == 2 {
|
|
image = parts[0]
|
|
tag = parts[1]
|
|
}
|
|
if repo != "" {
|
|
image = fmt.Sprintf("%s/%s", repo, image)
|
|
}
|
|
return image, tag
|
|
}
|