Skip to content

Commit

Permalink
Adding support to ContainerizedVM
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Guimaraes committed Jun 25, 2015
1 parent c1ba917 commit 85478a8
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 19 deletions.
17 changes: 13 additions & 4 deletions perfkitbenchmarker/benchmark_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""Container for all data required for a benchmark to run."""

import logging
import os
import pickle

from perfkitbenchmarker import disk
Expand All @@ -40,6 +41,7 @@
DIGITALOCEAN = 'DigitalOcean'
DEBIAN = 'debian'
RHEL = 'rhel'
UBUNTU_CONTAINER = 'ubuntu_container'
IMAGE = 'image'
MACHINE_TYPE = 'machine_type'
ZONE = 'zone'
Expand All @@ -50,7 +52,8 @@
GCP: {
VIRTUAL_MACHINE: {
DEBIAN: gce_virtual_machine.DebianBasedGceVirtualMachine,
RHEL: gce_virtual_machine.RhelBasedGceVirtualMachine
RHEL: gce_virtual_machine.RhelBasedGceVirtualMachine,
UBUNTU_CONTAINER: gce_virtual_machine.ContainerizedGceVirtualMachine
},
FIREWALL: gce_network.GceFirewall
},
Expand Down Expand Up @@ -84,12 +87,14 @@
flags.DEFINE_enum('cloud', GCP, [GCP, AZURE, AWS, DIGITALOCEAN],
'Name of the cloud to use.')
flags.DEFINE_enum(
'os_type', DEBIAN, [DEBIAN, RHEL],
'os_type', DEBIAN, [DEBIAN, RHEL, UBUNTU_CONTAINER],
'The VM\'s OS type. Ubuntu\'s os_type is "debian" because it is largely '
'built on Debian and uses the same package manager. Likewise, CentOS\'s '
'os_type is "rhel". In general if two OS\'s use the same package manager, '
'and are otherwise very similar, the same os_type should work on both of '
'them.')
flags.DEFINE_string('scratch_dir', '/',
'Directory in the VM where scratch disks will be mounted.')


class BenchmarkSpec(object):
Expand Down Expand Up @@ -156,9 +161,10 @@ def __init__(self, benchmark_info):
else:
num_striped_disks = FLAGS.num_striped_disks
for i in range(benchmark_info['scratch_disk']):
mount_point = os.path.join(FLAGS.scratch_dir, 'scratch%d' % i)
disk_spec = disk.BaseDiskSpec(
self.scratch_disk_size, self.scratch_disk_type,
'/scratch%d' % i, self.scratch_disk_iops,
mount_point, self.scratch_disk_iops,
num_striped_disks)
vm.disk_specs.append(disk_spec)

Expand Down Expand Up @@ -278,12 +284,15 @@ def PrepareVm(self, vm, firewall):
firewall.AllowPort(vm, port)
vm.AddMetadata(benchmark=self.benchmark_name)
vm.WaitForBootCompletion()
vm.OnStartup()
if FLAGS.scratch_disk_type == disk.LOCAL:
vm.SetupLocalDisks()
for disk_spec in vm.disk_specs:
vm.CreateScratchDisk(disk_spec)

# This must come after Scratch Disk creation to support the
# Containerized VM case
vm.OnStartup()

def DeleteVm(self, vm):
"""Deletes a single vm and scratch disk if required.
Expand Down
3 changes: 2 additions & 1 deletion perfkitbenchmarker/benchmarks/cassandra_stress_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ def Prepare(benchmark_spec):
'1 loader node, 3 data nodes')
vm_dict[LOADER_NODE] = [vm_dict['default'][-1]]
vm_dict[DATA_NODE] = vm_dict['default'][:3]
mount_point = os.path.join(vm_util.GetTempDir(), 'cassandra_data')
disk_spec = disk.BaseDiskSpec(
FLAGS.scratch_disk_size,
FLAGS.scratch_disk_type,
'/cassandra_data')
mount_point)
for vm in vm_dict[DATA_NODE]:
vm.CreateScratchDisk(disk_spec)

Expand Down
11 changes: 8 additions & 3 deletions perfkitbenchmarker/gcp/gce_virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from perfkitbenchmarker import disk
from perfkitbenchmarker import errors
from perfkitbenchmarker import flags
from perfkitbenchmarker import linux_virtual_machine
from perfkitbenchmarker import linux_virtual_machine as linux_vm
from perfkitbenchmarker import virtual_machine
from perfkitbenchmarker import vm_util
from perfkitbenchmarker.gcp import gce_disk
Expand Down Expand Up @@ -215,11 +215,16 @@ def AddMetadata(self, **kwargs):
vm_util.IssueCommand(cmd)


class ContainerizedGceVirtualMachine(GceVirtualMachine,
linux_vm.ContainerizedDebianMixin):
pass


class DebianBasedGceVirtualMachine(GceVirtualMachine,
linux_virtual_machine.DebianMixin):
linux_vm.DebianMixin):
DEFAULT_IMAGE = UBUNTU_IMAGE


class RhelBasedGceVirtualMachine(GceVirtualMachine,
linux_virtual_machine.RhelMixin):
linux_vm.RhelMixin):
DEFAULT_IMAGE = RHEL_IMAGE
168 changes: 158 additions & 10 deletions perfkitbenchmarker/linux_virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
SSH_RETRIES = 10
DEFAULT_SSH_PORT = 22
REMOTE_KEY_PATH = '.ssh/id_rsa'
UBUNTU_DOCKER_IMAGE = 'ubuntu:latest'

FLAGS = flags.FLAGS

Expand Down Expand Up @@ -183,7 +184,8 @@ def OnStartup(self):
@vm_util.Retry(log_errors=False, poll_interval=1)
def WaitForBootCompletion(self):
"""Waits until VM is has booted."""
resp, _ = self.RemoteCommand('hostname', retries=1, suppress_warning=True)
resp, _ = self.RemoteHostCommand('hostname', retries=1,
suppress_warning=True)
if self.bootable_time is None:
self.bootable_time = time.time()
if self.hostname is None:
Expand Down Expand Up @@ -231,15 +233,18 @@ def FormatDisk(self, device_path):
"""Formats a disk attached to the VM."""
fmt_cmd = ('sudo mke2fs -F -E lazy_itable_init=0 -O '
'^has_journal -t ext4 -b 4096 %s' % device_path)
self.RemoteCommand(fmt_cmd)
self.RemoteHostCommand(fmt_cmd)

def MountDisk(self, device_path, mount_path):
"""Mounts a formatted disk in the VM."""
mnt_cmd = ('sudo mkdir -p {1};sudo mount {0} {1};'
'sudo chown -R $USER:$USER {1};').format(device_path, mount_path)
self.RemoteCommand(mnt_cmd)
self.RemoteHostCommand(mnt_cmd)

def RemoteCopy(self, file_path, remote_path='', copy_to=True):
self.RemoteHostCopy(file_path, remote_path, copy_to)

def RemoteHostCopy(self, file_path, remote_path='', copy_to=True):
"""Copies a file to or from the VM.
Args:
Expand Down Expand Up @@ -279,8 +284,19 @@ def RemoteCommand(self, command,
should_log=False, retries=SSH_RETRIES,
ignore_failure=False, login_shell=False,
suppress_warning=False):
return self.RemoteHostCommand(command, should_log, retries,
ignore_failure, login_shell,
suppress_warning)

def RemoteHostCommand(self, command,
should_log=False, retries=SSH_RETRIES,
ignore_failure=False, login_shell=False,
suppress_warning=False):
"""Runs a command on the VM.
This is guaranteed to run on the host VM, whereas RemoteCommand might run
within i.e. a container in the host VM.
Args:
command: A valid bash command.
should_log: A boolean indicating whether the command result should be
Expand Down Expand Up @@ -336,6 +352,9 @@ def RemoteCommand(self, command,
return stdout, stderr

def MoveFile(self, target, source_path, remote_path=''):
self.MoveHostFile(target, source_path, remote_path)

def MoveHostFile(self, target, source_path, remote_path=''):
"""Copies a file from one VM to a target VM.
Args:
Expand All @@ -345,7 +364,7 @@ def MoveFile(self, target, source_path, remote_path=''):
is the home directory.
"""
if not self.has_private_key:
self.PushFile(target.ssh_private_key, REMOTE_KEY_PATH)
self.RemoteHostCopy(target.ssh_private_key, REMOTE_KEY_PATH)
self.has_private_key = True

# TODO(user): For security we may want to include
Expand All @@ -355,13 +374,13 @@ def MoveFile(self, target, source_path, remote_path=''):
# OpenMPI to operate correctly.
remote_location = '%s@%s:%s' % (
target.user_name, target.ip_address, remote_path)
self.RemoteCommand('scp -o StrictHostKeyChecking=no -i %s %s %s' %
(REMOTE_KEY_PATH, source_path, remote_location))
self.RemoteHostCommand('scp -o StrictHostKeyChecking=no -i %s %s %s' %
(REMOTE_KEY_PATH, source_path, remote_location))

def AuthenticateVm(self):
"""Authenticate a remote machine to access all peers."""
self.PushFile(vm_util.GetPrivateKeyPath(),
REMOTE_KEY_PATH)
self.RemoteHostCopy(vm_util.GetPrivateKeyPath(),
REMOTE_KEY_PATH)

def CheckJavaVersion(self):
"""Check the version of java on remote machine.
Expand Down Expand Up @@ -431,7 +450,7 @@ def SetupLocalDisks(self):
"""Performs Linux specific setup of local disks."""
# Some images may automount one local disk, but we don't
# want to fail if this wasn't the case.
self.RemoteCommand('sudo umount /mnt', ignore_failure=True)
self.RemoteHostCommand('sudo umount /mnt', ignore_failure=True)

def _CreateScratchDiskFromDisks(self, disk_spec, disks):
"""Helper method to prepare data disks.
Expand Down Expand Up @@ -480,7 +499,7 @@ def StripeDisks(self, devices, striped_device):
self.Install('mdadm')
stripe_cmd = ('yes | sudo mdadm --create %s --level=stripe --raid-devices='
'%s %s' % (striped_device, len(devices), ' '.join(devices)))
self.RemoteCommand(stripe_cmd)
self.RemoteHostCommand(stripe_cmd)

def BurnCpu(self, burn_cpu_threads=None, burn_cpu_seconds=None):
"""Burns vm cpu for some amount of time and dirty cache.
Expand Down Expand Up @@ -708,3 +727,132 @@ def SetupProxy(self):

if commands:
self.RemoteCommand(";".join(commands))


class ContainerizedDebianMixin(DebianMixin):
"""Class representing a Virtual Machine that runs Remote
Commands within a Docker Container running Ubuntu"""

def OnStartup(self):
"""Initializes docker before proceeding with Startup."""
self.RemoteHostCommand('mkdir -p %s' % vm_util.VM_TMP_DIR)
self.Install('docker')
self.InitDocker()
super(ContainerizedDebianMixin, self).OnStartup()

def InitDocker(self):
"""Initializes the docker container daemon."""
init_docker_cmd = 'sudo docker run -d --net=host '
for sd in self.scratch_disks:
init_docker_cmd += '-v %s:%s ' % (sd.mount_point, sd.mount_point)
init_docker_cmd += '%s sleep infinity ' % UBUNTU_DOCKER_IMAGE

resp, _ = self.RemoteHostCommand(init_docker_cmd)
self.docker_id = resp[:-1]
return self.docker_id

def RemoteCommand(self, command,
should_log=False, retries=SSH_RETRIES,
ignore_failure=False, login_shell=False,
suppress_warning=False):
"""Runs a command inside the container.
Args:
command: A valid bash command.
should_log: A boolean indicating whether the command result should be
logged at the info level. Even if it is false, the results will
still be logged at the debug level.
retries: The maximum number of times RemoteCommand should retry SSHing
when it receives a 255 return code.
ignore_failure: Ignore any failure if set to true.
login_shell: Run command in a login shell.
suppress_warning: Suppress the result logging from IssueCommand when the
return code is non-zero.
Returns:
A tuple of stdout and stderr from running the command.
"""
# Escapes bash sequences
command = command.replace('\'', '\'\\\'\'')

logging.info("Docker running: %s" % command)
command = "sudo docker exec %s bash -c '%s'" % (self.docker_id, command)
return self.RemoteHostCommand(command, should_log, retries,
ignore_failure, login_shell, suppress_warning)

def ContainerCopy(self, host_path, container_path='', copy_to=True):
"""Copies a file from host to container and vice versa.
Does not preserve permissions. If copying from container, host_path
must be a directory.
Args:
host_path: Path to file in the host.
container_path: Optional path of where to copy file on container..
copy_to: True to copy to container, False to copy from container.
Raises:
RemoteExceptionError: If the source container_path is blank.
"""
if copy_to:
file_name = os.path.basename(host_path)

# Default container directory is /
if container_path == '':
container_path = '/%s' % file_name

# Need to ensure container_path is not a directory
if container_path[-1] == '/':
container_path = os.path.join(container_path, file_name)

command = ("cat %s | sudo docker exec -i %s bash -c 'cat > %s'" %
(host_path, self.docker_id, container_path))
self.RemoteHostCommand(command)
else:
if container_path == '':
raise errors.VirtualMachine.RemoteExceptionError('Cannot copy '
'from blank target')
command = "sudo docker cp %s:%s %s" % (self.docker_id,
container_path, host_path)
self.RemoteHostCommand(command)

def RemoteCopy(self, file_path, remote_path='', copy_to=True):
"""Copies a file to or from the container in the remote VM.
Args:
file_path: Local path to file.
remote_path: Optional path of where to copy file inside the container.
copy_to: True to copy to VM, False to copy from VM.
"""
if copy_to:
file_name = os.path.basename(file_path)
middle_dir = vm_util.VM_TMP_DIR
middle_path = os.path.join(middle_dir, file_name)
self.RemoteHostCopy(file_path, middle_path, copy_to)
self.ContainerCopy(middle_path, remote_path, copy_to)
else:
file_name = os.path.basename(remote_path)
middle_dir = vm_util.VM_TMP_DIR
middle_path = os.path.join(middle_dir, file_name)
self.ContainerCopy(middle_dir, remote_path, copy_to)
self.RemoteHostCopy(file_path, middle_path, copy_to)

def MoveFile(self, target, source_path, remote_path=''):
"""Copies a file from one Container in a source VM to
a Container in a target VM.
Args:
target: The target ContainerizedVirtualMachine object.
source_path: The location of the file on the REMOTE machine.
remote_path: The destination of the file on the TARGET machine, default
is the root directory.
"""
file_name = os.path.basename(source_path)
remote_host_dir = vm_util.VM_TMP_DIR
self.ContainerCopy(remote_host_dir, source_path, copy_to=False)
source_host_path = os.path.join(remote_host_dir, file_name)

target_host_dir = vm_util.VM_TMP_DIR
self.MoveHostFile(target, source_host_path, target_host_dir)
target_host_path = os.path.join(target_host_dir, file_name)
target.ContainerCopy(target_host_path, remote_path)
1 change: 1 addition & 0 deletions perfkitbenchmarker/packages/cassandra.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def CheckPrerequisites():
def _Install(vm):
"""Installs Cassandra from a tarball."""
vm.Install('openjdk7')
vm.Install('curl')
vm.RemoteCommand('mkdir {0} && curl -L {1} | '
'tar -C {0} -xzf - --strip-components=1'.format(
CASSANDRA_DIR, CASSANDRA_TAR_URL))
Expand Down
2 changes: 1 addition & 1 deletion perfkitbenchmarker/packages/curl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2014 Google Inc. All rights reserved.
# Copyright 2015 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.
Expand Down
Loading

0 comments on commit 85478a8

Please sign in to comment.