Skip to content

Commit f6a2308

Browse files
committed
implement #38 & 39
1 parent 740e1e0 commit f6a2308

File tree

9 files changed

+177
-8
lines changed

9 files changed

+177
-8
lines changed

dd-helper.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#-------------------------------------------------------------------------------
4+
# Copyright 2016 upvm Contributors (see CONTRIBUTORS.md file in source)
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#-------------------------------------------------------------------------------
18+
19+
# Modules from standard library
20+
from subprocess import call
21+
from sys import argv, exit
22+
if len(argv) != 3:
23+
print("Improper arguments")
24+
exit(2)
25+
cmd = [
26+
'nice', '-n', '19',
27+
'dd',
28+
'of={}'.format(argv[1]),
29+
'if={}'.format(argv[2]),
30+
'bs=1M',
31+
]
32+
rc = call(cmd)
33+
exit(rc)

modules/argparser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,10 @@ def parse():
227227
# Optional virt-install options
228228
grpBB = p.add_argument_group(
229229
'TOTALLY OPTIONAL HARDWARE-LEVEL (VM) OPTIONS',
230-
description="Guest hardware configured by virt-install (after image is built). Each option\ncorresponds with virt-install option of same name. More configurability is\npossible than described. See virt-install man page.")
230+
description="Guest hardware configured by virt-install (after image is built). Most options\ncorrespond with virt-install options of same name. More configurability is\npossible than described. See virt-install man page.")
231+
grpBB.add_argument(
232+
'--primary-blockdev', metavar='BLOCKDEV',
233+
help="To use this option, specify a BLOCKDEV like '/dev/sdb2' or '/dev/vg/logvol' and then in that case, the image generated by virt-builder [using the above options] will be written to BLOCKDEV and then deleted; the first time you try this as a non-root user, {} will exit with a warning explaining how to add the appropriate sudo access".format(cfg.prog))
231234
grpBB.add_argument(
232235
'-m', '--memory', metavar='OPTIONS', default=1024,
233236
help="If this option is omitted, the guest will be defined with a RAM allocation of 1024 MiB; in its simplest form OPTIONS can be the amount of RAM in MiB; more complicated example: '-m 512,maxmemory=4096' (which would set the default to 512 MiB but allow raising up to 4 GiB without powering off)")

modules/blockdevimager.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 upvm Contributors (see CONTRIBUTORS.md file in source)
3+
# License: Apache License 2.0 (see LICENSE file in source)
4+
5+
# Modules from standard library
6+
from __future__ import print_function
7+
import subprocess
8+
import os
9+
from sys import exit
10+
11+
# Custom modules
12+
from . import string_ops as c
13+
from . import cfg
14+
15+
def write_and_cleanup_image():
16+
blockdev, imgfile = cfg.opts.primary_blockdev, cfg.opts.outFile
17+
cmd = ['sudo', cfg.blockdevHelper, blockdev, imgfile]
18+
print(c.GREEN("Starting write of image file '{}' to blockdev '{}'".format(imgfile, blockdev)))
19+
print(c.green("(This could take a while)"))
20+
rc = subprocess.call(cmd)
21+
if rc == 0:
22+
c.verbose("Successfully wrote intermediate image file to blockdev")
23+
cfg.cleanup_imagefile()
24+
else:
25+
print(c.RED("Error writing image file '{}' to block device '{}'".format(imgfile, blockdev)))
26+
cfg.cleanup_imagefile()
27+
exit(1)

modules/builder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def build():
7171
initialize_libvirt_qemu_session()
7272
o = cfg.opts
7373
cmd = [
74+
'nice',
7475
'virt-builder', o.templateName,
7576
'--output', o.outFile
7677
]

modules/cfg.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
from . import string_ops as c
1414

1515
# Version info
16-
__version__ = '0.10.5'
17-
__date__ = '2016/02/14'
16+
__version__ = '0.10.6'
17+
__date__ = '2016/02/16'
1818

1919
# All references to program name should use this
2020
prog = 'upvm'
2121

22+
# Path to helper blockdev script
23+
blockdevHelper = '/usr/libexec/{}-dd-helper.py'.format(prog)
24+
2225
# Set config file locations used by configargparse
2326
cfgfileExample = '/usr/share/{}/example.conf'.format(prog)
2427
cfgfileSystem = '/etc/{}.conf'.format(prog)
@@ -74,3 +77,15 @@ def prompt_for_template_and_exit():
7477
exit()
7578
else:
7679
exit(1)
80+
81+
def cleanup_imagefile():
82+
try:
83+
imgfile = opts.outFile
84+
except:
85+
return
86+
try:
87+
os.remove(imgfile)
88+
except:
89+
print(c.yellow("[Cleanup] Deleting image file '{}' failed; you can safely delete it yourself".format(imgfile)))
90+
else:
91+
c.verbose("[Cleanup] Deleted image file '{}'".format(imgfile))

modules/finalprompter.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ def check_prompt_hostname():
9696
cfg.opts.hostname = _default_name
9797

9898
def checkset_img_format():
99-
if cfg.opts.img_format in 'auto':
99+
if cfg.opts.primary_blockdev:
100+
c.verbose("Output image format unconditionally set to raw due to --primary-blockdev option")
101+
cfg.opts.img_format = 'raw'
102+
elif cfg.opts.img_format in 'auto':
100103
cfg.opts.img_format = cfg.templateInfo.get('format')
101104
if not cfg.opts.img_format or cfg.opts.img_format == 'qcow2' or cfg.opts.img_format == 'raw':
102105
c.verbose("Unable to determine native format of chosen template")

modules/installer.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import print_function
77
import subprocess
88
import os
9+
from sys import exit
910

1011
# Custom modules
1112
from . import cfg
@@ -34,10 +35,12 @@ def install():
3435
str(o.memory),
3536
'--vcpus',
3637
str(o.vcpus),
37-
'--disk',
38-
'{},format={}'.format(o.outFile, o.img_format),
3938
'--noautoconsole',
4039
]
40+
if o.primary_blockdev:
41+
cmd.extend(['--disk', o.primary_blockdev])
42+
else:
43+
cmd.extend(['--disk', '{},format={}'.format(o.outFile, o.img_format)])
4144
if o.os_variant:
4245
cmd.extend(['--os-variant', o.os_variant])
4346
if o.disk:
@@ -58,4 +61,8 @@ def install():
5861
cmd.append(a)
5962
c.verbose("Starting virt-install")
6063
c.debug("Executing:\n {}\n".format(" \ \n ".join(cmd)))
61-
subprocess.check_call(cmd)
64+
rc = subprocess.call(cmd)
65+
if rc != 0:
66+
print(c.RED("Error: virt-install command exited with non-zero status (see above)"))
67+
cfg.cleanup_imagefile()
68+
exit(rc)

modules/sysvalidator.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from sys import exit
1111
import pwd, grp
1212
import json
13+
from stat import S_ISBLK
1314

1415
# Custom modules
1516
from . import cfg
@@ -38,6 +39,27 @@ def call(cmd, showStdout=False, showStderr=False, shell=False):
3839
null.close()
3940
return ret(rc)
4041

42+
def convert_size_string_to_bytes(size):
43+
unit = size[-1:]
44+
sz = float(size[:-1])
45+
if unit == 'b':
46+
return sz
47+
elif unit == 'K':
48+
return sz * 1024**1
49+
elif unit == 'M':
50+
return sz * 1024**2
51+
elif unit == 'G':
52+
return sz * 1024**3
53+
elif unit == 'T':
54+
return sz * 1024**4
55+
elif unit == 'P':
56+
return sz * 1024**5
57+
elif unit == 'E':
58+
return sz * 1024**6
59+
else:
60+
print(c.RED("Invalid size specified (use e.g., '256M', '10G')"))
61+
exit(1)
62+
4163
def print_template_metadata(template):
4264
print("{0:17}: {1}".format("template", template['os-version']))
4365
for key in template:
@@ -151,8 +173,57 @@ def try_capture_existing_vm_names():
151173
print(c.RED("\nUnknown error executing '{}'".format(" ".join(cmd))))
152174
exit(1)
153175

176+
def validate_image_size():
177+
if cfg.opts.img_size:
178+
return convert_size_string_to_bytes(cfg.opts.img_size)
179+
else:
180+
return float(cfg.templateInfo['size'])
181+
182+
def validate_blockdev(imgsize):
183+
blockdev = cfg.opts.primary_blockdev
184+
try:
185+
mode = os.stat(blockdev).st_mode
186+
except OSError, e:
187+
print(c.RED(e))
188+
print("It looks like you passed the wrong thing to the --primary-blockdev option")
189+
exit(1)
190+
if not S_ISBLK(mode):
191+
print(c.RED("File passed as an arg to the --primary-blockdev option is not a block device"))
192+
print("This option is meant to be used to create a VM whose primary storage is a block device like /dev/sdb2")
193+
exit(1)
194+
cmd = ['lsblk', '-no', 'size', blockdev]
195+
c.debug("Executing: {}".format(" ".join(cmd)))
196+
try:
197+
blksize = subprocess.check_output(cmd).strip()
198+
except:
199+
print(c.RED("Unexpected error using lsblk to check size of --primary-blockdev device"))
200+
print("This shouldn't happen ... unless you somehow don't have util-linux and the lsblk command")
201+
exit(1)
202+
blksize = convert_size_string_to_bytes(blksize)
203+
if imgsize > blksize:
204+
print(c.YELLOW("Aborting because block device '{}' is smaller than projected image size".format(blockdev)))
205+
print("Block device size: {} MiB".format(int(blksize/1024/1024)))
206+
print("Image file size: {} MiB".format(int(imgsize/1024/1024)))
207+
exit(1)
208+
209+
def check_sudo_blockdevHelper_rights():
210+
blockdev, imgdir = cfg.opts.primary_blockdev, cfg.opts.img_dir
211+
cmd = ['sudo', '-l', cfg.blockdevHelper, blockdev, imgdir]
212+
if not call(cmd):
213+
print(c.YELLOW("Need root privileges to write image to {}".format(blockdev)))
214+
print(c.yellow(
215+
"You've requested {} write a VM disk image out to a block device but you\n"
216+
"are not root. To do this, give yourself the appropriate sudo access by\n"
217+
"running the following command as root:".format(cfg.prog)))
218+
print(c.green(" echo '{} ALL=(ALL) NOPASSWD: {}' >>/etc/sudoers.d/{}\n".format(myUser, cfg.blockdevHelper, cfg.prog)))
219+
print(c.yellow(
220+
"If you want to restrict your user to only have write access to a specific\n"
221+
"LVM volume group, you could instead do something like:"))
222+
print(c.green(" echo '{} ALL=(ALL) NOPASSWD: {} /dev/volgroup*' >>/etc/sudoers.d/{}\n".format(myUser, cfg.blockdevHelper, cfg.prog)))
223+
exit(1)
224+
154225
def check_system_config():
155-
check_virtbuilder_version()
226+
##check_virtbuilder_version()
156227
if_no_template_requested_then_print_vblist_and_quit()
157228
check_if_requested_template_exists()
158229
isolate_metadata_for_chosen_vb_template()
@@ -164,3 +235,7 @@ def check_system_config():
164235
##check_user_in_libvirt_group()
165236
check_for_writable_imgdir()
166237
try_capture_existing_vm_names()
238+
imgsize = validate_image_size()
239+
if cfg.opts.primary_blockdev:
240+
validate_blockdev(imgsize)
241+
check_sudo_blockdevHelper_rights()

upvm.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ def main():
8787
# Quit if requested
8888
if cfg.opts.build_image_only:
8989
exit()
90+
# Write image to blockdevice if requested
91+
if cfg.opts.primary_blockdev:
92+
from modules import blockdevimager
93+
blockdevimager.write_and_cleanup_image()
9094
# Launch virt-install
9195
from modules import installer
9296
installer.install()
@@ -101,4 +105,5 @@ def main():
101105
main()
102106
except KeyboardInterrupt:
103107
print("\nReceived KeyboardInterrupt. Exiting.")
108+
cfg.cleanup_imagefile()
104109
exit()

0 commit comments

Comments
 (0)