Skip to content

Commit

Permalink
Merge branch 'release/1.5.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmatthews committed Jun 7, 2016
2 parents aa8bcd9 + 5a027b5 commit 6c526e3
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 65 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ Change Log
All notable changes to the COT project will be documented in this file.
This project adheres to `Semantic Versioning`_.

`1.5.1`_ - 2016-06-07
---------------------

**Added**

- ``cot edit-hardware --network-descriptions`` option, to specify the
descriptive string(s) associated with each network definition.

**Fixed**

- `#48`_ - NIC type not set when adding NICs to an OVF that had none before.
- When updating NIC network mapping, COT now also updates any Description
that references the network mapping.

`1.5.0`_ - 2016-06-06
---------------------

Expand Down Expand Up @@ -385,6 +399,7 @@ Initial public release.
.. _#44: https://github.com/glennmatthews/cot/issues/44
.. _#45: https://github.com/glennmatthews/cot/issues/45
.. _#47: https://github.com/glennmatthews/cot/issues/47
.. _#48: https://github.com/glennmatthews/cot/issues/48

.. _Semantic Versioning: http://semver.org/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
Expand All @@ -404,6 +419,7 @@ Initial public release.
.. _argcomplete: https://argcomplete.readthedocs.io/en/latest/

.. _Unreleased: https://github.com/glennmatthews/cot/compare/master...develop
.. _1.5.1: https://github.com/glennmatthews/cot/compare/v1.5.0...v1.5.1
.. _1.5.0: https://github.com/glennmatthews/cot/compare/v1.4.2...v1.5.0
.. _1.4.2: https://github.com/glennmatthews/cot/compare/v1.4.1...v1.4.2
.. _1.4.1: https://github.com/glennmatthews/cot/compare/v1.4.0...v1.4.1
Expand Down
88 changes: 66 additions & 22 deletions COT/edit_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

from .data_validation import canonicalize_ide_subtype, canonicalize_nic_subtype
from .data_validation import canonicalize_scsi_subtype
from .data_validation import natural_sort, no_whitespace, mac_address
from .data_validation import no_whitespace, mac_address
from .data_validation import non_negative_int, positive_int, InvalidInputError
from .submodule import COTSubmodule

Expand All @@ -57,6 +57,7 @@ class COTEditHardware(COTSubmodule):
:attr:`mac_addresses_list`,
:attr:`nic_networks`,
:attr:`nic_names`,
:attr:`network_descriptions`,
:attr:`serial_ports`,
:attr:`serial_connectivity`,
:attr:`scsi_subtypes`,
Expand Down Expand Up @@ -85,6 +86,11 @@ def __init__(self, UI):
self.nic_names = None
"""List of NIC name strings.
Can use wildcards as described in :meth:`expand_list_wildcard`.
"""
self.network_descriptions = None
"""List of network description strings.
Can use wildcards as described in :meth:`expand_list_wildcard`.
"""
self._serial_ports = None
Expand Down Expand Up @@ -293,6 +299,7 @@ def ready_to_run(self):
self.mac_addresses_list is None and
self.nic_networks is None and
self.nic_names is None and
self.network_descriptions is None and
self.serial_ports is None and
self.serial_connectivity is None and
self.scsi_subtypes is None and
Expand All @@ -310,12 +317,20 @@ def run(self):
"""
super(COTEditHardware, self).run()

if self.profiles is not None and self.virtual_system_type is not None:
self.UI.confirm_or_die(
"VirtualSystemType is not filtered by configuration profile. "
"Requested system type(s) '{0}' will be set for ALL profiles, "
"not just profile(s) {1}. Continue?"
.format(" ".join(self.virtual_system_type), self.profiles))
# Warn user about non-profile-aware properties when setting profiles
if self.profiles is not None:
if self.virtual_system_type is not None:
self.UI.confirm_or_die(
"VirtualSystemType is not filtered by configuration"
" profile. Requested system type(s) '{0}' will be set for"
" ALL profiles, not just profile(s) {1}. Continue?"
.format(" ".join(self.virtual_system_type), self.profiles))
if self.network_descriptions is not None:
self.UI.confirm_or_die(
"Network descriptions are not filtered by configuration"
" profile. Requested network descriptions will be set for"
" networks across ALL profiles, not just profile(s) {0}."
" Continue?".format(self.profiles))

vm = self.vm

Expand Down Expand Up @@ -364,9 +379,6 @@ def run(self):
if self.memory is not None:
vm.set_memory(self.memory, self.profiles)

if self.nic_types is not None:
vm.set_nic_types(self.nic_types, self.profiles)

nics_dict = vm.get_nic_count(self.profiles)
if self.nics is not None:
for (profile, count) in nics_dict.items():
Expand All @@ -378,22 +390,45 @@ def run(self):
(count - self.nics), self.nics))
vm.set_nic_count(self.nics, self.profiles)

if self.nic_types is not None:
vm.set_nic_types(self.nic_types, self.profiles)

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

if self.network_descriptions is None:
new_descs = []
else:
new_descs = self.expand_list_wildcard(self.network_descriptions,
max_nics)
if self.nic_networks is None:
# Just rename existing networks, instead of making new ones
for network, desc in zip(vm.networks, new_descs):
# Despite the name, create_network can also be used to
# update an existing network.
vm.create_network(network, desc)

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(new_networks)):
for network in new_networks:
if new_descs:
new_desc = new_descs.pop(0)
else:
new_desc = None

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)
if not new_desc:
new_desc = self.UI.get_input(
"Please enter a description for this network",
network)
# create or update
vm.create_network(network, new_desc)

vm.set_nic_networks(new_networks, self.profiles)

if self.mac_addresses_list is not None:
Expand Down Expand Up @@ -454,11 +489,12 @@ def create_subparser(self):
help="Edit virtual machine hardware properties of an OVF",
description="Edit hardware properties of the specified OVF or OVA",
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."
"The --nic-names, --nic-networks, and --network-descriptions"
" options support the use of a wildcard value to"
" automatically generate a series of consecutively numbered"
" strings. 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',
Expand All @@ -468,10 +504,13 @@ def create_subparser(self):
" 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.",
" 'Ethernet0_10', 'Ethernet0_11', 'Ethernet0_12', etc.,"
" which are described as 'Data network 1', 'Data network 2',"
" etc.",
'cot edit-hardware input.ova -o output.ova'
' --nic-names "Ethernet0/{10}"'
' --nic-networks "Ethernet0_{10}"'),
' --nic-networks "Ethernet0_{10}"'
' --network-descriptions "Data network {1}"'),
("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'
Expand Down Expand Up @@ -523,6 +562,11 @@ def create_subparser(self):
metavar=('NETWORK', 'NETWORK2'),
help="Specify a series of one or more network names "
"or patterns to map NICs to. See Notes.")
g.add_argument('--network-descriptions', nargs='+',
metavar=('NAME1', 'NAME2'),
help="Specify a list of one or more network "
"descriptions or patterns to apply to the networks. "
"See Notes.")
g.add_argument('-M', '--mac-addresses-list', type=mac_address,
metavar=('MAC1', 'MAC2'), action='append', nargs='+',
help="Specify a list of MAC addresses for the NICs. "
Expand Down
46 changes: 37 additions & 9 deletions COT/ovf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,8 @@ def set_nic_count(self, count, profile_list):
def create_network(self, label, description):
"""Define a new network with the given label and description.
Also serves to update the description of an existing network label.
:param str label: Brief label for the network
:param str description: Verbose description of the network
"""
Expand Down Expand Up @@ -3271,11 +3273,29 @@ def add_item(self, item):
self.set_property(attrib_string, value, profiles, overwrite=False)

# Store any child elements of the Item.
# We need to iterate in reverse order because we want to be able
# to reference the VirtualQuantity and ResourceSubType
# when inspecting the ElementName and Description elements.
for child in reversed(list(item)):
if XML.strip_ns(child.tag) not in self.ITEM_CHILDREN:
# We save the ElementName and Description elements for last because
# they may include references to the VirtualQuantity, ResourceSubType,
# and/or Connection entries, which we won't know until we process them.
children = list(item)
name_child = next(
(child for child in children if
XML.strip_ns(child.tag) == self.ELEMENT_NAME),
None)
desc_child = next(
(child for child in children if
XML.strip_ns(child.tag) == self.ITEM_DESCRIPTION),
None)
if name_child is not None:
children.remove(name_child)
children.append(name_child)
# Description is *after* name because it may reference name
if desc_child is not None:
children.remove(desc_child)
children.append(desc_child)

for child in children:
tag = XML.strip_ns(child.tag)
if tag not in self.ITEM_CHILDREN:
# Non-standard elements may not follow the standard rules -
# for example, VMware OVF extensions may have multiple
# vmw:Config elements, each distinguished by its vmw:key attr.
Expand All @@ -3287,7 +3307,6 @@ def add_item(self, item):
profiles, overwrite=False)
continue
# Store the value of this element:
tag = XML.strip_ns(child.tag)
self.set_property(tag, child.text, profiles, overwrite=False)
# Store any attributes of this element
for (attrib, value) in child.attrib.items():
Expand Down Expand Up @@ -3337,9 +3356,9 @@ def set_property(self, key, value, profiles=None, overwrite=True):
# used by this property.
profiles = set.union(*value_dict.values())
profiles = set(profiles)
# If the ElementName or Description references the VirtualQuantity
# or ResourceSubType, replace that reference with a placeholder
# that we can regenerate at output time. That way, if the
# If the ElementName or Description references the VirtualQuantity,
# Connection, or ResourceSubType, replace that reference with a
# placeholder that we can regenerate at output time. That way, if the
# VirtualQuantity or ResourceSubType changes, these can change too.
if key == self.ELEMENT_NAME or key == self.ITEM_DESCRIPTION:
vq_val = self.get_value(self.VIRTUAL_QUANTITY, profiles)
Expand All @@ -3348,6 +3367,9 @@ def set_property(self, key, value, profiles=None, overwrite=True):
rst_val = self.get_value(self.RESOURCE_SUB_TYPE, profiles)
if rst_val is not None:
value = re.sub(rst_val, "_RST_", value)
conn_val = self.get_value(self.CONNECTION, profiles)
if conn_val is not None:
value = re.sub(conn_val, "_CONN_", value)
# Similarly, if the Description references the ElementName...
if key == self.ITEM_DESCRIPTION:
en_val = self.get_value(self.ELEMENT_NAME, profiles)
Expand Down Expand Up @@ -3547,12 +3569,15 @@ def get_value(self, tag, profiles=None):
rst_val = self._get_value(self.RESOURCE_SUB_TYPE, profiles)
vq_val = self._get_value(self.VIRTUAL_QUANTITY, profiles)
en_val = self._get_value(self.ELEMENT_NAME, profiles)
conn_val = self._get_value(self.CONNECTION, profiles)
if rst_val is not None:
val = re.sub("_RST_", str(rst_val), str(val))
if vq_val is not None:
val = re.sub("_VQ_", str(vq_val), str(val))
if en_val is not None:
val = re.sub("_EN_", str(en_val), str(val))
if conn_val is not None:
val = re.sub("_CONN_", str(conn_val), str(val))

return val

Expand Down Expand Up @@ -3683,6 +3708,7 @@ def generate_items(self):
rst_val = self.get_value(self.RESOURCE_SUB_TYPE, final_set)
vq_val = self.get_value(self.VIRTUAL_QUANTITY, final_set)
en_val = self.get_value(self.ELEMENT_NAME, final_set)
conn_val = self.get_value(self.CONNECTION, final_set)
for key in sorted(self.property_dict.keys()):
default_val = None
found = False
Expand All @@ -3708,6 +3734,8 @@ def generate_items(self):
val = re.sub("_RST_", str(rst_val), str(val))
if vq_val is not None:
val = re.sub("_VQ_", str(vq_val), str(val))
if conn_val is not None:
val = re.sub("_CONN_", str(conn_val), str(val))
if key == self.ITEM_DESCRIPTION:
if en_val is not None:
val = re.sub("_EN_", str(en_val), str(val))
Expand Down
Loading

0 comments on commit 6c526e3

Please sign in to comment.