Skip to content

Commit

Permalink
Merge branch 'release/1.3.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmatthews committed Jul 2, 2015
2 parents 932bc66 + 6a5826a commit 79dc181
Show file tree
Hide file tree
Showing 29 changed files with 2,248 additions and 699 deletions.
416 changes: 326 additions & 90 deletions COT/_version.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions COT/add_disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,9 @@ def add_disk_worker(vm,
logger.verbose("Found Disk but not File - maybe placeholder?")

if disk_item is not None:
match_or_die("disk Item ResourceType",
vm.get_type_from_device(disk_item),
"--type", type)
UI.confirm_or_die("Existing disk Item is a {0}. Change it to a {1}?"
.format(vm.get_type_from_device(disk_item),
type))
vm.check_sanity_of_disk_device(disk, file, disk_item, ctrl_item)

if ctrl_item is not None:
Expand Down
137 changes: 111 additions & 26 deletions COT/edit_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import argparse
import logging
import re
import textwrap

from .data_validation import natural_sort, no_whitespace, mac_address
from .data_validation import non_negative_int, positive_int, InvalidInputError
Expand All @@ -46,6 +47,7 @@ class COTEditHardware(COTSubmodule):
Attributes:
:attr:`profiles`,
:attr:`delete_all_other_profiles`,
:attr:`cpus`,
:attr:`memory`,
:attr:`nics`,
Expand All @@ -65,16 +67,24 @@ def __init__(self, UI):
super(COTEditHardware, self).__init__(UI)
self.profiles = None
"""Configuration profile(s) to edit."""
self.delete_all_other_profiles = False
"""Delete all profiles other than those set in :attr:`profiles`."""
self._cpus = None
self._memory = None
self._nics = None
self._nic_type = None
self.mac_addresses_list = None
"""List of MAC addresses to set."""
self.nic_networks = None
"""List of NIC-to-network mappings."""
"""List of NIC-to-network mappings.
Can use wildcards as described in :meth:`expand_list_wildcard`.
"""
self.nic_names = None
"""List of NIC name strings"""
"""List of NIC name strings.
Can use wildcards as described in :meth:`expand_list_wildcard`.
"""
self._serial_ports = None
self.serial_connectivity = None
"""List of serial connection strings."""
Expand Down Expand Up @@ -187,6 +197,7 @@ def ready_to_run(self):
# Need some work to do!
if (
self.profiles is None and
self.delete_all_other_profiles is False and
self.cpus is None and
self.memory is None and
self.nics is None and
Expand Down Expand Up @@ -236,6 +247,26 @@ def run(self):
vm.create_configuration_profile(profile, label=label,
description=desc)

if self.delete_all_other_profiles:
if self.profiles is None:
self.UI.confirm_or_die(
"--delete-all-other-profiles was specified but no "
"--profiles was specified. Really proceed to delete ALL "
"configuration profiles?")
profiles_to_delete = vm.config_profiles
else:
profiles_to_delete = list(set(vm.config_profiles) -
set(self.profiles))
for profile in profiles_to_delete:
if self.profiles is not None:
if not self.UI.confirm("Delete profile {0}?"
.format(profile)):
logger.verbose("Skipping deletion of profile {0}"
.format(profile))
continue
# else (profiles == None) we already confirmed earlier
vm.delete_configuration_profile(profile)

if self.virtual_system_type is not None:
vm.system_types = self.virtual_system_type

Expand All @@ -248,8 +279,8 @@ def run(self):
if self.nic_type is not None:
vm.set_nic_type(self.nic_type, self.profiles)

nics_dict = vm.get_nic_count(self.profiles)
if self.nics is not None:
nics_dict = vm.get_nic_count(self.profiles)
for (profile, count) in nics_dict.items():
if self.nics < count:
self.UI.confirm_or_die(
Expand All @@ -259,24 +290,30 @@ def run(self):
(count - self.nics), self.nics))
vm.set_nic_count(self.nics, self.profiles)

nics_dict = vm.get_nic_count(self.profiles)
max_nics = max(nics_dict.values())

if self.nic_networks is not None:
existing_networks = vm.networks
new_networks = self.expand_list_wildcard(self.nic_networks,
max_nics)
# Convert nic_networks to a set to merge duplicate entries
for network in natural_sort(set(self.nic_networks)):
for network in natural_sort(set(new_networks)):
if network not in existing_networks:
self.UI.confirm_or_die(
"Network {0} is not currently defined. "
"Create it?".format(network))
desc = self.UI.get_input(
"Please enter a description for this network", network)
vm.create_network(network, desc)
vm.set_nic_networks(self.nic_networks, self.profiles)
vm.set_nic_networks(new_networks, self.profiles)

if self.mac_addresses_list is not None:
vm.set_nic_mac_addresses(self.mac_addresses_list, self.profiles)

if self.nic_names is not None:
vm.set_nic_names(self.nic_names, self.profiles)
names = self.expand_list_wildcard(self.nic_names, max_nics)
vm.set_nic_names(names, self.profiles)

if self.serial_ports is not None:
serial_dict = vm.get_serial_count(self.profiles)
Expand Down Expand Up @@ -317,6 +354,9 @@ def create_subparser(self, parent, storage):
:param dict storage: Dict of { 'label': subparser } to be updated with
subparser(s) created, if any.
"""
wrapper = textwrap.TextWrapper(width=self.UI.terminal_width - 1,
initial_indent=' ',
subsequent_indent=' ')
p = parent.add_parser(
'edit-hardware', add_help=False,
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand All @@ -330,20 +370,30 @@ def create_subparser(self, parent, storage):
]),
help="Edit virtual machine hardware properties of an OVF",
description="Edit hardware properties of the specified OVF or OVA",
epilog=self.UI.fill_examples([
epilog=("Notes:\n" + wrapper.fill(
"The --nic-names and --nic-networks options support the use"
" of a wildcard value to automatically generate a series of"
" consecutively numbered names. The syntax for the wildcard"
" option is '{' followed by a number to start incrementing"
" from, followed by '}'. See examples below."
) + "\n\n" + self.UI.fill_examples([
('Create a new profile named "1CPU-8GB" with 1 CPU and 8'
' gigabytes of RAM',
'cot edit-hardware csr1000v.ova --output csr1000v_custom.ova'
' --profile 1CPU-4GB --cpus 1 --memory 8GB'),
("Rename the NICs in the output OVA as 'mgmt', 'eth0',"
" 'eth1', 'eth2'...",
("Wildcard example - without caring about how many NICs are"
" defined in the input OVA, rename all of the NICs in the"
" output OVA as 'Ethernet0/10', 'Ethernet0/11',"
" 'Ethernet0/12', etc., and map them to networks"
" 'Ethernet0_10', 'Ethernet0_11', 'Ethernet0_12', etc.",
'cot edit-hardware input.ova -o output.ova'
' --nic-names "mgmt" "eth{0}"'),
("Rename the NICs in the output OVA as 'Ethernet0/10',"
" 'Ethernet0/11', 'Ethernet0/12', etc.",
' --nic-names "Ethernet0/{10}"'
' --nic-networks "Ethernet0_{10}"'),
("Combination of fixed and wildcarded names - rename the NICs"
" in the output OVA as 'mgmt', 'eth0', 'eth1', 'eth2'...",
'cot edit-hardware input.ova -o output.ova'
' --nic-names "Ethernet0/{10}"')
]))
' --nic-names "mgmt" "eth{0}"'),
])))

g = p.add_argument_group("general options")

Expand All @@ -361,6 +411,9 @@ def create_subparser(self, parent, storage):
help="Make hardware changes only under the given "
"configuration profile(s). (default: changes apply "
"to all profiles)")
g.add_argument('--delete-all-other-profiles', action='store_true',
help="Delete all configuration profiles other than"
" those specified with the --profiles option")

g = p.add_argument_group("computational hardware options")

Expand All @@ -379,27 +432,20 @@ def create_subparser(self, parent, storage):
help="Set the hardware type for all NICs. "
"(default: do not change existing NICs, and new "
"NICs added will match the existing type.)")
g.add_argument('--nic-names', nargs='+',
metavar=('NAME1', 'NAME2'),
help="Specify a list of one or more NIC names or "
"patterns to apply to NIC devices. See Notes.")
g.add_argument('-N', '--nic-networks', nargs='+',
metavar=('NETWORK', 'NETWORK2'),
help="Specify a series of one or more network names "
"to map NICs to. If N network names are specified, "
"the first (N-1) NICs will be mapped to the first "
"(N-1) networks and all remaining NICs will be "
"mapped to the Nth network.")
"or patterns to map NICs to. See Notes.")
g.add_argument('-M', '--mac-addresses-list', type=mac_address,
metavar=('MAC1', 'MAC2'), nargs='+',
help="Specify a list of MAC addresses for the NICs. "
"If N MACs are specified, the first (N-1) NICs "
"will receive the first (N-1) MACs, and all "
"remaining NICs will receive the Nth MAC")
g.add_argument('--nic-names', nargs='+',
metavar=('NAME1', 'NAME2'),
help="Specify a list of one or more NIC names or "
"patterns to apply to NIC devices. "
"If N names/patterns are specified, the first (N-1) "
"NICs will receive the first (N-1) names and "
"remaining NICs will be named based on the "
"name or pattern of the Nth item. See examples.")

g = p.add_argument_group("serial port options")

Expand Down Expand Up @@ -430,3 +476,42 @@ def create_subparser(self, parent, storage):
p.set_defaults(instance=self)

storage['edit-hardware'] = p

def expand_list_wildcard(self, name_list, length):
"""Expand a list containing a wildcard to the desired length.
Since various items (NIC names, network names, etc.) are often
named or numbered sequentially, we provide this API to allow the
user to specify a wildcard value to permit automatically
expanding a list of input strings to the desired length.
The syntax for the wildcard option is ``{`` followed by a number
(indicating the starting index for the name) followed by ``}``.
Examples:
``["eth{0}"]``
``Expands to ["eth0", "eth1", "eth2", ...]``
``["mgmt0" "eth{10}"]``
``Expands to ["mgmt0", "eth10", "eth11", "eth12", ...]``
:param list name_list: List of names to assign.
:param list length: Length to expand to
:return: Expanded list
"""
if len(name_list) < length:
logger.info("Expanding list {0} to {1} entries"
.format(name_list, length))
# Extract the pattern and remove it from the list
pattern = name_list[-1]
name_list = name_list[:-1]
# Look for the magic string in the pattern
match = re.search("{(\d+)}", pattern)
if match:
i = int(match.group(1))
else:
i = 0
while len(name_list) < length:
name_list.append(re.sub("{\d+}", str(i), pattern))
i += 1
logger.info("New list is {0}".format(name_list))

return name_list
10 changes: 5 additions & 5 deletions COT/helpers/fatdisk.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,23 @@ def install_helper(self):
.format(self.name, self.path))
return
logger.info("Installing 'fatdisk'...")
if self.port_install('fatdisk'):
if Helper.port_install('fatdisk'):
pass
elif platform.system() == 'Linux':
# Fatdisk installation requires make
if not self.find_executable('make'):
logger.info("fatdisk requires 'make'... installing 'make'")
if not (self.apt_install('make') or
self.yum_install('make')):
if not (Helper.apt_install('make') or
Helper.yum_install('make')):
raise NotImplementedError("Not sure how to install 'make'")
# Fatdisk requires clang or gcc or g++
if not (self.find_executable('clang') or
self.find_executable('gcc') or
self.find_executable('g++')):
logger.info(
"fatdisk requires a C compiler... installing 'gcc'")
if not (self.apt_install('gcc') or
self.yum_install('gcc')):
if not (Helper.apt_install('gcc') or
Helper.yum_install('gcc')):
raise NotImplementedError(
"Not sure how to install a C compiler")
with self.download_and_expand(
Expand Down
12 changes: 12 additions & 0 deletions COT/helpers/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,31 @@ def download_and_expand(cls, url):
finally:
logger.debug("Cleaning up temporary directory {0}".format(d))

_apt_updated = False
"""Whether we have run 'apt-get update' yet."""

@classmethod
def apt_install(cls, package):
"""Try to use ``apt-get`` to install a package."""
if not cls.PACKAGE_MANAGERS['apt-get']:
return False
if not cls._apt_updated:
cls._check_call(['sudo', 'apt-get', '-q', 'update'])
cls._apt_updated = True
cls._check_call(['sudo', 'apt-get', '-q', 'install', package])
return True

_port_updated = False
"""Whether we have run 'port selfupdate' yet."""

@classmethod
def port_install(cls, package):
"""Try to use ``port`` to install a package."""
if not cls.PACKAGE_MANAGERS['port']:
return False
if not cls._port_updated:
cls._check_call(['sudo', 'port', 'selfupdate'])
cls._port_updated = True
cls._check_call(['sudo', 'port', 'install', package])
return True

Expand Down
6 changes: 3 additions & 3 deletions COT/helpers/mkisofs.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ def install_helper(self):
.format(self.name, self.path))
return
logger.info("Installing 'mkisofs' and/or 'genisoimage'...")
if self.port_install('cdrtools'):
if Helper.port_install('cdrtools'):
self._name = 'mkisofs'
elif (self.apt_install('genisoimage') or
self.yum_install('genisoimage')):
elif (Helper.apt_install('genisoimage') or
Helper.yum_install('genisoimage')):
self._name = "genisoimage"
else:
raise NotImplementedError(
Expand Down
6 changes: 3 additions & 3 deletions COT/helpers/qemu_img.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ def install_helper(self):
.format(self.name, self.path))
return
logger.info("Installing 'qemu-img'...")
if not (self.apt_install('qemu-utils') or
self.port_install('qemu') or
self.yum_install('qemu-img')):
if not (Helper.apt_install('qemu-utils') or
Helper.port_install('qemu') or
Helper.yum_install('qemu-img')):
raise NotImplementedError(
"Unsure how to install qemu-img.\n"
"See http://en.wikibooks.org/wiki/QEMU/Installing_QEMU")
Expand Down

0 comments on commit 79dc181

Please sign in to comment.