diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 498c47d..2e518db 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,12 @@ This project adheres to `Semantic Versioning`_.
`Unreleased`_
-------------
+**Fixed**
+
+- Fixed a case that could result in a RuntimeError being thrown when using
+ ``cot edit-hardware`` to simultaneously create NICs and define a new
+ configuration profile (`#64`_).
+
**Added**
- ``Command`` (formerly ``COTGenericSubmodule``) now checks the available
@@ -708,6 +714,7 @@ Initial public release.
.. _#61: https://github.com/glennmatthews/cot/issues/61
.. _#62: https://github.com/glennmatthews/cot/issues/62
.. _#63: https://github.com/glennmatthews/cot/issues/63
+.. _#64: https://github.com/glennmatthews/cot/issues/64
.. _Semantic Versioning: http://semver.org/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
diff --git a/COT/commands/tests/test_edit_hardware.py b/COT/commands/tests/test_edit_hardware.py
index d0d8ead..1836050 100644
--- a/COT/commands/tests/test_edit_hardware.py
+++ b/COT/commands/tests/test_edit_hardware.py
@@ -657,7 +657,7 @@ def test_set_nic_count_merge_profiles(self):
""")
def test_set_nic_count_create_new_one_profile(self):
- """"Create a new NIC under a single profile."""
+ """Create a new NIC under a single profile."""
self.command.package = self.input_ovf
self.command.nics = '4'
self.command.profiles = ['4CPU-4GB-3NIC']
@@ -675,6 +675,85 @@ def test_set_nic_count_create_new_one_profile(self):
+ 14
+ VMXNET3
+ 10
++
+
+""")
+
+ def test_set_nic_count_create_new_and_new_profile(self):
+ """Create new NICs under a new profile. Test for issue #64."""
+ self.command.package = self.input_ovf
+ self.command.nics = '4'
+ self.command.profiles = ['4CPU-4GB-4NIC']
+ self.command.run()
+ self.command.finished()
+ self.check_diff("""
+
++
++ 4CPU-4GB-4NIC
++ 4CPU-4GB-4NIC
++
+
+...
+
+-
++
+ 12
+...
+
+-
++
+ 13
+...
+ 13
++ VMXNET3
++ 10
++
++
++ 14
++ true
++ VM Network
++ VMXNET3 ethernet adapter on "VM Network"\
+
++ Ethernet4
++ 14
+ VMXNET3
+""")
+
+ def test_set_nic_count_create_new_and_split_new_profile(self):
+ """Create new NICs under a new profile splitting from unified profile.
+
+ Another test for issue #64.
+ """
+ self.command.package = self.csr_ovf
+ self.command.nics = '4'
+ self.command.profiles = ['4CPU-4GB-4NIC']
+ self.command.run()
+ self.command.finished()
+ self.check_diff(file1=self.csr_ovf, expected="""
+
++
++ Data network 4
++
+
+...
+ Large hardware profile (requires purchase of DRAM \
+upgrade SKU) - 4 vCPUs, 8 GB RAM
++
++
++ 4CPU-4GB-4NIC
++ 4CPU-4GB-4NIC
+
+...
+
++
++ 14
++ true
++ GigabitEthernet4
++ NIC representing GigabitEthernet4
++ GigabitEthernet4
++ 14
++ VMXNET3 virtio
++ 10
+
""")
@@ -693,6 +772,32 @@ def test_set_nic_count_delete_nics(self):
11
""")
+ def test_set_nic_count_delete_nics_new_profile(self):
+ """Set NIC count to a lower value under a newly created profile."""
+ self.command.package = self.csr_ovf
+ self.command.nics = 1
+ self.command.profiles = ['1CPU-4GB-1NIC']
+ self.command.run()
+ self.command.finished()
+ self.check_diff(file1=self.csr_ovf, expected="""
+
++
++ 1CPU-4GB-1NIC
++ 1CPU-4GB-1NIC
++
+
+...
+
+-
++
+ 12
+...
+
+-
++
+ 13
+""")
+
def test_set_nic_count_no_existing(self):
"""Create a NIC when nothing pre-exists."""
self.command.package = self.minimal_ovf
diff --git a/COT/vm_description/ovf/hardware.py b/COT/vm_description/ovf/hardware.py
index 3d3fa5d..0b0bd97 100644
--- a/COT/vm_description/ovf/hardware.py
+++ b/COT/vm_description/ovf/hardware.py
@@ -205,7 +205,17 @@ def clone_item(self, parent_item, profile_list):
tuple: ``(instance_id, ovfitem)``
"""
instance = self.find_unused_instance_id()
+ logger.debug("Cloning existing Item %s with new instance ID %s",
+ parent_item, instance)
ovfitem = copy.deepcopy(parent_item)
+
+ # Delete any profiles from the parent that we don't need now,
+ # otherwise we'll get an error when trying to set the instance ID
+ # on our clone due to self-inconsistency (#64).
+ for profile in self.ovf.config_profiles:
+ if ovfitem.has_profile(profile) and profile not in profile_list:
+ ovfitem.remove_profile(profile)
+
ovfitem.set_property(self.ovf.INSTANCE_ID, instance, profile_list)
ovfitem.modified = True
self.item_dict[instance] = ovfitem
diff --git a/COT/vm_description/ovf/name_helper.py b/COT/vm_description/ovf/name_helper.py
index fc372d5..8ecad19 100644
--- a/COT/vm_description/ovf/name_helper.py
+++ b/COT/vm_description/ovf/name_helper.py
@@ -283,7 +283,7 @@ def __getattr__(self, name):
elif name == "EPASD" or name == "SASD":
self._cache[name] = self.RASD
elif name not in self._raw:
- raise AttributeError
+ raise AttributeError("Unknown attribute '{0}'".format(name))
else:
ns = getattr(self, self._raw[name].namespace_name)
tag = self._raw[name].tag