Skip to content

Commit

Permalink
Guess network names when creating new NICs. Fixes #18
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmatthews committed Aug 26, 2016
1 parent 98335d9 commit 8b6d24e
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 16 deletions.
22 changes: 12 additions & 10 deletions COT/data_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
.. autosummary::
:nosignatures:
alphanum_split
canonicalize_helper
canonicalize_ide_subtype
canonicalize_nic_subtype
Expand Down Expand Up @@ -67,6 +68,15 @@ def to_string(obj):
return str(obj)


def alphanum_split(key):
"""Split the key into a list of [text, int, text, int, ...]."""
def text_to_int(text):
"""Convert number strings to ints, leave other strings as text."""
return int(text) if text.isdigit() else text

return [text_to_int(c) for c in re.split('([0-9]+)', key)]


def natural_sort(l):
"""Sort the given list "naturally" rather than in ASCII order.
Expand All @@ -77,16 +87,8 @@ def natural_sort(l):
:param list l: List to sort
:return: Sorted list
"""
def convert(text):
"""Convert number strings to ints, leave other strings as text."""
return int(text) if text.isdigit() else text

def alphanum_key(key):
"""Split the key into a list of [text, int, text, int, ...]."""
return [convert(c) for c in re.split('([0-9]+)', key)]

# Sort based on alphanum_key
return sorted(l, key=alphanum_key)
# Sort based on alphanum_split return value
return sorted(l, key=alphanum_split)


def match_or_die(first_label, first, second_label, second):
Expand Down
74 changes: 74 additions & 0 deletions COT/edit_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
:nosignatures:
expand_list_wildcard
guess_list_wildcard
**Classes**
Expand All @@ -38,6 +39,7 @@
import warnings

from COT.data_validation import (
alphanum_split,
canonicalize_ide_subtype,
canonicalize_nic_subtype,
canonicalize_scsi_subtype,
Expand Down Expand Up @@ -389,7 +391,30 @@ def _run_update_nics(self):
vm = self.vm

nics_dict = vm.get_nic_count(self.profiles)
max_nics = max(nics_dict.values())
if self.nics is not None:
# Special case:
# If...
# 1) We are creating at least one new NIC, AND
# 2) We didn't specify the network(s) to map the new NIC(s) to, AND
# 3) We have at least two NICs already, AND
# 4) Each existing NIC has a unique network
# ...then we will see if we can identify a pattern in the networks,
# and if so, we will create new network(s) following this pattern.
if (max_nics < self.nics and self.nic_networks is None and
max_nics >= 2 and max_nics == len(vm.networks)):
logger.info("Given that all existing NICs are mapped to "
"unique networks, trying to guess an implicit "
"pattern for creating new networks.")
# Can we guess a pattern from vm.networks?
self.nic_networks = guess_list_wildcard(vm.networks)
if self.nic_networks:
logger.info("Identified a pattern: --nic-networks %s",
" ".join(self.nic_networks))
else:
logger.info("No pattern could be identified from %s",
vm.networks)

for (profile, count) in nics_dict.items():
if self.nics < count:
self.UI.confirm_or_die(
Expand Down Expand Up @@ -697,3 +722,52 @@ def expand_list_wildcard(name_list, length):
logger.info("New list is %s", name_list)

return name_list


def guess_list_wildcard(known_values):
"""Inverse of :func:`expand_list_wildcard`. Guess the wildcard for a list.
Examples::
>>> guess_list_wildcard(['foo', 'bar', 'baz'])
>>> guess_list_wildcard(['foo1', 'foo2', 'foo3'])
['foo{1}']
>>> guess_list_wildcard(['foo', 'bar', 'baz3', 'baz4', 'baz5'])
['foo', 'bar', 'baz{3}']
>>> guess_list_wildcard(['Eth0/1', 'Eth0/2', 'Eth0/3'])
['Eth0/{1}']
>>> guess_list_wildcard(['Eth0/0', 'Eth1/0', 'Eth2/0'])
['Eth{0}/0']
>>> guess_list_wildcard(['fake1', 'fake2', 'real4', 'real5'])
['fake1', 'fake2', 'real{4}']
:param list known_values: Values to guess from
:return: Guessed wildcard list, or None if unable to guess
"""
logger.debug("Attempting to infer a pattern from %s", known_values)
# Guess sequences ending with simple N, N+1, N+2
for i in range(0, len(known_values)-1):
val = known_values[i]
split_val = alphanum_split(val)
for j in range(0, len(split_val)):
candidate = split_val[j]
if not isinstance(candidate, int):
continue
prefix = "".join([str(k) for k in split_val[:j]])
suffix = "".join([str(k) for k in split_val[j+1:]])
logger.debug("Possible next value for %s is %s%i%s",
val, prefix, candidate+1, suffix)
possible_next = prefix + str(candidate + 1) + suffix
if known_values[i+1] == possible_next:
match_pattern = prefix + "{" + str(candidate) + "}" + suffix
logger.debug("Match pattern is %s", match_pattern)
possible_name_list = known_values[:i] + [match_pattern]
logger.debug("Checking possible name list %s",
possible_name_list)
if (expand_list_wildcard(possible_name_list,
len(known_values)) == known_values):
return possible_name_list
logger.debug("No joy")

logger.debug("Unable to guess a pattern")
return None
70 changes: 64 additions & 6 deletions COT/tests/test_edit_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,17 +461,75 @@ def test_set_nic_type_no_existing(self):

def test_set_nic_count_add(self):
"""Add additional NICs across all profiles."""
self.instance.package = self.input_ovf
self.instance.nics = 5
self.instance.run()
self.instance.finished()
self.check_diff("""
</ovf:Item>
- <ovf:Item ovf:configuration="4CPU-4GB-3NIC">
+ <ovf:Item>
<rasd:AddressOnParent>12</rasd:AddressOnParent>
...
</ovf:Item>
- <ovf:Item ovf:configuration="4CPU-4GB-3NIC">
+ <ovf:Item>
<rasd:AddressOnParent>13</rasd:AddressOnParent>
...
<rasd:InstanceID>13</rasd:InstanceID>
+ <rasd:ResourceSubType>VMXNET3</rasd:ResourceSubType>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ </ovf:Item>
+ <ovf:Item>
+ <rasd:AddressOnParent>14</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>VM Network</rasd:Connection>
+ <rasd:Description>VMXNET3 ethernet adapter on "VM Network"\
</rasd:Description>
+ <rasd:ElementName>Ethernet4</rasd:ElementName>
+ <rasd:InstanceID>14</rasd:InstanceID>
+ <rasd:ResourceSubType>VMXNET3</rasd:ResourceSubType>
+ <rasd:ResourceType>10</rasd:ResourceType>
+ </ovf:Item>
+ <ovf:Item>
+ <rasd:AddressOnParent>15</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>VM Network</rasd:Connection>
+ <rasd:Description>VMXNET3 ethernet adapter on "VM Network"\
</rasd:Description>
+ <rasd:ElementName>Ethernet5</rasd:ElementName>
+ <rasd:InstanceID>15</rasd:InstanceID>
<rasd:ResourceSubType>VMXNET3</rasd:ResourceSubType>""")

def test_set_nic_count_add_smart_networks(self):
"""Add additional NICs (and implicitly networks) across all profiles.
In this OVF, each NIC is mapped to a unique network, so COT must be
smart enough to create additional networks as well.
"""
self.instance.package = self.csr_ovf
self.instance.nics = 6
self.instance.run()
self.instance.finished()
self.check_diff("""
<ovf:Description>Data network 3</ovf:Description>
+ </ovf:Network>
+ <ovf:Network ovf:name="GigabitEthernet4">
+ <ovf:Description>GigabitEthernet4</ovf:Description>
+ </ovf:Network>
+ <ovf:Network ovf:name="GigabitEthernet5">
+ <ovf:Description>GigabitEthernet5</ovf:Description>
+ </ovf:Network>
+ <ovf:Network ovf:name="GigabitEthernet6">
+ <ovf:Description>GigabitEthernet6</ovf:Description>
</ovf:Network>
...
</ovf:Item>
+ <ovf:Item>
+ <rasd:AddressOnParent>14</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>GigabitEthernet3</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet3</rasd:Description>
+ <rasd:Connection>GigabitEthernet4</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet4</rasd:Description>
+ <rasd:ElementName>GigabitEthernet4</rasd:ElementName>
+ <rasd:InstanceID>14</rasd:InstanceID>
+ <rasd:ResourceSubType>VMXNET3 virtio</rasd:ResourceSubType>
Expand All @@ -480,8 +538,8 @@ def test_set_nic_count_add(self):
+ <ovf:Item>
+ <rasd:AddressOnParent>15</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>GigabitEthernet3</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet3</rasd:Description>
+ <rasd:Connection>GigabitEthernet5</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet5</rasd:Description>
+ <rasd:ElementName>GigabitEthernet5</rasd:ElementName>
+ <rasd:InstanceID>15</rasd:InstanceID>
+ <rasd:ResourceSubType>VMXNET3 virtio</rasd:ResourceSubType>
Expand All @@ -490,8 +548,8 @@ def test_set_nic_count_add(self):
+ <ovf:Item>
+ <rasd:AddressOnParent>16</rasd:AddressOnParent>
+ <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
+ <rasd:Connection>GigabitEthernet3</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet3</rasd:Description>
+ <rasd:Connection>GigabitEthernet6</rasd:Connection>
+ <rasd:Description>NIC representing GigabitEthernet6</rasd:Description>
+ <rasd:ElementName>GigabitEthernet6</rasd:ElementName>
+ <rasd:InstanceID>16</rasd:InstanceID>
+ <rasd:ResourceSubType>VMXNET3 virtio</rasd:ResourceSubType>
Expand Down

0 comments on commit 8b6d24e

Please sign in to comment.