Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows on GCE #338

Merged
merged 1 commit into from
Jun 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,23 @@ $ ./pkb.py --cloud=Azure --machine_type=ExtraSmall --benchmarks=iperf
$ ./pkb.py --cloud=DigitalOcean --machine_type=16gb --benchmarks=iperf
```

HOW TO RUN WINDOWS BENCHMARKS
==================
You must be running on a Windows machine in order to run Windows benchmarks.
Install all dependencies as above and set TrustedHosts to accept all hosts so
that you can open PowerShell sessions with the VMs (both machines having each
other in their TrustedHosts list is neccessary, but not sufficient to issue
remote commands; valid credentials are still required):

```
set-item wsman:\localhost\Client\TrustedHosts -value *
```

Now you can run Windows benchmarks by running with `--os_type=windows`. Windows has a
different set of benchmarks than Linux does. They can be found under
perfkitbenchmarker/windows_benchmarks/. The target VM OS is Windows Server 2012
R2.

HOW TO RUN ALL STANDARD BENCHMARKS
==================
Run without the --benchmarks parameter and every benchmark in the standard set will run serially which can take a couple of hours (alternatively run with --benchmarks="standard_set"). Additionally if you dont specify --cloud=... all benchmarks will run on the Google Cloud Platform.
Expand Down
15 changes: 8 additions & 7 deletions perfkitbenchmarker/benchmark_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"""Benchmark set specific functions and definitions."""

from perfkitbenchmarker import benchmarks
from perfkitbenchmarker import benchmark_spec
from perfkitbenchmarker import flags
from perfkitbenchmarker import windows_benchmarks

FLAGS = flags.FLAGS

Expand Down Expand Up @@ -147,19 +149,18 @@ def GetBenchmarksFromFlags():
benchmark_name][BENCHMARK_LIST])
break

# Create a dictionary of valid benchmark names and modules
# TODO(voellm): I really think the benchmarks package init should
# build the dictionary
valid_benchmarks = {}
for benchmark_module in benchmarks.BENCHMARKS:
valid_benchmarks[benchmark_module.GetInfo()['name']] = benchmark_module
if FLAGS.os_type == benchmark_spec.WINDOWS:
valid_benchmarks = windows_benchmarks.VALID_BENCHMARKS
else:
valid_benchmarks = benchmarks.VALID_BENCHMARKS
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 163 below - can you expand the error message to say "benchmark %s not valid on %platform"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


# create a list of modules to return
benchmark_module_list = []
for benchmark_name in benchmark_names:
if benchmark_name in valid_benchmarks:
benchmark_module_list.append(valid_benchmarks[benchmark_name])
else:
raise ValueError('Invalid benchmark %s' % benchmark_name)
raise ValueError('Benchmark "%s" not valid on os_type "%s"' %
(benchmark_name, FLAGS.os_type))

return benchmark_module_list
18 changes: 14 additions & 4 deletions perfkitbenchmarker/benchmark_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import pickle

from perfkitbenchmarker import disk
from perfkitbenchmarker import errors
from perfkitbenchmarker import flags
from perfkitbenchmarker import network
from perfkitbenchmarker import static_virtual_machine
Expand All @@ -40,7 +41,9 @@
DIGITALOCEAN = 'DigitalOcean'
DEBIAN = 'debian'
RHEL = 'rhel'
WINDOWS = 'windows'
IMAGE = 'image'
WINDOWS_IMAGE = 'windows_image'
MACHINE_TYPE = 'machine_type'
ZONE = 'zone'
VIRTUAL_MACHINE = 'virtual_machine'
Expand All @@ -50,7 +53,8 @@
GCP: {
VIRTUAL_MACHINE: {
DEBIAN: gce_virtual_machine.DebianBasedGceVirtualMachine,
RHEL: gce_virtual_machine.RhelBasedGceVirtualMachine
RHEL: gce_virtual_machine.RhelBasedGceVirtualMachine,
WINDOWS: gce_virtual_machine.WindowsGceVirtualMachine
},
FIREWALL: gce_network.GceFirewall
},
Expand Down Expand Up @@ -84,7 +88,7 @@
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, WINDOWS],
'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, '
Expand Down Expand Up @@ -176,7 +180,8 @@ def Prepare(self):
if self.vms:
prepare_args = [((vm, self.firewall), {}) for vm in self.vms]
vm_util.RunThreaded(self.PrepareVm, prepare_args)
vm_util.GenerateSSHConfig(self.vms)
if FLAGS.os_type != WINDOWS:
vm_util.GenerateSSHConfig(self.vms)

def Delete(self):
if FLAGS.run_stage not in ['all', 'cleanup'] or self.deleted:
Expand Down Expand Up @@ -218,7 +223,12 @@ def CreateVirtualMachine(self, zone):
if vm:
return vm

vm_class = CLASSES[self.cloud][VIRTUAL_MACHINE][FLAGS.os_type]
vm_classes = CLASSES[self.cloud][VIRTUAL_MACHINE]
if FLAGS.os_type not in vm_classes:
raise errors.Error(
'VMs of type %s" are not currently supported on cloud "%s".' %
(FLAGS.os_type, self.cloud))
vm_class = vm_classes[FLAGS.os_type]

vm_spec = virtual_machine.BaseVirtualMachineSpec(
self.project, zone, self.machine_type, self.image)
Expand Down
4 changes: 3 additions & 1 deletion perfkitbenchmarker/benchmarks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@
def _LoadBenchmarks():
return list(import_util.LoadModulesForPath(__path__, __name__))


BENCHMARKS = _LoadBenchmarks()

VALID_BENCHMARKS = {module.BENCHMARK_INFO['name']: module
for module in BENCHMARKS}
18 changes: 14 additions & 4 deletions perfkitbenchmarker/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ def __init__(self, disk_spec):
self.mount_point = disk_spec.mount_point
self.iops = disk_spec.iops

# Windows related attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessarily Windows specific. I think keeping count of the per-instance disks would benefit some Linux implementations also, such as the rather ugly /dev/vdN name allocation in azure_disk.py. If the base class can track this generically, would be nice to document this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more documentation in case we want to use it later.


# The disk number corresponds to the order in which disks were attached to
# the instance. The System Disk has a disk number of 0. Any local disks
# have disk numbers ranging from 1 to the number of local disks on the
# system. Any additional disks that were attached after boot will have
# disk numbers starting at the number of local disks + 1. These disk
# numbers are used in diskpart scripts in order to identify the disks
# that we want to operate on.
self.disk_number = None

@abc.abstractmethod
def Attach(self, vm):
"""Attaches the disk to a VM.
Expand All @@ -77,9 +88,8 @@ def Detach(self):
"""Detaches the disk from a VM."""
pass

@abc.abstractmethod
def GetDevicePath(self):
"""Returns the path to the device inside the VM."""
"""Returns the path to the device inside a Linux VM."""
pass


Expand All @@ -88,13 +98,13 @@ class StripedDisk(BaseDisk):

is_striped = True

def __init__(self, disk_spec, disks, device_path):
def __init__(self, disk_spec, disks, device_path=None):
"""Initializes a StripedDisk object.

Args:
disk_spec: A BaseDiskSpec containing the desired mount point.
disks: A list of BaseDisk objects that constitute the StripedDisk.
device_path: The path of the striped device.
device_path: The path of the striped device in a Linux VM.
"""
super(StripedDisk, self).__init__(disk_spec)
self.disks = disks
Expand Down
68 changes: 51 additions & 17 deletions perfkitbenchmarker/gcp/gce_virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from perfkitbenchmarker import linux_virtual_machine
from perfkitbenchmarker import virtual_machine
from perfkitbenchmarker import vm_util
from perfkitbenchmarker import windows_virtual_machine
from perfkitbenchmarker.gcp import gce_disk
from perfkitbenchmarker.gcp import gce_network
from perfkitbenchmarker.gcp import util
Expand All @@ -47,12 +48,11 @@

FLAGS = flags.FLAGS

BOOT_DISK_SIZE_GB = 10
BOOT_DISK_TYPE = disk.STANDARD
NVME = 'nvme'
SCSI = 'SCSI'
UBUNTU_IMAGE = 'ubuntu-14-04'
RHEL_IMAGE = 'rhel-7'
WINDOWS_IMAGE = 'windows-2012-r2'


class GceVirtualMachine(virtual_machine.BaseVirtualMachine):
Expand All @@ -78,6 +78,7 @@ def __init__(self, vm_spec):
self.boot_disk = gce_disk.GceDisk(
disk_spec, self.name, self.zone, self.project, self.image)
self.max_local_disks = FLAGS.gce_num_local_ssds
self.boot_metadata = {}


@classmethod
Expand Down Expand Up @@ -122,6 +123,8 @@ def _Create(self):
'sshKeys=%s' % tf.name,
'--metadata',
'owner=%s' % FLAGS.owner]
for key, value in self.boot_metadata.iteritems():
create_cmd.append('%s=%s' % (key, value))
ssd_interface_option = NVME if NVME in self.image else SCSI
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think nvme is supported on Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - but the ssd_interface_option will only be "nvme" if the string "nvme" is present in the image name - which it isn't for windows. So this works as it's supposed to

for _ in range(self.max_local_disks):
create_cmd.append('--local-ssd')
Expand Down Expand Up @@ -175,21 +178,26 @@ def CreateScratchDisk(self, disk_spec):
Args:
disk_spec: virtual_machine.BaseDiskSpec object of the disk.
"""
# Get the names for the disk(s) we are going to create.
if disk_spec.disk_type == disk.LOCAL:
new_count = self.local_disk_counter + disk_spec.num_striped_disks
if new_count > self.max_local_disks:
raise errors.Error('Not enough local disks.')
disk_names = ['local-ssd-%d' % i
for i in range(self.local_disk_counter, new_count)]
self.local_disk_counter = new_count
else:
disk_names = ['%s-data-%d-%d' % (self.name, len(self.scratch_disks), i)
for i in range(disk_spec.num_striped_disks)]

# Instantiate the disk(s).
disks = [gce_disk.GceDisk(disk_spec, name, self.zone, self.project)
for name in disk_names]
disks = []

for i in xrange(disk_spec.num_striped_disks):
if disk_spec.disk_type == disk.LOCAL:
name = 'local-ssd-%d' % self.local_disk_counter
data_disk = gce_disk.GceDisk(disk_spec, name, self.zone, self.project)
# Local disk numbers start at 1 (0 is the system disk).
data_disk.disk_number = self.local_disk_counter + 1
self.local_disk_counter += 1
if self.local_disk_counter > self.max_local_disks:
raise errors.Error('Not enough local disks.')
else:
name = '%s-data-%d-%d' % (self.name, len(self.scratch_disks), i)
data_disk = gce_disk.GceDisk(disk_spec, name, self.zone, self.project)
# Remote disk numbers start at 1+max_local_disks (0 is the system disk
# and local disks occupy 1-max_local_disks).
data_disk.disk_number = (self.remote_disk_counter +
1 + self.max_local_disks)
self.remote_disk_counter += 1
disks.append(data_disk)

self._CreateScratchDiskFromDisks(disk_spec, disks)

Expand Down Expand Up @@ -223,3 +231,29 @@ class DebianBasedGceVirtualMachine(GceVirtualMachine,
class RhelBasedGceVirtualMachine(GceVirtualMachine,
linux_virtual_machine.RhelMixin):
DEFAULT_IMAGE = RHEL_IMAGE


class WindowsGceVirtualMachine(GceVirtualMachine,
windows_virtual_machine.WindowsMixin):

DEFAULT_IMAGE = WINDOWS_IMAGE
BOOT_DISK_SIZE_GB = 50
BOOT_DISK_TYPE = disk.REMOTE_SSD

def __init__(self, vm_spec):
super(WindowsGceVirtualMachine, self).__init__(vm_spec)
self.boot_metadata[
'windows-startup-script-ps1'] = windows_virtual_machine.STARTUP_SCRIPT

def _PostCreate(self):
super(WindowsGceVirtualMachine, self)._PostCreate()
reset_password_cmd = [FLAGS.gcloud_path,
'compute',
'reset-windows-password',
'--user',
self.user_name,
self.name]
reset_password_cmd.extend(util.GetDefaultGcloudFlags(self))
stdout, _ = vm_util.IssueRetryableCommand(reset_password_cmd)
response = json.loads(stdout)
self.password = response['password']
27 changes: 10 additions & 17 deletions perfkitbenchmarker/pkb.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
from perfkitbenchmarker import timing_util
from perfkitbenchmarker import version
from perfkitbenchmarker import vm_util
from perfkitbenchmarker import windows_benchmarks
from perfkitbenchmarker.publisher import SampleCollector

STAGE_ALL = 'all'
Expand Down Expand Up @@ -177,17 +178,6 @@ def ValidateBenchmarkInfo(benchmark_info):
return True


def ListUnknownBenchmarks():
"""Identify invalid benchmark names specified in the command line flags."""
valid_benchmark_names = frozenset(benchmark.GetInfo()['name']
for benchmark in benchmarks.BENCHMARKS)
valid_benchmark_sets = frozenset(benchmark_sets.BENCHMARK_SETS)
specified_benchmark_names = frozenset(FLAGS.benchmarks)

return sorted((specified_benchmark_names - valid_benchmark_names) -
valid_benchmark_sets)


def DoPreparePhase(benchmark, name, spec, timer):
"""Performs the Prepare phase of benchmark execution.

Expand Down Expand Up @@ -391,10 +381,9 @@ def RunBenchmarks(publish=True):
run_uri=FLAGS.run_uri)
_LogCommandLineFlags()

unknown_benchmarks = ListUnknownBenchmarks()
if unknown_benchmarks:
logging.error('Unknown benchmark(s) provided: %s',
', '.join(unknown_benchmarks))
if FLAGS.os_type == benchmark_spec.WINDOWS and not vm_util.RunningOnWindows():
logging.error('In order to run benchmarks on Windows VMs, you must be '
'running on Windows.')
return 1

vm_util.SSHKeyGen()
Expand Down Expand Up @@ -444,15 +433,19 @@ def RunBenchmarks(publish=True):
def _GenerateBenchmarkDocumentation():
"""Generates benchmark documentation to show in --help."""
benchmark_docs = []
for benchmark_module in benchmarks.BENCHMARKS:
for benchmark_module in (benchmarks.BENCHMARKS +
windows_benchmarks.BENCHMARKS):
benchmark_info = benchmark_module.BENCHMARK_INFO
vm_count = benchmark_info.get('num_machines') or 'variable'
scratch_disk_str = ''
if benchmark_info.get('scratch_disk'):
scratch_disk_str = ' with scratch volume'

name = benchmark_info['name']
if benchmark_module in windows_benchmarks.BENCHMARKS:
name += ' (Windows)'
benchmark_docs.append('%s: %s (%s VMs%s)' %
(benchmark_info['name'],
(name,
benchmark_info['description'],
vm_count,
scratch_disk_str))
Expand Down
2 changes: 2 additions & 0 deletions perfkitbenchmarker/virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,14 @@ def __init__(self, vm_spec):
self.ip_address = None
self.internal_ip = None
self.user_name = DEFAULT_USERNAME
self.password = None
self.ssh_public_key = vm_util.GetPublicKeyPath()
self.ssh_private_key = vm_util.GetPrivateKeyPath()
self.disk_specs = []
self.scratch_disks = []
self.max_local_disks = 0
self.local_disk_counter = 0
self.remote_disk_counter = 0

@classmethod
def SetVmSpecDefaults(cls, vm_spec):
Expand Down
30 changes: 30 additions & 0 deletions perfkitbenchmarker/windows_benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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.
# 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.
"""Contains benchmark imports and a list of benchmarks.

All modules within this package are considered benchmarks, and are loaded
dynamically. Add non-benchmark code to other packages.
"""

from perfkitbenchmarker import import_util


def _LoadBenchmarks():
return list(import_util.LoadModulesForPath(__path__, __name__))


BENCHMARKS = _LoadBenchmarks()

VALID_BENCHMARKS = {module.BENCHMARK_INFO['name']: module
for module in BENCHMARKS}
Loading