package host
import (
"fmt"
"path"
"strings"
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
"github.com/openshift/origin/pkg/bootstrap/docker/dockerhelper"
"github.com/openshift/origin/pkg/bootstrap/docker/errors"
"github.com/openshift/origin/pkg/bootstrap/docker/run"
)
const (
cmdTestNsenterMount = "nsenter --mount=/rootfs/proc/1/ns/mnt findmnt"
cmdEnsureHostDirs = "for dir in %s; do if [ ! -d \"${dir}\" ]; then mkdir -p \"${dir}\"; fi; done"
cmdCreateVolumesDirBindMount = "cat /rootfs/proc/1/mountinfo | grep /var/lib/origin || " +
"nsenter --mount=/rootfs/proc/1/ns/mnt mount -o bind %[1]s %[1]s"
cmdCreateVolumesDirShare = "cat /rootfs/proc/1/mountinfo | grep %[1]s | grep shared || " +
"nsenter --mount=/rootfs/proc/1/ns/mnt mount --make-shared %[1]s"
DefaultVolumesDir = "/var/lib/origin/openshift.local.volumes"
DefaultConfigDir = "/var/lib/origin/openshift.local.config"
)
// HostHelper contains methods to help check settings on a Docker host machine
// using a privileged container
type HostHelper struct {
runHelper *run.RunHelper
client *docker.Client
image string
volumesDir string
configDir string
dataDir string
}
// NewHostHelper creates a new HostHelper
func NewHostHelper(client *docker.Client, image, volumesDir, configDir, dataDir string) *HostHelper {
return &HostHelper{
runHelper: run.NewRunHelper(client),
client: client,
image: image,
volumesDir: volumesDir,
configDir: configDir,
dataDir: dataDir,
}
}
// CanUseNsenterMounter returns true if the Docker host machine can execute findmnt through nsenter
func (h *HostHelper) CanUseNsenterMounter() (bool, error) {
rc, err := h.runner().
Image(h.image).
DiscardContainer().
Privileged().
Bind("/:/rootfs:ro").
Entrypoint("/bin/bash").
Command("-c", cmdTestNsenterMount).Run()
return err == nil && rc == 0, nil
}
// EnsureVolumeShare ensures that the host Docker machine has a shared directory that can be used
// for OpenShift volumes
func (h *HostHelper) EnsureVolumeShare() error {
if err := h.ensureVolumesDirBindMount(); err != nil {
return err
}
if err := h.ensureVolumesDirShare(); err != nil {
return err
}
return nil
}
func (h *HostHelper) defaultBinds() []string {
return []string{fmt.Sprintf("%s:/var/lib/origin/openshift.local.config:z", h.configDir)}
}
// DownloadDirFromContainer copies a set of files from the Docker host to the local file system
func (h *HostHelper) DownloadDirFromContainer(sourceDir, destDir string) error {
container, err := h.runner().
Image(h.image).
Bind(h.defaultBinds()...).
Entrypoint("/bin/true").
Create()
if err != nil {
return err
}
defer func() {
errors.LogError(h.client.RemoveContainer(docker.RemoveContainerOptions{ID: container}))
}()
err = dockerhelper.DownloadDirFromContainer(h.client, container, sourceDir, destDir)
if err != nil {
glog.V(4).Infof("An error occurred downloading the directory: %v", err)
} else {
glog.V(4).Infof("Successfully downloaded directory.")
}
return err
}
// UploadFileToContainer copies a local file to the Docker host
func (h *HostHelper) UploadFileToContainer(src, dst string) error {
container, err := h.runner().
Image(h.image).
Bind(h.defaultBinds()...).
Entrypoint("/bin/true").
Create()
if err != nil {
return err
}
defer func() {
errors.LogError(h.client.RemoveContainer(docker.RemoveContainerOptions{ID: container}))
}()
err = dockerhelper.UploadFileToContainer(h.client, container, src, dst)
if err != nil {
glog.V(4).Infof("An error occurred uploading the file: %v", err)
} else {
glog.V(4).Infof("Successfully uploaded file.")
}
return err
}
// Hostname retrieves the FQDN of the Docker host machine
func (h *HostHelper) Hostname() (string, error) {
hostname, _, _, err := h.runner().
Image(h.image).
HostNetwork().
HostPid().
DiscardContainer().
Privileged().
Entrypoint("/bin/bash").
Command("-c", "uname -n").Output()
if err != nil {
return "", err
}
return strings.ToLower(strings.TrimSpace(hostname)), nil
}
func (h *HostHelper) EnsureHostDirectories() error {
// Attempt to create host directories only if they are
// the default directories. If the user specifies them, then the
// user is responsible for ensuring they exist, are mountable, etc.
dirs := []string{}
if h.configDir == DefaultConfigDir {
dirs = append(dirs, path.Join("/rootfs", h.configDir))
}
if h.volumesDir == DefaultVolumesDir {
dirs = append(dirs, path.Join("/rootfs", h.volumesDir))
}
if len(dirs) > 0 {
cmd := fmt.Sprintf(cmdEnsureHostDirs, strings.Join(dirs, " "))
rc, err := h.runner().
Image(h.image).
DiscardContainer().
Privileged().
Bind("/var:/rootfs/var").
Entrypoint("/bin/bash").
Command("-c", cmd).Run()
if err != nil || rc != 0 {
return errors.NewError("cannot create host volumes directory").WithCause(err)
}
}
return nil
}
func (h *HostHelper) hostPidCmd(cmd string) (int, error) {
return h.runner().
Image(h.image).
DiscardContainer().
HostPid().
Privileged().
Bind("/proc:/rootfs/proc:ro").
Entrypoint("/bin/bash").
Command("-c", cmd).Run()
}
func (h *HostHelper) ensureVolumesDirBindMount() error {
cmd := fmt.Sprintf(cmdCreateVolumesDirBindMount, h.volumesDir)
rc, err := h.hostPidCmd(cmd)
if err != nil || rc != 0 {
return errors.NewError("cannot create volumes dir mount").WithCause(err)
}
return nil
}
func (h *HostHelper) ensureVolumesDirShare() error {
cmd := fmt.Sprintf(cmdCreateVolumesDirShare, h.volumesDir)
rc, err := h.hostPidCmd(cmd)
if err != nil || rc != 0 {
return errors.NewError("cannot create volumes dir share").WithCause(err)
}
return nil
}
func (h *HostHelper) runner() *run.Runner {
return h.runHelper.New()
}