Skip to content
Open
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
22 changes: 22 additions & 0 deletions image_creator/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Vangelis Koukis <vkoukis@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Various constants used throughout snf-image-creator"""

DEFAULT_LOGFILE = "/var/log/snf-image-creator.log"

# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
206 changes: 125 additions & 81 deletions image_creator/dialog_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2015 GRNET S.A.
# Copyright (C) 2015 Vangelis Koukis <vkoukis@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -16,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""This module is the entrance point for the dialog-based version of the
"""This module is the entry point for the dialog-based version of the
snf-image-creator program. The main function will create a dialog where the
user is asked if he wants to use the program in expert or wizard mode.
"""
Expand All @@ -25,14 +26,18 @@
import sys
import os
import signal
import optparse
import argparse
import types
import time
import termios
import traceback
import tempfile
import logging

from image_creator import __version__ as version
from image_creator.util import FatalError
from image_creator import constants
from image_creator.log import SetupLogging
from image_creator.util import FatalError, ensure_root
from image_creator.output.cli import SimpleOutput
from image_creator.output.dialog import GaugeOutput
from image_creator.output.composite import CompositeOutput
Expand All @@ -45,14 +50,16 @@

PROGNAME = os.path.basename(sys.argv[0])

log = logging.getLogger(__name__)

def create_image(d, media, out, tmp, snapshot):
"""Create an image out of `media'"""

def create_image(d, medium, out, tmp, snapshot):
"""Create an image out of `medium'"""
d.setBackgroundTitle('snf-image-creator')

gauge = GaugeOutput(d, "Initialization", "Initializing...")
out.append(gauge)
disk = Disk(media, out, tmp)
disk = Disk(medium, out, tmp)

def signal_handler(signum, frame):
gauge.cleanup()
Expand Down Expand Up @@ -83,7 +90,7 @@ def dummy(self):
session['excluded_tasks'] = [-1]
session['task_metadata'] = ["EXCLUDE_ALL_TASKS"]

msg = "The system on the input media is not supported." \
msg = "The system on the input medium is not supported." \
"\n\nReason: %s\n\n" \
"We highly recommend not to create an image out of this, " \
"since the image won't be cleaned up and you will not be " \
Expand All @@ -97,7 +104,7 @@ def dummy(self):
d.infobox("Thank you for using snf-image-creator. Bye", width=53)
return 0

msg = "snf-image-creator detected a %s system on the input media. " \
msg = "snf-image-creator detected a %s system on the input medium. " \
"Would you like to run a wizard to assist you through the " \
"image creation process?\n\nChoose <Wizard> to run the wizard," \
" <Expert> to run snf-image-creator in expert mode or press " \
Expand Down Expand Up @@ -161,12 +168,11 @@ def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
return (code, output.splitlines())


def dialog_main(media, **kwargs):
def dialog_main(medium, **kwargs):
"""Main function for the dialog-based version of the program"""

tmpdir = kwargs['tmpdir'] if 'tmpdir' in kwargs else None
snapshot = kwargs['snapshot'] if 'snapshot' in kwargs else True
logfile = kwargs['logfile'] if 'logfile' in kwargs else None
syslog = kwargs['syslog'] if 'syslog' in kwargs else False

# In openSUSE dialog is buggy under xterm
Expand Down Expand Up @@ -197,115 +203,153 @@ def dialog_main(media, **kwargs):

d.setBackgroundTitle('snf-image-creator')

# Pick input media
# Pick input medium
while True:
media = select_file(d, init=media, ftype="br", bundle_host=True,
title="Please select an input media.")
if media is None:
medium = select_file(d, init=medium, ftype="br", bundle_host=True,
title="Please select an input medium.")
if medium is None:
if confirm_exit(
d, "You canceled the media selection dialog box."):
d, "You canceled the medium selection dialog box."):
return 0
continue
break

tmplog = None if logfile else tempfile.NamedTemporaryFile(prefix='fatal-',
delete=False)
# FIXME: It does not make sense to pass both the dialog instance
# explicitly, and Output instances separately. The called function
# shouldn't have to know that it is using a dialog instance, or call
# pythondialog-specific methods, but use the Output instance via a
# defined interface.

# This is an ugly workaround, until the separation of logging and Output
# frontends is complete: Just make logging through Output a no-op for now,
# by passing an empty list.
logs = []
try:
stream = logfile if logfile else tmplog
logs.append(SimpleOutput(colored=False, stderr=stream, stdout=stream))
if syslog:
logs.append(SyslogOutput())

while 1:
try:
out = CompositeOutput(logs)
out.info("Starting %s v%s ..." % (PROGNAME, version))
ret = create_image(d, media, out, tmpdir, snapshot)
ret = create_image(d, medium, out, tmpdir, snapshot)
break
except Reset:
for log in logs:
log.info("Resetting everything ...")
log.info("Resetting everything ...")
except FatalError as error:
for log in logs:
log.error(str(error))
msg = 'A fatal error occured. See %s for a full log.' % log.stderr.name
log.error("Fatal: " + str(error))
msg = "A fatal error has occured: " + str(error)
d.infobox(msg, width=WIDTH, title="Fatal Error")
return 1
else:
if tmplog:
os.unlink(tmplog.name)
finally:
if tmplog:
tmplog.close()

return ret


def main():
"""Entrance Point"""
if os.geteuid() != 0:
sys.stderr.write("Error: You must run %s as root\n" % PROGNAME)
"""Entry Point"""
d = ("Create a cloud Image from the specified INPUT_MEDIUM."
" INPUT_MEDIUM must be the hard disk of an existing OS deployment"
" to be used as the template for Image creation. Supported formats"
" include raw block devices, all disk image file formats supported"
" by QEMU (e.g., QCOW2, VMDK, VDI, VHD), or the filesystem of the"
" host itself. The resulting Image is meant to be used with Synnefo"
" and other IaaS cloud platforms. Note this program works on a"
" snapshot of INPUT_MEDIUM, and will not modify its contents.")
e = ("%(prog)s requires root privileges.")

parser = argparse.ArgumentParser(description=d, epilog=e)

parser.add_argument("--tmpdir", metavar="TMPDIR", type=str, dest="tmpdir",
default=None,
help=("Create large temporary files under TMPDIR."
" Default is to use a randomly-named temporary"
" directory under /var/tmp or /tmp."))
parser.add_argument("-l", "--logfile", metavar="LOGFILE", type=str,
dest="logfile", default=constants.DEFAULT_LOGFILE,
help=("Log all messages to LOGFILE."
" Default: %(default)s"))
parser.add_argument("--syslog", dest="syslog", default=False,
action="store_true", help="Also log to syslog")
parser.add_argument("-v", "--verbose", dest="verbose", default=False,
action="store_true",
help="Be verbose, log everything to ease debugging")
parser.add_argument("--no-snapshot", dest="snapshot", default=True,
action="store_false",
help=("Do not work on a snapshot, but modify the input"
" medium directly instead. DO NOT USE THIS"
" OPTION UNLESS YOU REALLY KNOW WHAT YOU ARE"
" DOING. THIS WILL ALTER THE ORIGINAL MEDIUM!"))
parser.add_argument("-V", "--version", action="version",
version=version)
parser.add_argument(metavar="INPUT_MEDIUM",
nargs='?', dest="medium", type=str, default=None,
help=("Use INPUT_MEDIUM as the template for"
" Image creation, e.g., /dev/sdc, /disk0.vmdk."
" Specify a single slash character (/) to bundle"
" the filesystem of the host itself."))

args = parser.parse_args()

ensure_root(PROGNAME)

if args.tmpdir is not None and not os.path.isdir(args.tmpdir):
parser.error("Argument `%s' to --tmpdir must be a directory"
% args.tmpdir)

# Setup logging and get a logger as early as possible.
# FIXME: Must turn on redirect_stderr, but need to verify that only
# errors/diagnostics and not user-visible output goes to stderr
SetupLogging(PROGNAME, logfile=args.logfile, debug=args.verbose,
use_syslog=args.syslog, redirect_stderr_fd=False)
log.info("%s v%s starting..." % (PROGNAME, version))

# Ensure we run on a terminal, so we can use termios calls liberally
if not (os.isatty(sys.stdin.fileno()) and os.isatty(sys.stdout.fileno())):
sys.stderr.write(("Error: This program is interactive and requires a"
"terminal for standard input and output."))
sys.exit(2)

usage = "Usage: %prog [options] [<input_media>]"
parser = optparse.OptionParser(version=version, usage=usage)
parser.add_option("-l", "--logfile", type="string", dest="logfile",
default=None, help="log all messages to FILE",
metavar="FILE")
parser.add_option("--no-snapshot", dest="snapshot", default=True,
help="don't snapshot the input media. (THIS IS "
"DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)",
action="store_false")
parser.add_option("--syslog", dest="syslog", default=False,
help="log to syslog", action="store_true")
parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
help="create large temporary image files under DIR",
metavar="DIR")

opts, args = parser.parse_args(sys.argv[1:])

if len(args) > 1:
parser.error("Wrong number of arguments")

media = args[0] if len(args) == 1 else None

if opts.tmp is not None and not os.path.isdir(opts.tmp):
parser.error("Directory: `%s' specified with --tmpdir is not valid"
% opts.tmp)

try:
logfile = open(opts.logfile, 'w') if opts.logfile is not None else None
except IOError as error:
parser.error("Unable to open logfile `%s' for writing. Reason: %s" %
(opts.logfile, error.strerror))

# Save the terminal attributes
attr = termios.tcgetattr(sys.stdin.fileno())
try:
# Save the terminal attributes
attr = termios.tcgetattr(sys.stdin.fileno())
try:
ret = dialog_main(media, logfile=logfile, tmpdir=opts.tmp,
snapshot=opts.snapshot, syslog=opts.syslog)
ret = dialog_main(args.medium, tmpdir=args.tmpdir,
snapshot=args.snapshot, syslog=args.syslog)
finally:
# Restore the terminal attributes. If an error occurs make sure
# that the terminal turns back to normal.
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr)

# This is an ugly hack:
#
# It seems resetting the terminal after using dialog
# races with printing the text of the exception,
# and overwrites it with the dialog background.
#
# Sleep for a tiny amount of time to ensure
# the exception is visible.
#
# This code path is ugly and must be replaced:
#
# Logging should be mandatory, since we have a temporary directory
# anyway, and all logging output should go there.
time.sleep(0.5)
termios.tcflush(sys.stdin.fileno(), termios.TCIOFLUSH)
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, attr)
except:
# Clear the screen
# An unexpected exception has occured.
# Ensure the exception is logged,
# then clear the screen and output the traceback.
log.exception("Internal error. Unexpected Exception:")
sys.stdout.flush()
sys.stdout.write('\033[2J') # Erase Screen
sys.stdout.write('\033[H') # Cursor Home
sys.stdout.flush()

exception = traceback.format_exc()
sys.stderr.write("An unexpected exception has occured. Please"
" include the following text in any bug report:\n\n")
sys.stderr.write(exception)
if logfile is not None:
logfile.write(exception)
sys.stderr.write(("\nLogfile `%s' may contain more information about"
" the cause of this error.\n\n" % args.logfile))
sys.stderr.flush()

sys.exit(3)
finally:
if logfile is not None:
logfile.close()

sys.exit(ret)

Expand Down
Loading