Initial drop of a very early CLI tool
This commit is contained in:
parent
53e449fc7d
commit
2b81d68f7c
44
README.md
44
README.md
|
@ -1,4 +1,46 @@
|
|||
image-building-poc
|
||||
==================
|
||||
|
||||
Image Building Service Proof of Concept
|
||||
This is a very early demonstration of doing native OS installs inside of Nova.
|
||||
|
||||
To try it out
|
||||
|
||||
1) Install the requirements listed below
|
||||
2) Replace all <FIXME> lines in glance_install.sh with the details of your OpenStack setup
|
||||
3) Uncomment and edit the <FIXME> lines in the *.ks files to set your own root password and VNC password.
|
||||
|
||||
Then run:
|
||||
|
||||
./glance_install.sh fedora-18.ks
|
||||
|
||||
If all goes well, this will install Fedora 18 entirely within a Nova container using
|
||||
the well known network install sources found in the kickstart file.
|
||||
|
||||
You can edit the details of the kickstart file as you wish. The only requirement
|
||||
at the moment is that the install must be "url" based.
|
||||
|
||||
It has only been run against the packstack Folsom distribution running on RHEL6.
|
||||
|
||||
Packstack details:
|
||||
|
||||
https://wiki.openstack.org/wiki/Packstack
|
||||
|
||||
It should work on newer OpenStack releases and more recent OSes including Fedora 17 and 18
|
||||
|
||||
It requires the following OpenStack packages (tested version listed):
|
||||
|
||||
python-glanceclient-0.5.1-1.el6.noarch
|
||||
python-novaclient-2.10.0-2.el6.noarch
|
||||
python-keystoneclient-0.1.3.27-1.el6.noarch
|
||||
|
||||
These are automatically installed when using Packstack
|
||||
|
||||
It also requires:
|
||||
|
||||
syslinux
|
||||
qemu-img
|
||||
|
||||
And benefits from having "vncviewer" from the tigervnc package.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,502 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import guestfs
|
||||
import pycurl
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
import re
|
||||
from tempfile import mkdtemp, NamedTemporaryFile, TemporaryFile
|
||||
from glanceclient import client as glance_client
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from novaclient.v1_1 import client as nova_client
|
||||
from time import sleep, gmtime, strftime
|
||||
from ping import do_one
|
||||
|
||||
|
||||
### Utility functions borrowed from Oz and lightly modified
|
||||
def executable_exists(program):
|
||||
"""
|
||||
Function to find out whether an executable exists in the PATH
|
||||
of the user. If so, the absolute path to the executable is returned.
|
||||
If not, an exception is raised.
|
||||
"""
|
||||
def is_exe(fpath):
|
||||
"""
|
||||
Helper method to check if a file exists and is executable
|
||||
"""
|
||||
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
if program is None:
|
||||
raise Exception("Invalid program name passed")
|
||||
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
return program
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
exe_file = os.path.join(path, program)
|
||||
if is_exe(exe_file):
|
||||
return exe_file
|
||||
|
||||
raise Exception("Could not find %s" % (program))
|
||||
|
||||
|
||||
def subprocess_check_output(*popenargs, **kwargs):
|
||||
"""
|
||||
Function to call a subprocess and gather the output.
|
||||
Addresses a lack of check_output() prior to Python 2.7
|
||||
"""
|
||||
if 'stdout' in kwargs:
|
||||
raise ValueError('stdout argument not allowed, it will be overridden.')
|
||||
if 'stderr' in kwargs:
|
||||
raise ValueError('stderr argument not allowed, it will be overridden.')
|
||||
|
||||
executable_exists(popenargs[0][0])
|
||||
|
||||
# NOTE: it is very, very important that we use temporary files for
|
||||
# collecting stdout and stderr here. There is a nasty bug in python
|
||||
# subprocess; if your process produces more than 64k of data on an fd that
|
||||
# is using subprocess.PIPE, the whole thing will hang. To avoid this, we
|
||||
# use temporary fds to capture the data
|
||||
stdouttmp = TemporaryFile()
|
||||
stderrtmp = TemporaryFile()
|
||||
|
||||
process = subprocess.Popen(stdout=stdouttmp, stderr=stderrtmp, *popenargs,
|
||||
**kwargs)
|
||||
process.communicate()
|
||||
retcode = process.poll()
|
||||
|
||||
stdouttmp.seek(0, 0)
|
||||
stdout = stdouttmp.read()
|
||||
stdouttmp.close()
|
||||
|
||||
stderrtmp.seek(0, 0)
|
||||
stderr = stderrtmp.read()
|
||||
stderrtmp.close()
|
||||
|
||||
if retcode:
|
||||
cmd = ' '.join(*popenargs)
|
||||
raise Exception("'%s' failed(%d): %s" % (cmd, retcode, stderr), retcode)
|
||||
return (stdout, stderr, retcode)
|
||||
|
||||
|
||||
def http_download_file(url, filename):
|
||||
"""
|
||||
Function to download a file from url to filename
|
||||
"""
|
||||
|
||||
def _data(buf):
|
||||
"""
|
||||
Function that is called back from the pycurl perform() method to
|
||||
actually write data to disk.
|
||||
"""
|
||||
os.write(fd, buf)
|
||||
|
||||
fd = os.open(filename,os.O_CREAT | os.O_WRONLY | os.O_TRUNC)
|
||||
|
||||
try:
|
||||
c = pycurl.Curl()
|
||||
c.setopt(c.URL, url)
|
||||
c.setopt(c.CONNECTTIMEOUT, 15)
|
||||
c.setopt(c.WRITEFUNCTION, _data)
|
||||
c.setopt(c.FOLLOWLOCATION, 1)
|
||||
c.perform()
|
||||
c.close()
|
||||
finally:
|
||||
os.close(fd)
|
||||
### End of borrowed Oz functions
|
||||
|
||||
|
||||
### Borrowed from Image Factory OpenStack plugin
|
||||
def glance_upload(image_filename, creds = {'auth_url': None, 'password': None, 'strategy': 'noauth', 'tenant': None, 'username': None},
|
||||
glance_url = None, token = None, name = 'Factory Test Image', disk_format = 'raw'):
|
||||
|
||||
k = keystone_client.Client(username=creds['username'], password=creds['password'], tenant_name=creds['tenant'], auth_url=creds['auth_url'])
|
||||
|
||||
if (k.authenticate()):
|
||||
#Connect to glance to upload the image
|
||||
glance = glance_client.Client("1", endpoint=glance_url, token=k.auth_token)
|
||||
image_data = open(image_filename, "r")
|
||||
image_meta = {'container_format': 'bare',
|
||||
'disk_format': disk_format,
|
||||
'is_public': True,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
'name': name,
|
||||
'data': image_data,
|
||||
'properties': {'distro': 'rhel'}}
|
||||
try:
|
||||
image = glance.images.create(name=name)
|
||||
print "Uploading to Glance"
|
||||
image.update(**image_meta)
|
||||
return image.id
|
||||
except Exception, e:
|
||||
raise
|
||||
else:
|
||||
raise Exception("Unable to authenticate into glance")
|
||||
|
||||
def ks_extract_bits(ksfile):
|
||||
# I briefly looked at pykickstart but it more or less requires you know the version of the
|
||||
# format you wish to use
|
||||
# The approach below actually works as far back as RHEL5 and as recently as F18
|
||||
|
||||
f = open(ksfile)
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
|
||||
install_url = None
|
||||
vnc_password = None
|
||||
poweroff = False
|
||||
|
||||
for line in lines:
|
||||
# Install URL lines look like this
|
||||
# url --url=http://download.com/released/RHEL-5-Server/U9/x86_64/os/
|
||||
m = re.match("url.*--url=(\S+)", line)
|
||||
if m and len(m.groups()) == 1:
|
||||
install_url = m.group(1)
|
||||
continue
|
||||
|
||||
# VNC console lines look like this
|
||||
# Inisist on a password being set
|
||||
# vnc --password=vncpasswd
|
||||
m = re.match("vnc.*--password=(\S+)", line)
|
||||
if m and len(m.groups()) == 1:
|
||||
vnc_password = m.group(1)
|
||||
continue
|
||||
|
||||
# SSH console lines look like this
|
||||
# Inisist on a password being set
|
||||
# ssh --password=sshpasswd
|
||||
m = re.match("ssh.*--password=(\S+)", line)
|
||||
if m and len(m.groups()) == 1:
|
||||
vnc_password = m.group(1)
|
||||
continue
|
||||
|
||||
# We require a poweroff after install to detect completion - look for the line
|
||||
if re.match("poweroff", line):
|
||||
poweroff=True
|
||||
continue
|
||||
|
||||
return (install_url, vnc_password, poweroff)
|
||||
|
||||
def generate_blank_syslinux():
|
||||
# Generate syslinux.qcow2 in working directory if it isn't already there
|
||||
if os.path.isfile("./syslinux.qcow2"):
|
||||
print "Found a syslinux.qcow2 image in the working directory - using it"
|
||||
return
|
||||
|
||||
print "Generating an empty bootable syslinux image as ./syslinux.qcow2"
|
||||
raw_fs_image = NamedTemporaryFile(delete=False)
|
||||
raw_image_name = raw_fs_image.name
|
||||
try:
|
||||
output_image_name = "./syslinux.qcow2"
|
||||
|
||||
# 200 MB sparse file
|
||||
outsize = 1024 * 1024 * 200
|
||||
raw_fs_image.truncate(outsize)
|
||||
raw_fs_image.close()
|
||||
|
||||
# Partition, format and add DOS MBR
|
||||
g = guestfs.GuestFS()
|
||||
g.add_drive(raw_image_name)
|
||||
g.launch()
|
||||
g.part_disk("/dev/sda","msdos")
|
||||
g.part_set_mbr_id("/dev/sda",1,0xb)
|
||||
g.mkfs("vfat", "/dev/sda1")
|
||||
g.part_set_bootable("/dev/sda", 1, 1)
|
||||
dosmbr = open("/usr/share/syslinux/mbr.bin").read()
|
||||
ws = g.pwrite_device("/dev/sda", dosmbr, 0)
|
||||
if ws != len(dosmbr):
|
||||
raise Exception("Failed to write entire MBR")
|
||||
g.sync()
|
||||
g.close()
|
||||
|
||||
# Install syslinux - this is the ugly root-requiring part
|
||||
gotloop = False
|
||||
for n in range(4):
|
||||
# If this has a nonzero return code we will take the exception
|
||||
(stdout, stderr, retcode) = subprocess_check_output(["losetup","-f"])
|
||||
loopdev = stdout.rstrip()
|
||||
# Race - Try it a few times and then give up
|
||||
try:
|
||||
subprocess_check_output(["losetup",loopdev,raw_image_name])
|
||||
except:
|
||||
sleep(1)
|
||||
continue
|
||||
gotloop = True
|
||||
break
|
||||
|
||||
if not gotloop:
|
||||
raise Exception("Failed to setup loopback")
|
||||
|
||||
loopbase = os.path.basename(loopdev)
|
||||
|
||||
try:
|
||||
subprocess_check_output(["kpartx","-a",loopdev])
|
||||
# On RHEL6 there seems to be a short delay before the mappings actually show up
|
||||
sleep(5)
|
||||
subprocess_check_output(["syslinux", "/dev/mapper/%sp1" % (loopbase)])
|
||||
subprocess_check_output(["kpartx", "-d", loopdev])
|
||||
subprocess_check_output(["losetup", "-d", loopdev])
|
||||
except:
|
||||
print "Exception while executing syslinux install commands."
|
||||
raise
|
||||
|
||||
try:
|
||||
subprocess_check_output(["qemu-img","convert","-c","-O","qcow2",raw_image_name,output_image_name])
|
||||
except:
|
||||
print "Exception while converting image to qcow2"
|
||||
|
||||
finally:
|
||||
pass
|
||||
# Leave a mess for debugging for now
|
||||
#os.remove(raw_image_name)
|
||||
|
||||
|
||||
def generate_boot_content(url, dest_dir):
|
||||
"""
|
||||
Insert kernel, ramdisk and syslinux.cfg file in dest_dir
|
||||
source from url
|
||||
"""
|
||||
# TODO: Add support for something other than rhel5
|
||||
|
||||
kernel_url = url + "images/pxeboot/vmlinuz"
|
||||
kernel_dest = os.path.join(dest_dir,"vmlinuz")
|
||||
http_download_file(kernel_url, kernel_dest)
|
||||
|
||||
initrd_url = url + "images/pxeboot/initrd.img"
|
||||
initrd_dest = os.path.join(dest_dir,"initrd.img")
|
||||
http_download_file(initrd_url, initrd_dest)
|
||||
|
||||
syslinux_conf="""default customhd
|
||||
timeout 30
|
||||
prompt 1
|
||||
label customhd
|
||||
kernel vmlinuz
|
||||
append initrd=initrd.img ks=http://169.254.169.254/latest/user-data
|
||||
"""
|
||||
|
||||
f = open(os.path.join(dest_dir, "syslinux.cfg"),"w")
|
||||
f.write(syslinux_conf)
|
||||
f.close()
|
||||
|
||||
|
||||
def copy_content_to_image(contentdir, target_image):
|
||||
g = guestfs.GuestFS()
|
||||
g.add_drive(target_image)
|
||||
g.launch()
|
||||
g.mount_options ("", "/dev/sda1", "/")
|
||||
for filename in os.listdir(contentdir):
|
||||
g.upload(os.path.join(contentdir,filename),"/" + filename)
|
||||
g.sync()
|
||||
g.close()
|
||||
|
||||
def wait_for_shutoff(instance, nova):
|
||||
for i in range(1200):
|
||||
status = nova.servers.get(instance.id).status
|
||||
if status == "SHUTOFF":
|
||||
print "Instance has entered SHUTOFF state"
|
||||
return instance
|
||||
if i % 10 == 0:
|
||||
print "Waiting for instance status SHUTOFF - current status (%s): %d/1200" % (status, i)
|
||||
sleep(1)
|
||||
|
||||
def wait_for_noping(instance, nova, vncpassword):
|
||||
# pre-grizzly releases are slow to notice an instance is shut off - see thread:
|
||||
# http://lists.openstack.org/pipermail/openstack-dev/2013-January/004501.html
|
||||
#
|
||||
# This is an imperfect workaround using pings
|
||||
|
||||
from ping import do_one
|
||||
print "Warning - using ping to monitor progress - this is a crude shutdown detection scheme"
|
||||
|
||||
# It is unclear where in the instance lifecycle this first becomes available
|
||||
# Just try for a few minutes then give up
|
||||
instance_ip = None
|
||||
for i in range(18):
|
||||
try:
|
||||
instance = nova.servers.get(instance.id)
|
||||
print "Instance status: %s" % (instance.status)
|
||||
# First IP for the first key returned in the networks dict
|
||||
instance_ip = instance.networks[instance.networks.keys()[0]][0]
|
||||
break
|
||||
except:
|
||||
sleep(10)
|
||||
pass
|
||||
|
||||
if not instance_ip:
|
||||
raise Exception("Unable to determine instance IP after 3 minutes")
|
||||
|
||||
print "Using instance ip: %s" % (instance_ip)
|
||||
print "Waiting 3 minutes for instance to respond to pings"
|
||||
# First wait up to 3 minutes for ping to _start_ replying
|
||||
started = False
|
||||
for i in range(18):
|
||||
print '.',
|
||||
sys.stdout.flush()
|
||||
if do_one(instance_ip, 10):
|
||||
started = True
|
||||
break
|
||||
print ''
|
||||
|
||||
if not started:
|
||||
raise Exception("Instance at IP (%s) failed to start after 3 minutes." % (instance_ip) )
|
||||
|
||||
print "Instance responding to pings - waiting up to 20 minutes for it to stop"
|
||||
# TODO: Automate this using subprocess
|
||||
if vnc_password:
|
||||
print "Kickstart file contains a vnc directive with a password"
|
||||
print "You should be able to view Anaconda progress with the following command:"
|
||||
print "$ vncviewer %s:1" % (instance_ip)
|
||||
print "When prompted for a password enter: %s" % (vnc_password)
|
||||
print "Note that it may take a few mintues for the server to become available"
|
||||
# Now wait for up to 20 minutes for it to stop ping replies for at least 30 seconds
|
||||
misses=0
|
||||
for i in range(120):
|
||||
print '.',
|
||||
sys.stdout.flush()
|
||||
if do_one(instance_ip, 10):
|
||||
misses=0
|
||||
sleep(10)
|
||||
else:
|
||||
print '-',
|
||||
sys.stdout.flush()
|
||||
misses += 1
|
||||
if misses == 4:
|
||||
break
|
||||
print ''
|
||||
|
||||
if misses != 4:
|
||||
print "Instance still pinging after 20 seconds - Assuming install failure"
|
||||
return
|
||||
|
||||
print "Instance has stopped responding to ping for at least 30 seconds - assuming install is complete"
|
||||
return instance
|
||||
|
||||
|
||||
|
||||
def launch_and_wait(image_id, ks_file, creds, vnc_password):
|
||||
userdata = open(ks_file)
|
||||
nova = nova_client.Client(creds['username'], creds['password'], creds['username'],
|
||||
auth_url=creds['auth_url'], insecure=True)
|
||||
instance = nova.servers.create("self-install instance", image_id, 2, userdata=userdata, meta={})
|
||||
print "Started instance id (%s)" % (instance.id)
|
||||
userdata.close()
|
||||
|
||||
#noping for Folsom - shutoff for newer
|
||||
#result = wait_for_shutoff(instance, nova)
|
||||
result = wait_for_noping(instance, nova, vnc_password)
|
||||
|
||||
if not result:
|
||||
raise Exception("Timeout while waiting for install to finish")
|
||||
|
||||
return result
|
||||
|
||||
parser = argparse.ArgumentParser(description='Launch and snapshot a kickstart install using syslinux and Glance')
|
||||
parser.add_argument('--auth-url', dest='auth_url', required=True,
|
||||
help='URL for keystone authorization')
|
||||
parser.add_argument('--username', dest='username', required=True,
|
||||
help='username for keystone authorization')
|
||||
parser.add_argument('--tenant', dest='tenant', required=True,
|
||||
help='tenant for keystone authorization')
|
||||
parser.add_argument('--password', dest='password', required=True,
|
||||
help='password for keystone authorization')
|
||||
parser.add_argument('--glance-url', dest='glance_url', required=True,
|
||||
help='URL for glance service')
|
||||
parser.add_argument('--install-tree-url', dest='install_tree_url',
|
||||
help='URL for preferred network install tree (optional)')
|
||||
parser.add_argument('--image-name', dest='image_name', default='',
|
||||
help='name to assign newly created image (optional)')
|
||||
parser.add_argument('ks_file',
|
||||
help='kickstart file to use for install')
|
||||
args = parser.parse_args()
|
||||
|
||||
(install_tree_url, vnc_password, poweroff) = ks_extract_bits(args.ks_file)
|
||||
|
||||
if args.install_tree_url:
|
||||
# Allow the specified tree to override anything extracted above
|
||||
install_tree_url = args['install_tree_url']
|
||||
|
||||
if args.image_name:
|
||||
image_name = args.image_name
|
||||
else:
|
||||
image_name = "Image from ks file: %s - Date: %s" % (args.ks_file, strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))
|
||||
|
||||
error = False
|
||||
|
||||
if not install_tree_url:
|
||||
# Not provided on command line and not extracted
|
||||
print "ERROR: no install tree URL specified and could not extract one from the kickstart"
|
||||
error = True
|
||||
|
||||
if not poweroff:
|
||||
print "ERROR: supplied kickstart file must contain a 'poweroff' line"
|
||||
error = True
|
||||
|
||||
if error:
|
||||
sys.exit(1)
|
||||
|
||||
# Artifact of borrowing factory code - pass this as a dict
|
||||
creds = { 'username': args.username, 'tenant': args.tenant, 'password': args.password, 'auth_url': args.auth_url }
|
||||
|
||||
# Generate "blank" syslinux bootable mini-image
|
||||
# This is the only step that strictly requires root access due to the need
|
||||
# for a loopback mount to install the bootloader
|
||||
generate_blank_syslinux()
|
||||
|
||||
# Take a copy of it
|
||||
modified_image = "./syslinux_modified_%s.qcow2" % os.getpid()
|
||||
shutil.copy("./syslinux.qcow2",modified_image)
|
||||
|
||||
# Generate the content to put into the image
|
||||
tmp_content_dir = mkdtemp()
|
||||
print "Collecting boot content for auto-install image"
|
||||
generate_boot_content(install_tree_url, tmp_content_dir)
|
||||
|
||||
# Copy in the kernel, initrd and conf files into the blank boot stub using libguestfs
|
||||
print "Copying boot content into a bootable syslinux image"
|
||||
copy_content_to_image(tmp_content_dir, modified_image)
|
||||
|
||||
# Upload the resulting image to glance
|
||||
print "Uploading image to glance"
|
||||
image_id = glance_upload(modified_image, creds = creds, glance_url = args.glance_url,
|
||||
name = "INSTALL for: %s" % (image_name), disk_format='qcow2')
|
||||
|
||||
print "Uploaded successfully as glance image (%s)" % (image_id)
|
||||
# Launch the image with the provided ks.cfg as the user data
|
||||
# Optionally - spawn a vncviewer to watch the install graphically
|
||||
# Poll on image status until it is SHUTDOWN or timeout
|
||||
print "Launching install image"
|
||||
installed_instance = launch_and_wait(image_id, args.ks_file, creds, vnc_password)
|
||||
|
||||
# Take a snapshot of the now safely shutdown image
|
||||
print "Taking snapshot of completed install"
|
||||
finished_image_id = installed_instance.create_image(image_name)
|
||||
|
||||
print "Finished image snapshot ID is: %s" % (finished_image_id)
|
||||
print "Finished image name is: %s" % (image_name)
|
||||
#print "Cleaning up"
|
||||
#print "Removing temp content dir"
|
||||
#shutil.rmtree(tmp_content_dir)
|
||||
#print "Removing install image"
|
||||
##TODO:Note that thie is actually cacheable on a per-os-version basis
|
||||
#os.remove(modified_image)
|
|
@ -0,0 +1,36 @@
|
|||
url --url=http://mirrors.kernel.org/fedora/releases/17/Fedora/x86_64/os/
|
||||
# Without the Everything repo, we cannot install cloud-init
|
||||
#repo --name="fedora-everything" --baseurl=http://mirrors.kernel.org/fedora/releases/17/Everything/x86_64/os/
|
||||
repo --name="fedora-everything" --mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=fedora-17&arch=x86_64
|
||||
install
|
||||
graphical
|
||||
## <FIXME>
|
||||
#vnc --password=ozrootpw
|
||||
keyboard us
|
||||
lang en_US.UTF-8
|
||||
skipx
|
||||
network --device eth0 --bootproto dhcp
|
||||
## <FIXME>
|
||||
#rootpw ozrootpw
|
||||
firewall --disabled
|
||||
authconfig --enableshadow --enablemd5
|
||||
selinux --enforcing
|
||||
timezone --utc America/New_York
|
||||
bootloader --location=mbr
|
||||
zerombr
|
||||
clearpart --all --drives=vda
|
||||
|
||||
part biosboot --fstype=biosboot --size=1
|
||||
part /boot --fstype ext4 --size=200 --ondisk=vda
|
||||
part pv.2 --size=1 --grow --ondisk=vda
|
||||
volgroup VolGroup00 --pesize=32768 pv.2
|
||||
logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=768 --grow --maxsize=1536
|
||||
logvol / --fstype ext4 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow
|
||||
poweroff
|
||||
|
||||
bootloader --location=mbr --timeout=5 --append="rhgb quiet"
|
||||
|
||||
%packages
|
||||
@base
|
||||
|
||||
%end
|
|
@ -0,0 +1,35 @@
|
|||
url --url=http://mirrors.kernel.org/fedora/releases/18/Fedora/x86_64/os/
|
||||
# Without the Everything repo, we cannot install cloud-init
|
||||
#repo --name="fedora-everything" --baseurl=http://mirrors.kernel.org/fedora/releases/18/Everything/x86_64/os/
|
||||
repo --name="fedora-everything" --mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=fedora-18&arch=x86_64
|
||||
install
|
||||
graphical
|
||||
#vnc --password=<FIXME>
|
||||
text
|
||||
keyboard us
|
||||
lang en_US.UTF-8
|
||||
skipx
|
||||
network --device eth0 --bootproto dhcp
|
||||
#rootpw <FIXME>
|
||||
firewall --disabled
|
||||
authconfig --enableshadow --enablemd5
|
||||
selinux --enforcing
|
||||
timezone --utc America/New_York
|
||||
bootloader --location=mbr
|
||||
zerombr
|
||||
clearpart --all --drives=vda
|
||||
|
||||
part biosboot --fstype=biosboot --size=1
|
||||
part /boot --fstype ext4 --size=200 --ondisk=vda
|
||||
part pv.2 --size=1 --grow --ondisk=vda
|
||||
volgroup VolGroup00 --pesize=32768 pv.2
|
||||
logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=768 --grow --maxsize=1536
|
||||
logvol / --fstype ext4 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow
|
||||
poweroff
|
||||
|
||||
bootloader --location=mbr --timeout=5 --append="rhgb quiet"
|
||||
|
||||
%packages
|
||||
@core
|
||||
|
||||
%end
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
./create_glance_image.py --username <FIXME> --tenant <FIXME> --password <FIXME> --auth-url <FIXME> \
|
||||
--glance-url <FIXME> $1
|
||||
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
A pure python ping implementation using raw socket.
|
||||
|
||||
|
||||
Note that ICMP messages can only be sent from processes running as root.
|
||||
|
||||
|
||||
Derived from ping.c distributed in Linux's netkit. That code is
|
||||
copyright (c) 1989 by The Regents of the University of California.
|
||||
That code is in turn derived from code written by Mike Muuss of the
|
||||
US Army Ballistic Research Laboratory in December, 1983 and
|
||||
placed in the public domain. They have my thanks.
|
||||
|
||||
Bugs are naturally mine. I'd be glad to hear about them. There are
|
||||
certainly word - size dependenceies here.
|
||||
|
||||
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/~mdc/>.
|
||||
Distributable under the terms of the GNU General Public License
|
||||
version 2. Provided with no warranties of any sort.
|
||||
|
||||
Original Version from Matthew Dixon Cowles:
|
||||
-> ftp://ftp.visi.com/users/mdc/ping.py
|
||||
|
||||
Rewrite by Jens Diemer:
|
||||
-> http://www.python-forum.de/post-69122.html#69122
|
||||
|
||||
|
||||
Revision history
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
March 11, 2010
|
||||
changes by Samuel Stauffer:
|
||||
- replaced time.clock with default_timer which is set to
|
||||
time.clock on windows and time.time on other systems.
|
||||
|
||||
May 30, 2007
|
||||
little rewrite by Jens Diemer:
|
||||
- change socket asterisk import to a normal import
|
||||
- replace time.time() with time.clock()
|
||||
- delete "return None" (or change to "return" only)
|
||||
- in checksum() rename "str" to "source_string"
|
||||
|
||||
November 22, 1997
|
||||
Initial hack. Doesn't do much, but rather than try to guess
|
||||
what features I (or others) will want in the future, I've only
|
||||
put in what I need now.
|
||||
|
||||
December 16, 1997
|
||||
For some reason, the checksum bytes are in the wrong order when
|
||||
this is run under Solaris 2.X for SPARC but it works right under
|
||||
Linux x86. Since I don't know just what's wrong, I'll swap the
|
||||
bytes always and then do an htons().
|
||||
|
||||
December 4, 2000
|
||||
Changed the struct.pack() calls to pack the checksum and ID as
|
||||
unsigned. My thanks to Jerome Poincheval for the fix.
|
||||
|
||||
|
||||
Last commit info:
|
||||
~~~~~~~~~~~~~~~~~
|
||||
$LastChangedDate: $
|
||||
$Rev: $
|
||||
$Author: $
|
||||
"""
|
||||
|
||||
|
||||
import os, sys, socket, struct, select, time
|
||||
|
||||
if sys.platform == "win32":
|
||||
# On Windows, the best timer is time.clock()
|
||||
default_timer = time.clock
|
||||
else:
|
||||
# On most other platforms the best timer is time.time()
|
||||
default_timer = time.time
|
||||
|
||||
# From /usr/include/linux/icmp.h; your milage may vary.
|
||||
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.
|
||||
|
||||
|
||||
def checksum(source_string):
|
||||
"""
|
||||
I'm not too confident that this is right but testing seems
|
||||
to suggest that it gives the same answers as in_cksum in ping.c
|
||||
"""
|
||||
sum = 0
|
||||
countTo = (len(source_string)/2)*2
|
||||
count = 0
|
||||
while count<countTo:
|
||||
thisVal = ord(source_string[count + 1])*256 + ord(source_string[count])
|
||||
sum = sum + thisVal
|
||||
sum = sum & 0xffffffff # Necessary?
|
||||
count = count + 2
|
||||
|
||||
if countTo<len(source_string):
|
||||
sum = sum + ord(source_string[len(source_string) - 1])
|
||||
sum = sum & 0xffffffff # Necessary?
|
||||
|
||||
sum = (sum >> 16) + (sum & 0xffff)
|
||||
sum = sum + (sum >> 16)
|
||||
answer = ~sum
|
||||
answer = answer & 0xffff
|
||||
|
||||
# Swap bytes. Bugger me if I know why.
|
||||
answer = answer >> 8 | (answer << 8 & 0xff00)
|
||||
|
||||
return answer
|
||||
|
||||
|
||||
def receive_one_ping(my_socket, ID, timeout):
|
||||
"""
|
||||
receive the ping from the socket.
|
||||
"""
|
||||
timeLeft = timeout
|
||||
while True:
|
||||
startedSelect = default_timer()
|
||||
whatReady = select.select([my_socket], [], [], timeLeft)
|
||||
howLongInSelect = (default_timer() - startedSelect)
|
||||
if whatReady[0] == []: # Timeout
|
||||
return
|
||||
|
||||
timeReceived = default_timer()
|
||||
recPacket, addr = my_socket.recvfrom(1024)
|
||||
icmpHeader = recPacket[20:28]
|
||||
type, code, checksum, packetID, sequence = struct.unpack(
|
||||
"bbHHh", icmpHeader
|
||||
)
|
||||
if packetID == ID:
|
||||
bytesInDouble = struct.calcsize("d")
|
||||
timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
|
||||
return timeReceived - timeSent
|
||||
|
||||
timeLeft = timeLeft - howLongInSelect
|
||||
if timeLeft <= 0:
|
||||
return
|
||||
|
||||
|
||||
def send_one_ping(my_socket, dest_addr, ID):
|
||||
"""
|
||||
Send one ping to the given >dest_addr<.
|
||||
"""
|
||||
dest_addr = socket.gethostbyname(dest_addr)
|
||||
|
||||
# Header is type (8), code (8), checksum (16), id (16), sequence (16)
|
||||
my_checksum = 0
|
||||
|
||||
# Make a dummy heder with a 0 checksum.
|
||||
header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
|
||||
bytesInDouble = struct.calcsize("d")
|
||||
data = (192 - bytesInDouble) * "Q"
|
||||
data = struct.pack("d", default_timer()) + data
|
||||
|
||||
# Calculate the checksum on the data and the dummy header.
|
||||
my_checksum = checksum(header + data)
|
||||
|
||||
# Now that we have the right checksum, we put that in. It's just easier
|
||||
# to make up a new header than to stuff it into the dummy.
|
||||
header = struct.pack(
|
||||
"bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1
|
||||
)
|
||||
packet = header + data
|
||||
my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1
|
||||
|
||||
|
||||
def do_one(dest_addr, timeout):
|
||||
"""
|
||||
Returns either the delay (in seconds) or none on timeout.
|
||||
"""
|
||||
icmp = socket.getprotobyname("icmp")
|
||||
try:
|
||||
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
|
||||
except socket.error, (errno, msg):
|
||||
if errno == 1:
|
||||
# Operation not permitted
|
||||
msg = msg + (
|
||||
" - Note that ICMP messages can only be sent from processes"
|
||||
" running as root."
|
||||
)
|
||||
raise socket.error(msg)
|
||||
raise # raise the original error
|
||||
|
||||
my_ID = os.getpid() & 0xFFFF
|
||||
|
||||
send_one_ping(my_socket, dest_addr, my_ID)
|
||||
delay = receive_one_ping(my_socket, my_ID, timeout)
|
||||
|
||||
my_socket.close()
|
||||
return delay
|
||||
|
||||
|
||||
def verbose_ping(dest_addr, timeout = 2, count = 4):
|
||||
"""
|
||||
Send >count< ping to >dest_addr< with the given >timeout< and display
|
||||
the result.
|
||||
"""
|
||||
for i in xrange(count):
|
||||
print "ping %s..." % dest_addr,
|
||||
try:
|
||||
delay = do_one(dest_addr, timeout)
|
||||
except socket.gaierror, e:
|
||||
print "failed. (socket error: '%s')" % e[1]
|
||||
break
|
||||
|
||||
if delay == None:
|
||||
print "failed. (timeout within %ssec.)" % timeout
|
||||
else:
|
||||
delay = delay * 1000
|
||||
print "get ping in %0.4fms" % delay
|
||||
print
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
verbose_ping("heise.de")
|
||||
verbose_ping("google.com")
|
||||
verbose_ping("a-test-url-taht-is-not-available.com")
|
||||
verbose_ping("192.168.1.1")
|
Loading…
Reference in New Issue