Skip to content

Commit

Permalink
Add VMware ESXi
Browse files Browse the repository at this point in the history
  • Loading branch information
ltrager committed Jul 31, 2018
1 parent fff8355 commit 5a3444c
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__
*.orig
output-qemu
*~
\#*\#
.#*
*.*.swp
*.gz
*.xz
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "vmware-esxi/pyyaml"]
path = vmware-esxi/pyyaml
url = https://github.com/yaml/pyyaml
[submodule "vmware-esxi/oauthlib"]
path = vmware-esxi/oauthlib
url = https://github.com/oauthlib/oauthlib
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Packer MAAS

[Packer](http://packer.io) [templates](https://www.packer.io/docs/templates/index.html), assoicated scripts, and configuration for creating deployable OS images for [MAAS](http://maas.io).
42 changes: 42 additions & 0 deletions vmware-esxi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# VMware ESXi Packer Template for MAAS

## Prerequisites

The VMware ESXi installation ISO must be downloaded manually. You can download it [here.](https://www.vmware.com/go/get-free-esxi)

## Building an image
Your current working directory must be in packer-maas/vmware-esxi, where this file is located. Once in packer-maas/vmware-esxi you can generate an image with:
```
$ sudo packer build -var 'vmware_esxi_iso_path=/path/to/VMware-VMvisor-Installer-6.7.0-8169922.x86_64.iso' vmware-esxi.json
```
Installation is non-interactive.

## Uploading an image to MAAS
```
$ maas $PROFILE boot-resources create name='vmware-esxi-6.7' title='VMware ESXi 6.7' architecture='amd64/generic' filetype='ddgz' content@=vmware-esxi.dd.gz
```

## Customization
The deployment image may be customized by modifying packer-maas/vmware-esxi/http/vmware-esxi-ks.cfg see Installation and Upgrade Scripts in the [VMware ESXi installation and Setup manual](https://docs.vmware.com/en/VMware-vSphere/6.7/vsphere-esxi-67-installation-setup-guide.pdf) for more information.

## Requirements
VMware ESXi has a specific set of [hardware requirements](https://www.vmware.com/resources/compatibility/search.php) which are more stringent then MAAS.

The machine building the deployment image must be a GNU/Linux host with a dual core x86_64 processor supporting hardware virtualization with at least 4GB of RAM and 10GB of disk space available. Additionally the qemu-kvm and qemu-utils packages must be installed on the build system.

### libvirt testing
While VMware ESXi does not support running in any virtual machine it is possible to deploy to one. The libvirt machine must be a KVM instance with at least CPU 2 cores and 4GB of RAM. To give VMware ESXi access to hardware virtualization go into machine settings, CPUs, and select 'copy host CPU configuration.' VMware ESXi has no support for libvirt drivers, instead an emulated IDE disk, and an emulated e1000 NIC must be used.

## Known limitations

### Only the deployment storage device is used
Custom storage configuration is not supported as VMware ESXi has specific requirements for how files are written to the disk. MAAS will extend datastore1 to the full size of the deployment disk. After deployment VMware tools may be used to access the other disks.

### IP Address not assoicated with machine
VMware ESXi connects the physical NIC to a vSphere Standard Switch and creates a new VMkernel adapter for networking. The VMkernel adapter has its own MAC address which is recognized by MAAS as a new device. Custom network configuration is not supported.

### Image fails to build due to qemu-nbd error
If the image fails to build due to a qemu-nbd error try disconnecting the device with
```
$ sudo qemu-nbd -d /dev/nbd4
```
48 changes: 48 additions & 0 deletions vmware-esxi/curtin-hooks
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python3

from curtin.config import load_command_config
from curtin.util import load_command_environment

from subprocess import check_call
import os
import sys
import tempfile


def find_altbootbank_blkdev(target):
with open('/proc/mounts', 'r') as mounts:
for line in mounts.readlines():
mount = line.split()
blkdev = mount[0]
mount_point = mount[1]
if mount_point == target:
return '%s6' % blkdev[0:-1]

print('ERROR: Unable to find /altbootbank block device!')
sys.exit(1)


def mount(blkdev):
mount_point = tempfile.mkdtemp(prefix='curtin-hooks-')
check_call(['mount', blkdev, mount_point])
return mount_point


def umount(blkdev):
check_call(['umount', blkdev])

def main():
state = load_command_environment()
config = load_command_config(None, state)

# Write the MAAS credentials to /altbootbank
altbootbank_blkdev = find_altbootbank_blkdev(state['target'])
altbootbank_mount_point = mount(altbootbank_blkdev)
maas_cfg_path = os.path.join(altbootbank_mount_point, 'maas', 'maas.cfg')
with open(maas_cfg_path, 'w') as f:
f.write(config['cloudconfig']['maas-cloud-config']['content'])
umount(altbootbank_blkdev)


if __name__ =="__main__":
main()
53 changes: 53 additions & 0 deletions vmware-esxi/http/vmware-esxi-ks.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
vmaccepteula

# The root password for the deployed image
rootpw password123!

install --firstdisk

# Sets VMware ESXi licensing key. If not included installs in
# evaluation mode.
# serialnum --esx=XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

network --bootproto=dhcp

%post --interpreter=busybox
# The install reboots by default, when creating an image halt so Packer can
# run post-processors and finish.
halt

%firstboot --interpreter=busybox
# Enable SSH
vim-cmd hostsvc/enable_ssh
vim-cmd hostsvc/start_ssh

# Enable ESXi shell
# vim-cmd hostsvc/enable_esx_shell
# vim-cmd hostsvc/start_esx_shell

# Extend VMFS to the size of the disk. This is normally done by Curtin but
# must be done here due to VMFS requiring proprietary tools.
BLK_DEV=$(esxcli storage vmfs extent list | awk '/datastore1/ { print $4 }')
BLK_DEV="/dev/disks/$BLK_DEV"
printf 'Y\nFix\n' | partedUtil fixGpt $BLK_DEV
PART_START=$(partedUtil get $BLK_DEV | awk '/^3 / { print $2 }')
END_DISK=$(partedUtil getUsableSectors $BLK_DEV | cut -d ' ' -f2)
partedUtil resize $BLK_DEV 3 $PART_START $END_DISK
vmkfstools --growfs $BLK_DEV:3 $BLK_DEV:3

# Temporarily disable firewall so we can communicate with MAAS
esxcli network firewall set --enabled=false

# Copy SSH keys from MAAS
/altbootbank/maas/maas-md-get -c /altbootbank/maas/maas.cfg \
latest/meta-data/public-keys >> /etc/ssh/keys-root/authorized_keys

# Tell MAAS deployment has finished by retrieving user-data. user-data is not
# currently executed.
/altbootbank/maas/maas-md-get -c /altbootbank/maas/maas.cfg latest/user-data

# Reenable firewall
esxcli network firewall set --enabled=true

# Cleanup MAAS first boot files.
#rm -rf /altbootbank/maas
112 changes: 112 additions & 0 deletions vmware-esxi/maas-md-get
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python3

from argparse import ArgumentParser
import http.client
import socket
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

import oauthlib.oauth1 as oauth
import yaml


def authenticate_headers(url, headers, creds, clockskew=0):
"""Update and sign a dict of request headers."""
if creds.get('consumer_key', None) is not None:
timestamp = int(time.time()) + clockskew
client = oauth.Client(
client_key=creds['consumer_key'],
client_secret=creds['consumer_secret'],
resource_owner_key=creds['token_key'],
resource_owner_secret=creds['token_secret'],
signature_method=oauth.SIGNATURE_PLAINTEXT,
timestamp=str(timestamp))
_, signed_headers, _ = client.sign(url)
headers.update(signed_headers)


def geturl(url, creds, headers=None, data=None):
# Takes a dict of creds to be passed through to oauth_headers,
# so it should have consumer_key, token_key, ...
if headers is None:
headers = {}
else:
headers = dict(headers)

clockskew = 0

error = Exception("Unexpected Error")
for naptime in (1, 1, 2, 4, 8, 16, 32):
authenticate_headers(url, headers, creds, clockskew)
try:
req = urllib.request.Request(url=url, data=data, headers=headers)
ret = urllib.request.urlopen(req)
return ret.read().decode()
except urllib.error.HTTPError as exc:
error = exc
if 'date' not in exc.headers:
print("date field not in %d headers" % exc.code)
pass
elif exc.code in (http.client.UNAUTHORIZED, http.client.FORBIDDEN):
date = exc.headers['date']
try:
ret_time = time.mktime(parsedate(date))
clockskew = int(ret_time - time.time())
print("updated clock skew to %d" % clockskew)
except:
print("failed to convert date '%s'" % date)
elif exc.code == http.client.NOT_FOUND:
# Nothing to download.
return ''

except Exception as exc:
error = exc

print("request to %s failed. sleeping %d.: %s" % (url, naptime, error))
time.sleep(naptime)

raise error


def main():
parser = ArgumentParser(
description='Get data from the MAAS metadata server')
parser.add_argument(
'-c', '--config', help='Path to the MAAS metadata credentials file',
required=True)
parser.add_argument('entry', help='The metadata path to get')

args = parser.parse_args()

with open(args.config, 'r') as f:
cfg = yaml.safe_load(f)

if 'datasource' in cfg:
cfg = cfg['datasource']['MAAS']

if 'consumer_secret' not in cfg:
cfg['consumer_secret'] = ''

if cfg['metadata_url'].endswith('/'):
url = '%s%s' % (cfg['metadata_url'], args.entry)
else:
url = '%s/%s' % (cfg['metadata_url'], args.entry)

try:
print(geturl(url, creds=cfg))
except urllib.error.HTTPError as exc:
print("HTTP error [%s]" % exc.code)
sys.exit(1)
except urllib.error.URLError as exc:
print("URL error [%s]" % exc.reason)
sys.exit(1)
except socket.timeout as exc:
print("Socket timeout [%s]" % exc)
sys.exit(1)


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions vmware-esxi/oauthlib
Submodule oauthlib added at f991b5
1 change: 1 addition & 0 deletions vmware-esxi/pyyaml
Submodule pyyaml added at a9c28e
59 changes: 59 additions & 0 deletions vmware-esxi/vmware-esxi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"variables": {
"vmware_esxi_iso_path": "{{env `VMWARE_ESXI_ISO_PATH`}}"
},
"builders": [
{
"type": "qemu",
"communicator": "none",
"iso_url": "{{user `vmware_esxi_iso_path`}}",
"iso_checksum_type": "none",
"accelerator": "kvm",
"boot_wait": "3s",
"boot_command": [
"<enter><wait>",
"<leftShift>O",
" ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/vmware-esxi-ks.cfg<enter>"
],
"disk_interface": "ide",
"disk_size": 10240,
"http_directory": "http",
"format": "raw",
"net_device": "e1000",
"qemuargs": [
[ "-m", "4096m" ],
[ "-cpu", "host" ],
[ "-smp", "2,sockets=2,cores=1,threads=1" ]
]
}
],
"post-processors": [
{
"type": "shell-local",
"inline_shebang": "/bin/bash -e",
"inline": [
"TMP_DIR=$(mktemp -d)",
"qemu-nbd -c /dev/nbd4 -f raw -n output-qemu/packer-qemu",
"sleep 1",
"mount /dev/nbd4p1 $TMP_DIR",
"mkdir -p $TMP_DIR/curtin",
"cp curtin-hooks $TMP_DIR/curtin",
"sync $TMP_DIR/curtin",
"umount $TMP_DIR",
"mount /dev/nbd4p6 $TMP_DIR",
"mkdir -p $TMP_DIR/maas",
"cp maas-md-get $TMP_DIR/maas",
"cp -r pyyaml/lib3/yaml $TMP_DIR/maas",
"cp -r oauthlib/oauthlib $TMP_DIR/maas",
"sync $TMP_DIR/maas",
"umount $TMP_DIR",
"qemu-nbd -d /dev/nbd4",
"rmdir $TMP_DIR"
]
},
{
"type": "compress",
"output": "vmware-esxi.dd.gz"
}
]
}

0 comments on commit 5a3444c

Please sign in to comment.