Merge ~sankaraditya/cloud-init:topic-stanguturi-vmware-curtin-changes into cloud-init:master

Proposed by Sankar Tanguturi
Status: Merged
Approved by: Scott Moser
Approved revision: f0b2b7cba92cd940ad3931a487087abb4126b019
Merged at revision: a1dfdda2a2ae20fe026881980ddf7d16110f06e2
Proposed branch: ~sankaraditya/cloud-init:topic-stanguturi-vmware-curtin-changes
Merge into: cloud-init:master
Diff against target: 808 lines (+418/-103)
4 files modified
cloudinit/sources/DataSourceOVF.py (+64/-27)
cloudinit/sources/helpers/vmware/imc/config_nic.py (+130/-71)
cloudinit/sources/helpers/vmware/imc/guestcust_util.py (+7/-5)
tests/unittests/test_vmware_config_file.py (+217/-0)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Chad Smith Approve
Review via email: mp+319866@code.launchpad.net

Description of the change

vmware customization: return network config format

For customizing the machines hosted on 'VMWare' hypervisor, the datasource
should return the 'network config' data in 'curtin' format.

Modify the code to read the customization configuration and return the converted data.

Added few tests.

LP: #1675063

To post a comment you must log in.
cab90ca... by Sankar Tanguturi

  Fix the positioning of 'route' element.

  As per the documentation available at
  http://curtin.readthedocs.io/en/latest/topics/networking.html,
  'route' is not a child element of 'subnets'. It is a peer element of
  'nameserver' / 'physical' elements.

  Modified the code to fix the 'route' elements.
  Updated the tests.

6f4a2e0... by Sankar Tanguturi

  Populate the 'destination' key to the 'route' elements.

  Added the code to populate 'destination' key in the route elements.
  Added the new tests for the new function.

  Ignoring the routes for static IPV6 for now.
  Fixed the unit tests accordingly.

66391c3... by Sankar Tanguturi

Merge branch 'master' into topic-stanguturi-vmware-curtin

Revision history for this message
Scott Moser (smoser) wrote :

Sankar,

Hey, this looks really good. I do have some comments, but thanks for the good work.

a.) I think I'd like to move the conversion of the network config out of the 'get'.
   The LOG.debug "Applying the network customization" is actually incorrect in that
   no changes are done there. Lets move that to happen in the 'network_config' function.

    if self._network_config is not None:
        return self._network_config

    ## convert the network config from the data in 'self' here to to the desired
    ## 'network_config' format.
    nc = NicConcifugrator(self.conf.nics)
    cfg = get_vmware_network_config(...)
    self._network_config = cfg
    return self.network_config

b.) lets change get_vmware_network_config to either take no 'Config' input or only Config input.
    as it is right now it takes a Config and a 'nics_cfg_list'.
    but the nics_cfg_list is completely derived from the Config, so why not just pass the Config?

    its probably helpful to just have a:
       get_network_config_from_conf()
    that calls:
       get_network_config(nameservers=None, search=None, nics=None)

c.) It'll be helpful if there is an easy way to go from a vmwareImcConfigFilePath contents to a network config. Then your tests will just do:
    nc = network_config_from_vmware_cfg("""
[NETWORK]
NETWORKING = yes
BOOTPROTO = dhcp
HOSTNAME = myhost1
DOMAINNAME = eng.vmware.com

[NIC-CONFIG]
NICS = NIC1,NIC2

[NIC1]
...
""")
    self.assertEqual(expected, found)

394a132... by Sankar Tanguturi

  Addressed review comments from Scott Moser.

  - Added few private variables to use in the future when
  the NICS have to be enabled when the customization gets completed.
  - Re-factored the code. Implemented few wrapper functions to
  make the code more readable and make testing easier.
  - Modified the unit test code accordingly.
  - Remove the code to clear dhcp. Now that the datasource is just
  returning the network configuration, cloud-init / curtin should
  take care of all dhcp settings.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

> a.) I think I'd like to move the conversion of the network config out of the
> 'get'.
> The LOG.debug "Applying the network customization" is actually incorrect in
> that
> no changes are done there. Lets move that to happen in the
> 'network_config' function.
>
> if self._network_config is not None:
> return self._network_config
>
> ## convert the network config from the data in 'self' here to to the
> desired
> ## 'network_config' format.
> nc = NicConcifugrator(self.conf.nics)
> cfg = get_vmware_network_config(...)
> self._network_config = cfg
> return self.network_config

Thanks for the comments. I tried modifying the code as per your suggestion and found an issue. When cloud-init runs in local mode initially, the datasourceOVF returned the data and then cloud-init saved the instance some where. During this stage, _network_config is saved as False. And then network-config property is called. The local mode finished. When the cloud-init in 'network' mode started, it loaded the saved instance. Since the _network_config is not saved, whenever this property is retrieved, the same code is executed again. This is not quite right for our use case.

Any suggestion to fix this?

Revision history for this message
Scott Moser (smoser) wrote :

Why was the network information not available in 'local' mode?

You'll have to show the code that you tried with, because nothing in your current tree here will set _network_config to false.

You're right that we have to get the network config in local mode otherwise it wont be applied early enough.

Revision history for this message
Scott Moser (smoser) wrote :

I'd also recommend storing the contents of vmwareImcConfigFilePath to the instance during 'get_data'.

self._vmware_conf_contents = util.load_file(vmwareImcConfigFilePath)

and then when you make a 'ConfigFile' object pass those contents in (you'll have ot modify it to take a string contents instead of having ConfigFile take a filename.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

> Why was the network information not available in 'local' mode?
>
> You'll have to show the code that you tried with, because nothing in your
> current tree here will set _network_config to false.
>
> You're right that we have to get the network config in local mode otherwise it
> wont be applied early enough.

Sorry if my previous comment was not clear. (_network_config was set to None and not False). Let me explain:

1. When the cloud-init initially runs in 'local' mode, first getdata is called and the necessary settings are retrieved. Now that we modified the code to retrieve the network code to 'network_config', getdata will not return the network data. At that point, cloud-init saves the datasource instance and it will have self._network_config set to None. When network-config property is retrieved, proper content is retrieved. This is perfect and works as expected.

2. After few seconds, cloud-init runs in 'network' mode. This time, the saved instance is retrieved and since the self._network_config is saved as None, when network-config property is retrieved, our 'function' to get the NICs information is called. This function should be called only once and not multiple times.

So, to avoid this, I think, self._network_config should be populated as a part of 'getData' function only.

Thanks
Sankar.

4bb42b4... by Sankar Tanguturi

  Added the code to wait for nics.txt file.

  NICS.txt is another file that is populated by 'VMware Tools'
  daemon which contains the information related to NICS. Added the
  code to wait for nics.txt file.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Addressed few review comments. Updated the code and also the tests.

In the getData() function itself, we want to parse the network configuration and return an error if parsing fails. For that reason, I didn't move the 'network config' part from getData() to network_config @property method.

Thanks

Revision history for this message
Scott Moser (smoser) wrote :

Can you open a bug for this? Just so I have something to track.
  https://bugs.launchpad.net/cloud-init/+filebug

I think my request for get_network_config does not make any sense any more.
The way you've done it now, there is basically no value in that function. No one calls it directly, so just make it part of 'get_network_config_from_conf'.

Some other comments in line, nothing huge.
Good work Sankar,
Scott

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

> Can you open a bug for this? Just so I have something to track.
> https://bugs.launchpad.net/cloud-init/+filebug

Scott,
Thanks for the valuable comments. For what part, you are asking me to file a bug?

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Will fix as per the review comments and update the diff. I have only one question about the 'activate' method. I mentioned below.

Thanks
Sankar.

2f2d810... by Sankar Tanguturi

  Fixed some nits suggested by Scott Moser.

Revision history for this message
Scott Moser (smoser) wrote :

Sankar, I went ahead and opened a bug. I just wanted something to track this larger set of changes.

One other request in this Merge. We should now be able to drop NicConfigurator and any tests that exist for it as this will all be getting rendered with cloud-init's generic renderers.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

> One other request in this Merge. We should now be able to drop
> NicConfigurator and any tests that exist for it as this will all be getting
> rendered with cloud-init's generic renderers.

I don't think we can drop the NicConfigurator. It the the class that generates the necessary network config in curtin format. It doesn't not actually configure the nics but just reads the mac addresses of the system and returns proper network data.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Working on some small nits. Will update the diff.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
8e3bb47... by Sankar Tanguturi

Merge branch 'master' into topic-stanguturi-vmware-curtin

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

one small comment. this was as far as I got just yet.

Revision history for this message
Chad Smith (chad.smith) wrote :

Looks really good. Long term I would like to see us hoist some of the ip-command parsing utilities up into cloudinit.net.network_state or net.__init__ but that doesn't have to be part of this branch. I added a few inline comments or questions. Nothing major, I just wanted some clarification to better understand the approach.

Thanks again for this work. let's wrap up these branches early next week if we can. Sorry for the delay.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Thanks Chad and Scott for your valuable inputs.

Will address all review comments and will update the diff.

4301c29... by Sankar Tanguturi

  Addressed few review comments from Chad Smith and Scott Moser.

  - Added a docstring to each test function.
  - Removed few unnecessary log messages.
  - Removed 'enable_nics' in a specific code path where it is really not
  required.
  - Fixed a typo in a comment.
  - Modified wait_for_imc_cfg_file to take dirpath as optional argument
  and look into /var/run/vmware-imc by default.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Updating the diff after addressing review comments from Chat and SCott. Thanks.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:4301c299aaffaf2fcc02e32d72e365464e5ae391
https://jenkins.ubuntu.com/server/job/cloud-init-ci/160/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/160/rebuild

review: Needs Fixing (continuous-integration)
13e4e08... by Sankar Tanguturi

Merge branch 'master' into current topic branch.

Resolved Conflicts:
 cloudinit/sources/DataSourceOVF.py
 tests/unittests/test_vmware_config_file.py

39d7aae... by Sankar Tanguturi

  Fixing few issues after the recent merge with master.

  - Use mask_to_net_prefix instead of mask2cidr
  - Fixing few issues reported by flake / tox
  - Modified the code to enable NICS only in successful case.
    In the case of failure, no request will be sent to
    the underlying hypervisor to enable the NICS.
  - Minor code refactoring.

Revision history for this message
Chad Smith (chad.smith) wrote :

Thanks for the comment updates and questions answered Sankar!
Looks like this branch only needs a git rebase to fixup the following conflicts:
Conflict in cloudinit/sources/DataSourceOVF.py
Conflict in tests/unittests/test_vmware_config_file.py

# from your branch: you probably already know this, it was kindof new to me
git pull origin/master
git rebase origin/master
# iterate through any conflicts, edit conflicts git add <conflict_files>; git rebase --continue

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Resolved conflicts and updated the diff. Thanks.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:39d7aaed101f06c4095890f40f442c9097b9e9eb
https://jenkins.ubuntu.com/server/job/cloud-init-ci/190/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/190/rebuild

review: Approve (continuous-integration)
a3d130d... by Sankar Tanguturi

  Add 'source interfaces.d/*.cfg' line to /etc/network/interfaces

  Modified the code to configure the /etc/network/interfaces file
  for debian guests. The 'configure' operation takes a backup of
  the file and writes 'source /etc/network/interfaces.d/*.cfg' line so that
  the cloud-init rendered config in /etc/network/interfaces.d/...cfg
  file will be used.

  Fixed the wait_for_imc_cfg_file function. Used os.path.isfile()

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

As discussed with Chad Smith today during 'cloud-init' meeting, updated the diff to add 'source /etc/network/interfaces.d/*.cfg' line to /etc/network/interfaces file during debian customization.

Thanks Chad.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:a3d130dc31e8f166c0bd89851a18d3f3220fdfc0
https://jenkins.ubuntu.com/server/job/cloud-init-ci/197/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/197/rebuild

review: Approve (continuous-integration)
891e2d3... by Sankar Tanguturi

  Generate a unique instance id for the ds so that re-customization
  will happen in cloud-init.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

In VMware guest customization workflow, we should be able to do re-customization. After discussing with Scott and Chad, modified the code to generate a unique id when there is a customization. This will force the cloud-init to do the re-customization.

Thanks Scott and Chad.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:891e2d38d3eb1541771531ede27c6562d6ca2ad4
https://jenkins.ubuntu.com/server/job/cloud-init-ci/202/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/202/rebuild

review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Chad Smith (chad.smith) wrote :

Good update on random instance-id Sankar, if you can instrument or add to an existing unit test that exposes the instance-id update +1 on this branch.

e0a6eb6... by Sankar Tanguturi

  Adding a new unit test case for the instance-id for DataSourceOVF

  Fixed the typo in config_nic.py
  Added a new unit test case for DataSourceOVF instance-id
  Fixed the docstring for the functions comments in config_nic.py

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Addressed all review comments from Chad and updated the diff.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:e0a6eb60c18ec3fa921ffe16afd8c2dbbc8931fb
https://jenkins.ubuntu.com/server/job/cloud-init-ci/208/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/208/rebuild

review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

Thanks Sankar for the fixes.
Since, as you mentioned, the python configuration engine overwrites /etc/network/interfaces anyway, I see this as an improvement over the existing image behavior (as any user-modified changes already got lost due to the perl conifiguration engine). At least this documents behavior with the comment
 # DO NOT EDIT THIS FILE BY HAND -- AUTOMATICALLY GENERATED BY cloud-init.

Thanks for the additional unit test on randomized instance-id. etc.
Just some minor nits on the unit tests.
Wherever possible please don't use assertTrue( some conditional) because unit test errors in those cases are not helpful, they only say "AssertionError: False is not true". But if you use assertEqual() or assertIn you get slightly more informative test errors:

AssertionError: 'iid-vmware-' not found in 'some-random-instance-id-CFtW7ekG'

I approve after your unittest fixups are there

f0b2b7c... by Sankar Tanguturi

  Fix test_vmware_config_file test script.

  Replaced self.assertTrue with self.assertIn and self.assertEqual
  at few places in the code.

Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

Fixed the new unit test case as Chad suggested. Thanks.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:f0b2b7cba92cd940ad3931a487087abb4126b019
https://jenkins.ubuntu.com/server/job/cloud-init-ci/241/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    FAILED: MAAS Compatability Testing

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/241/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

LGTM; +1 and thanks.

review: Approve
Revision history for this message
Sankar Tanguturi (sankaraditya) wrote :

> LGTM; +1 and thanks.

Thanks Chad.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:f0b2b7cba92cd940ad3931a487087abb4126b019
https://jenkins.ubuntu.com/server/job/cloud-init-ci/242/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/242/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:f0b2b7cba92cd940ad3931a487087abb4126b019
https://jenkins.ubuntu.com/server/job/cloud-init-ci/248/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/248/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

approving based on Chad's review and talking with Sankar.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
2index 73d3877..aa5f798 100644
3--- a/cloudinit/sources/DataSourceOVF.py
4+++ b/cloudinit/sources/DataSourceOVF.py
5@@ -51,6 +51,10 @@ class DataSourceOVF(sources.DataSource):
6 self.cfg = {}
7 self.supported_seed_starts = ("/", "file://")
8 self.vmware_customization_supported = True
9+ self._network_config = None
10+ self._vmware_nics_to_enable = None
11+ self._vmware_cust_conf = None
12+ self._vmware_cust_found = False
13
14 def __str__(self):
15 root = sources.DataSource.__str__(self)
16@@ -60,8 +64,8 @@ class DataSourceOVF(sources.DataSource):
17 found = []
18 md = {}
19 ud = ""
20- vmwarePlatformFound = False
21- vmwareImcConfigFilePath = ''
22+ vmwareImcConfigFilePath = None
23+ nicspath = None
24
25 defaults = {
26 "instance-id": "iid-dsovf",
27@@ -101,25 +105,26 @@ class DataSourceOVF(sources.DataSource):
28 logfunc=LOG.debug,
29 msg="waiting for configuration file",
30 func=wait_for_imc_cfg_file,
31- args=("/var/run/vmware-imc", "cust.cfg", max_wait))
32+ args=("cust.cfg", max_wait))
33
34 if vmwareImcConfigFilePath:
35 LOG.debug("Found VMware Customization Config File at %s",
36 vmwareImcConfigFilePath)
37+ nicspath = wait_for_imc_cfg_file(
38+ filename="nics.txt", maxwait=10, naplen=5)
39 else:
40 LOG.debug("Did not find VMware Customization Config File")
41 else:
42 LOG.debug("Customization for VMware platform is disabled.")
43
44 if vmwareImcConfigFilePath:
45- nics = ""
46+ self._vmware_nics_to_enable = ""
47 try:
48 cf = ConfigFile(vmwareImcConfigFilePath)
49- conf = Config(cf)
50- (md, ud, cfg) = read_vmware_imc(conf)
51- dirpath = os.path.dirname(vmwareImcConfigFilePath)
52- nics = get_nics_to_enable(dirpath)
53- markerid = conf.marker_id
54+ self._vmware_cust_conf = Config(cf)
55+ (md, ud, cfg) = read_vmware_imc(self._vmware_cust_conf)
56+ self._vmware_nics_to_enable = get_nics_to_enable(nicspath)
57+ markerid = self._vmware_cust_conf.marker_id
58 markerexists = check_marker_exists(markerid)
59 except Exception as e:
60 LOG.debug("Error parsing the customization Config File")
61@@ -127,28 +132,29 @@ class DataSourceOVF(sources.DataSource):
62 set_customization_status(
63 GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
64 GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
65- enable_nics(nics)
66- return False
67+ raise e
68 finally:
69 util.del_dir(os.path.dirname(vmwareImcConfigFilePath))
70 try:
71- LOG.debug("Applying the Network customization")
72- nicConfigurator = NicConfigurator(conf.nics)
73- nicConfigurator.configure()
74+ LOG.debug("Preparing the Network configuration")
75+ self._network_config = get_network_config_from_conf(
76+ self._vmware_cust_conf,
77+ True,
78+ True,
79+ self.distro.osfamily)
80 except Exception as e:
81- LOG.debug("Error applying the Network Configuration")
82 LOG.exception(e)
83 set_customization_status(
84 GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
85 GuestCustEventEnum.GUESTCUST_EVENT_NETWORK_SETUP_FAILED)
86- enable_nics(nics)
87- return False
88+ raise e
89+
90 if markerid and not markerexists:
91 LOG.debug("Applying password customization")
92 pwdConfigurator = PasswordConfigurator()
93- adminpwd = conf.admin_password
94+ adminpwd = self._vmware_cust_conf.admin_password
95 try:
96- resetpwd = conf.reset_password
97+ resetpwd = self._vmware_cust_conf.reset_password
98 if adminpwd or resetpwd:
99 pwdConfigurator.configure(adminpwd, resetpwd,
100 self.distro)
101@@ -159,7 +165,6 @@ class DataSourceOVF(sources.DataSource):
102 set_customization_status(
103 GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
104 GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
105- enable_nics(nics)
106 return False
107 if markerid:
108 LOG.debug("Handle marker creation")
109@@ -170,14 +175,18 @@ class DataSourceOVF(sources.DataSource):
110 set_customization_status(
111 GuestCustStateEnum.GUESTCUST_STATE_RUNNING,
112 GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED)
113- enable_nics(nics)
114 return False
115
116- vmwarePlatformFound = True
117+ self._vmware_cust_found = True
118+ found.append('vmware-tools')
119+
120+ # TODO: Need to set the status to DONE only when the
121+ # customization is done successfully.
122 set_customization_status(
123 GuestCustStateEnum.GUESTCUST_STATE_DONE,
124 GuestCustErrorEnum.GUESTCUST_ERROR_SUCCESS)
125- enable_nics(nics)
126+ enable_nics(self._vmware_nics_to_enable)
127+
128 else:
129 np = {'iso': transport_iso9660,
130 'vmware-guestd': transport_vmware_guestd, }
131@@ -192,7 +201,7 @@ class DataSourceOVF(sources.DataSource):
132 found.append(name)
133
134 # There was no OVF transports found
135- if len(found) == 0 and not vmwarePlatformFound:
136+ if len(found) == 0:
137 return False
138
139 if 'seedfrom' in md and md['seedfrom']:
140@@ -237,6 +246,10 @@ class DataSourceOVF(sources.DataSource):
141 def get_config_obj(self):
142 return self.cfg
143
144+ @property
145+ def network_config(self):
146+ return self._network_config
147+
148
149 class DataSourceOVFNet(DataSourceOVF):
150 def __init__(self, sys_cfg, distro, paths):
151@@ -268,12 +281,13 @@ def get_max_wait_from_cfg(cfg):
152 return max_wait
153
154
155-def wait_for_imc_cfg_file(dirpath, filename, maxwait=180, naplen=5):
156+def wait_for_imc_cfg_file(filename, maxwait=180, naplen=5,
157+ dirpath="/var/run/vmware-imc"):
158 waited = 0
159
160 while waited < maxwait:
161- fileFullPath = search_file(dirpath, filename)
162- if fileFullPath:
163+ fileFullPath = os.path.join(dirpath, filename)
164+ if os.path.isfile(fileFullPath):
165 return fileFullPath
166 LOG.debug("Waiting for VMware Customization Config File")
167 time.sleep(naplen)
168@@ -281,6 +295,26 @@ def wait_for_imc_cfg_file(dirpath, filename, maxwait=180, naplen=5):
169 return None
170
171
172+def get_network_config_from_conf(config, use_system_devices=True,
173+ configure=False, osfamily=None):
174+ nicConfigurator = NicConfigurator(config.nics, use_system_devices)
175+ nics_cfg_list = nicConfigurator.generate(configure, osfamily)
176+
177+ return get_network_config(nics_cfg_list,
178+ config.name_servers,
179+ config.dns_suffixes)
180+
181+
182+def get_network_config(nics=None, nameservers=None, search=None):
183+ config_list = nics
184+
185+ if nameservers or search:
186+ config_list.append({'type': 'nameserver', 'address': nameservers,
187+ 'search': search})
188+
189+ return {'version': 1, 'config': config_list}
190+
191+
192 # This will return a dict with some content
193 # meta-data, user-data, some config
194 def read_vmware_imc(config):
195@@ -296,6 +330,9 @@ def read_vmware_imc(config):
196 if config.timezone:
197 cfg['timezone'] = config.timezone
198
199+ # Generate a unique instance-id so that re-customization will
200+ # happen in cloud-init
201+ md['instance-id'] = "iid-vmware-" + util.rand_str(strlen=8)
202 return (md, ud, cfg)
203
204
205diff --git a/cloudinit/sources/helpers/vmware/imc/config_nic.py b/cloudinit/sources/helpers/vmware/imc/config_nic.py
206index 67ac21d..2fb07c5 100644
207--- a/cloudinit/sources/helpers/vmware/imc/config_nic.py
208+++ b/cloudinit/sources/helpers/vmware/imc/config_nic.py
209@@ -9,22 +9,48 @@ import logging
210 import os
211 import re
212
213+from cloudinit.net.network_state import mask_to_net_prefix
214 from cloudinit import util
215
216 logger = logging.getLogger(__name__)
217
218
219+def gen_subnet(ip, netmask):
220+ """
221+ Return the subnet for a given ip address and a netmask
222+ @return (str): the subnet
223+ @param ip: ip address
224+ @param netmask: netmask
225+ """
226+ ip_array = ip.split(".")
227+ mask_array = netmask.split(".")
228+ result = []
229+ for index in list(range(4)):
230+ result.append(int(ip_array[index]) & int(mask_array[index]))
231+
232+ return ".".join([str(x) for x in result])
233+
234+
235 class NicConfigurator(object):
236- def __init__(self, nics):
237+ def __init__(self, nics, use_system_devices=True):
238 """
239 Initialize the Nic Configurator
240 @param nics (list) an array of nics to configure
241+ @param use_system_devices (Bool) Get the MAC names from the system
242+ if this is True. If False, then mac names will be retrieved from
243+ the specified nics.
244 """
245 self.nics = nics
246 self.mac2Name = {}
247 self.ipv4PrimaryGateway = None
248 self.ipv6PrimaryGateway = None
249- self.find_devices()
250+
251+ if use_system_devices:
252+ self.find_devices()
253+ else:
254+ for nic in self.nics:
255+ self.mac2Name[nic.mac.lower()] = nic.name
256+
257 self._primaryNic = self.get_primary_nic()
258
259 def get_primary_nic(self):
260@@ -61,138 +87,163 @@ class NicConfigurator(object):
261
262 def gen_one_nic(self, nic):
263 """
264- Return the lines needed to configure a nic
265- @return (str list): the string list to configure the nic
266+ Return the config list needed to configure a nic
267+ @return (list): the subnets and routes list to configure the nic
268 @param nic (NicBase): the nic to configure
269 """
270- lines = []
271- name = self.mac2Name.get(nic.mac.lower())
272+ mac = nic.mac.lower()
273+ name = self.mac2Name.get(mac)
274 if not name:
275 raise ValueError('No known device has MACADDR: %s' % nic.mac)
276
277- if nic.onboot:
278- lines.append('auto %s' % name)
279+ nics_cfg_list = []
280+
281+ cfg = {'type': 'physical', 'name': name, 'mac_address': mac}
282+
283+ subnet_list = []
284+ route_list = []
285
286 # Customize IPv4
287- lines.extend(self.gen_ipv4(name, nic))
288+ (subnets, routes) = self.gen_ipv4(name, nic)
289+ subnet_list.extend(subnets)
290+ route_list.extend(routes)
291
292 # Customize IPv6
293- lines.extend(self.gen_ipv6(name, nic))
294+ (subnets, routes) = self.gen_ipv6(name, nic)
295+ subnet_list.extend(subnets)
296+ route_list.extend(routes)
297+
298+ cfg.update({'subnets': subnet_list})
299
300- lines.append('')
301+ nics_cfg_list.append(cfg)
302+ if route_list:
303+ nics_cfg_list.extend(route_list)
304
305- return lines
306+ return nics_cfg_list
307
308 def gen_ipv4(self, name, nic):
309 """
310- Return the lines needed to configure the IPv4 setting of a nic
311- @return (str list): the string list to configure the gateways
312- @param name (str): name of the nic
313+ Return the set of subnets and routes needed to configure the
314+ IPv4 settings of a nic
315+ @return (set): the set of subnet and routes to configure the gateways
316+ @param name (str): subnet and route list for the nic
317 @param nic (NicBase): the nic to configure
318 """
319- lines = []
320+
321+ subnet = {}
322+ route_list = []
323+
324+ if nic.onboot:
325+ subnet.update({'control': 'auto'})
326
327 bootproto = nic.bootProto.lower()
328 if nic.ipv4_mode.lower() == 'disabled':
329 bootproto = 'manual'
330- lines.append('iface %s inet %s' % (name, bootproto))
331
332 if bootproto != 'static':
333- return lines
334+ subnet.update({'type': 'dhcp'})
335+ return ([subnet], route_list)
336+ else:
337+ subnet.update({'type': 'static'})
338
339 # Static Ipv4
340 addrs = nic.staticIpv4
341 if not addrs:
342- return lines
343+ return ([subnet], route_list)
344
345 v4 = addrs[0]
346 if v4.ip:
347- lines.append(' address %s' % v4.ip)
348+ subnet.update({'address': v4.ip})
349 if v4.netmask:
350- lines.append(' netmask %s' % v4.netmask)
351+ subnet.update({'netmask': v4.netmask})
352
353 # Add the primary gateway
354 if nic.primary and v4.gateways:
355 self.ipv4PrimaryGateway = v4.gateways[0]
356- lines.append(' gateway %s metric 0' % self.ipv4PrimaryGateway)
357- return lines
358+ subnet.update({'gateway': self.ipv4PrimaryGateway})
359+ return [subnet]
360
361 # Add routes if there is no primary nic
362 if not self._primaryNic:
363- lines.extend(self.gen_ipv4_route(nic, v4.gateways))
364+ route_list.extend(self.gen_ipv4_route(nic,
365+ v4.gateways,
366+ v4.netmask))
367
368- return lines
369+ return ([subnet], route_list)
370
371- def gen_ipv4_route(self, nic, gateways):
372+ def gen_ipv4_route(self, nic, gateways, netmask):
373 """
374- Return the lines needed to configure additional Ipv4 route
375- @return (str list): the string list to configure the gateways
376+ Return the routes list needed to configure additional Ipv4 route
377+ @return (list): the route list to configure the gateways
378 @param nic (NicBase): the nic to configure
379 @param gateways (str list): the list of gateways
380 """
381- lines = []
382+ route_list = []
383+
384+ cidr = mask_to_net_prefix(netmask)
385
386 for gateway in gateways:
387- lines.append(' up route add default gw %s metric 10000' %
388- gateway)
389+ destination = "%s/%d" % (gen_subnet(gateway, netmask), cidr)
390+ route_list.append({'destination': destination,
391+ 'type': 'route',
392+ 'gateway': gateway,
393+ 'metric': 10000})
394
395- return lines
396+ return route_list
397
398 def gen_ipv6(self, name, nic):
399 """
400- Return the lines needed to configure the gateways for a nic
401- @return (str list): the string list to configure the gateways
402+ Return the set of subnets and routes needed to configure the
403+ gateways for a nic
404+ @return (set): the set of subnets and routes to configure the gateways
405 @param name (str): name of the nic
406 @param nic (NicBase): the nic to configure
407 """
408- lines = []
409
410 if not nic.staticIpv6:
411- return lines
412+ return ([], [])
413
414+ subnet_list = []
415 # Static Ipv6
416 addrs = nic.staticIpv6
417- lines.append('iface %s inet6 static' % name)
418- lines.append(' address %s' % addrs[0].ip)
419- lines.append(' netmask %s' % addrs[0].netmask)
420
421- for addr in addrs[1:]:
422- lines.append(' up ifconfig %s inet6 add %s/%s' % (name, addr.ip,
423- addr.netmask))
424- # Add the primary gateway
425- if nic.primary:
426- for addr in addrs:
427- if addr.gateway:
428- self.ipv6PrimaryGateway = addr.gateway
429- lines.append(' gateway %s' % self.ipv6PrimaryGateway)
430- return lines
431+ for addr in addrs:
432+ subnet = {'type': 'static6',
433+ 'address': addr.ip,
434+ 'netmask': addr.netmask}
435+ subnet_list.append(subnet)
436
437- # Add routes if there is no primary nic
438- if not self._primaryNic:
439- lines.extend(self._genIpv6Route(name, nic, addrs))
440+ # TODO: Add the primary gateway
441+
442+ route_list = []
443+ # TODO: Add routes if there is no primary nic
444+ # if not self._primaryNic:
445+ # route_list.extend(self._genIpv6Route(name, nic, addrs))
446
447- return lines
448+ return (subnet_list, route_list)
449
450 def _genIpv6Route(self, name, nic, addrs):
451- lines = []
452+ route_list = []
453
454 for addr in addrs:
455- lines.append(' up route -A inet6 add default gw '
456- '%s metric 10000' % addr.gateway)
457+ route_list.append({'type': 'route',
458+ 'gateway': addr.gateway,
459+ 'metric': 10000})
460+
461+ return route_list
462
463- return lines
464+ def generate(self, configure=False, osfamily=None):
465+ """Return the config elements that are needed to configure the nics"""
466+ if configure:
467+ logger.info("Configuring the interfaces file")
468+ self.configure(osfamily)
469
470- def generate(self):
471- """Return the lines that is needed to configure the nics"""
472- lines = []
473- lines.append('iface lo inet loopback')
474- lines.append('auto lo')
475- lines.append('')
476+ nics_cfg_list = []
477
478 for nic in self.nics:
479- lines.extend(self.gen_one_nic(nic))
480+ nics_cfg_list.extend(self.gen_one_nic(nic))
481
482- return lines
483+ return nics_cfg_list
484
485 def clear_dhcp(self):
486 logger.info('Clearing DHCP leases')
487@@ -201,11 +252,16 @@ class NicConfigurator(object):
488 util.subp(["pkill", "dhclient"], rcs=[0, 1])
489 util.subp(["rm", "-f", "/var/lib/dhcp/*"])
490
491- def configure(self):
492+ def configure(self, osfamily=None):
493 """
494- Configure the /etc/network/intefaces
495+ Configure the /etc/network/interfaces
496 Make a back up of the original
497 """
498+
499+ if not osfamily or osfamily != "debian":
500+ logger.info("Debian OS not detected. Skipping the configure step")
501+ return
502+
503 containingDir = '/etc/network'
504
505 interfaceFile = os.path.join(containingDir, 'interfaces')
506@@ -215,10 +271,13 @@ class NicConfigurator(object):
507 if not os.path.exists(originalFile) and os.path.exists(interfaceFile):
508 os.rename(interfaceFile, originalFile)
509
510- lines = self.generate()
511- with open(interfaceFile, 'w') as fp:
512- for line in lines:
513- fp.write('%s\n' % line)
514+ lines = [
515+ "# DO NOT EDIT THIS FILE BY HAND --"
516+ " AUTOMATICALLY GENERATED BY cloud-init",
517+ "source /etc/network/interfaces.d/*.cfg",
518+ ]
519+
520+ util.write_file(interfaceFile, content='\n'.join(lines))
521
522 self.clear_dhcp()
523
524diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
525index 1ab6bd4..4407525 100644
526--- a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
527+++ b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
528@@ -59,14 +59,16 @@ def set_customization_status(custstate, custerror, errormessage=None):
529 return (out, err)
530
531
532-# This will read the file nics.txt in the specified directory
533-# and return the content
534-def get_nics_to_enable(dirpath):
535- if not dirpath:
536+def get_nics_to_enable(nicsfilepath):
537+ """Reads the NICS from the specified file path and returns the content
538+
539+ @param nicsfilepath: Absolute file path to the NICS.txt file.
540+ """
541+
542+ if not nicsfilepath:
543 return None
544
545 NICS_SIZE = 1024
546- nicsfilepath = os.path.join(dirpath, "nics.txt")
547 if not os.path.exists(nicsfilepath):
548 return None
549
550diff --git a/tests/unittests/test_vmware_config_file.py b/tests/unittests/test_vmware_config_file.py
551index 03b36d3..2572ee8 100644
552--- a/tests/unittests/test_vmware_config_file.py
553+++ b/tests/unittests/test_vmware_config_file.py
554@@ -9,9 +9,13 @@ import logging
555 import sys
556
557 from .helpers import CiTestCase
558+from cloudinit.sources.DataSourceOVF import get_network_config_from_conf
559+from cloudinit.sources.DataSourceOVF import read_vmware_imc
560 from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProtoEnum
561 from cloudinit.sources.helpers.vmware.imc.config import Config
562 from cloudinit.sources.helpers.vmware.imc.config_file import ConfigFile
563+from cloudinit.sources.helpers.vmware.imc.config_nic import gen_subnet
564+from cloudinit.sources.helpers.vmware.imc.config_nic import NicConfigurator
565
566 logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
567 logger = logging.getLogger(__name__)
568@@ -20,6 +24,7 @@ logger = logging.getLogger(__name__)
569 class TestVmwareConfigFile(CiTestCase):
570
571 def test_utility_methods(self):
572+ """Tests basic utility methods of ConfigFile class"""
573 cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
574
575 cf.clear()
576@@ -43,7 +48,26 @@ class TestVmwareConfigFile(CiTestCase):
577 self.assertFalse(cf.should_keep_current_value("BAR"), "keepBar")
578 self.assertTrue(cf.should_remove_current_value("BAR"), "removeBar")
579
580+ def test_datasource_instance_id(self):
581+ """Tests instance id for the DatasourceOVF"""
582+ cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
583+
584+ instance_id_prefix = 'iid-vmware-'
585+
586+ conf = Config(cf)
587+
588+ (md1, _, _) = read_vmware_imc(conf)
589+ self.assertIn(instance_id_prefix, md1["instance-id"])
590+ self.assertEqual(len(md1["instance-id"]), len(instance_id_prefix) + 8)
591+
592+ (md2, _, _) = read_vmware_imc(conf)
593+ self.assertIn(instance_id_prefix, md2["instance-id"])
594+ self.assertEqual(len(md2["instance-id"]), len(instance_id_prefix) + 8)
595+
596+ self.assertNotEqual(md1["instance-id"], md2["instance-id"])
597+
598 def test_configfile_static_2nics(self):
599+ """Tests Config class for a configuration with two static NICs."""
600 cf = ConfigFile("tests/data/vmware/cust-static-2nic.cfg")
601
602 conf = Config(cf)
603@@ -81,6 +105,7 @@ class TestVmwareConfigFile(CiTestCase):
604 self.assertTrue(not nics[1].staticIpv6, "ipv61 dhcp")
605
606 def test_config_file_dhcp_2nics(self):
607+ """Tests Config class for a configuration with two DHCP NICs."""
608 cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
609
610 conf = Config(cf)
611@@ -117,5 +142,197 @@ class TestVmwareConfigFile(CiTestCase):
612 conf = Config(cf)
613 self.assertTrue(conf.reset_password, "reset password")
614
615+ def test_get_config_nameservers(self):
616+ """Tests DNS and nameserver settings in a configuration."""
617+ cf = ConfigFile("tests/data/vmware/cust-static-2nic.cfg")
618+
619+ config = Config(cf)
620+
621+ network_config = get_network_config_from_conf(config, False)
622+
623+ self.assertEqual(1, network_config.get('version'))
624+
625+ config_types = network_config.get('config')
626+ name_servers = None
627+ dns_suffixes = None
628+
629+ for type in config_types:
630+ if type.get('type') == 'nameserver':
631+ name_servers = type.get('address')
632+ dns_suffixes = type.get('search')
633+ break
634+
635+ self.assertEqual(['10.20.145.1', '10.20.145.2'],
636+ name_servers,
637+ "dns")
638+ self.assertEqual(['eng.vmware.com', 'proxy.vmware.com'],
639+ dns_suffixes,
640+ "suffixes")
641+
642+ def test_gen_subnet(self):
643+ """Tests if gen_subnet properly calculates network subnet from
644+ IPv4 address and netmask"""
645+ ip_subnet_list = [['10.20.87.253', '255.255.252.0', '10.20.84.0'],
646+ ['10.20.92.105', '255.255.252.0', '10.20.92.0'],
647+ ['192.168.0.10', '255.255.0.0', '192.168.0.0']]
648+ for entry in ip_subnet_list:
649+ self.assertEqual(entry[2], gen_subnet(entry[0], entry[1]),
650+ "Subnet for a specified ip and netmask")
651+
652+ def test_get_config_dns_suffixes(self):
653+ """Tests if get_network_config_from_conf properly
654+ generates nameservers and dns settings from a
655+ specified configuration"""
656+ cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
657+
658+ config = Config(cf)
659+
660+ network_config = get_network_config_from_conf(config, False)
661+
662+ self.assertEqual(1, network_config.get('version'))
663+
664+ config_types = network_config.get('config')
665+ name_servers = None
666+ dns_suffixes = None
667+
668+ for type in config_types:
669+ if type.get('type') == 'nameserver':
670+ name_servers = type.get('address')
671+ dns_suffixes = type.get('search')
672+ break
673+
674+ self.assertEqual([],
675+ name_servers,
676+ "dns")
677+ self.assertEqual(['eng.vmware.com'],
678+ dns_suffixes,
679+ "suffixes")
680+
681+ def test_get_nics_list_dhcp(self):
682+ """Tests if NicConfigurator properly calculates network subnets
683+ for a configuration with a list of DHCP NICs"""
684+ cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
685+
686+ config = Config(cf)
687+
688+ nicConfigurator = NicConfigurator(config.nics, False)
689+ nics_cfg_list = nicConfigurator.generate()
690+
691+ self.assertEqual(2, len(nics_cfg_list), "number of config elements")
692+
693+ nic1 = {'name': 'NIC1'}
694+ nic2 = {'name': 'NIC2'}
695+ for cfg in nics_cfg_list:
696+ if cfg.get('name') == nic1.get('name'):
697+ nic1.update(cfg)
698+ elif cfg.get('name') == nic2.get('name'):
699+ nic2.update(cfg)
700+
701+ self.assertEqual('physical', nic1.get('type'), 'type of NIC1')
702+ self.assertEqual('NIC1', nic1.get('name'), 'name of NIC1')
703+ self.assertEqual('00:50:56:a6:8c:08', nic1.get('mac_address'),
704+ 'mac address of NIC1')
705+ subnets = nic1.get('subnets')
706+ self.assertEqual(1, len(subnets), 'number of subnets for NIC1')
707+ subnet = subnets[0]
708+ self.assertEqual('dhcp', subnet.get('type'), 'DHCP type for NIC1')
709+ self.assertEqual('auto', subnet.get('control'), 'NIC1 Control type')
710+
711+ self.assertEqual('physical', nic2.get('type'), 'type of NIC2')
712+ self.assertEqual('NIC2', nic2.get('name'), 'name of NIC2')
713+ self.assertEqual('00:50:56:a6:5a:de', nic2.get('mac_address'),
714+ 'mac address of NIC2')
715+ subnets = nic2.get('subnets')
716+ self.assertEqual(1, len(subnets), 'number of subnets for NIC2')
717+ subnet = subnets[0]
718+ self.assertEqual('dhcp', subnet.get('type'), 'DHCP type for NIC2')
719+ self.assertEqual('auto', subnet.get('control'), 'NIC2 Control type')
720+
721+ def test_get_nics_list_static(self):
722+ """Tests if NicConfigurator properly calculates network subnets
723+ for a configuration with 2 static NICs"""
724+ cf = ConfigFile("tests/data/vmware/cust-static-2nic.cfg")
725+
726+ config = Config(cf)
727+
728+ nicConfigurator = NicConfigurator(config.nics, False)
729+ nics_cfg_list = nicConfigurator.generate()
730+
731+ self.assertEqual(5, len(nics_cfg_list), "number of elements")
732+
733+ nic1 = {'name': 'NIC1'}
734+ nic2 = {'name': 'NIC2'}
735+ route_list = []
736+ for cfg in nics_cfg_list:
737+ cfg_type = cfg.get('type')
738+ if cfg_type == 'physical':
739+ if cfg.get('name') == nic1.get('name'):
740+ nic1.update(cfg)
741+ elif cfg.get('name') == nic2.get('name'):
742+ nic2.update(cfg)
743+ elif cfg_type == 'route':
744+ route_list.append(cfg)
745+
746+ self.assertEqual('physical', nic1.get('type'), 'type of NIC1')
747+ self.assertEqual('NIC1', nic1.get('name'), 'name of NIC1')
748+ self.assertEqual('00:50:56:a6:8c:08', nic1.get('mac_address'),
749+ 'mac address of NIC1')
750+
751+ subnets = nic1.get('subnets')
752+ self.assertEqual(2, len(subnets), 'Number of subnets')
753+
754+ static_subnet = []
755+ static6_subnet = []
756+
757+ for subnet in subnets:
758+ subnet_type = subnet.get('type')
759+ if subnet_type == 'static':
760+ static_subnet.append(subnet)
761+ elif subnet_type == 'static6':
762+ static6_subnet.append(subnet)
763+ else:
764+ self.assertEqual(True, False, 'Unknown type')
765+
766+ self.assertEqual(1, len(static_subnet), 'Number of static subnet')
767+ self.assertEqual(1, len(static6_subnet), 'Number of static6 subnet')
768+
769+ subnet = static_subnet[0]
770+ self.assertEqual('10.20.87.154', subnet.get('address'),
771+ 'IPv4 address of static subnet')
772+ self.assertEqual('255.255.252.0', subnet.get('netmask'),
773+ 'NetMask of static subnet')
774+ self.assertEqual('auto', subnet.get('control'),
775+ 'control for static subnet')
776+
777+ subnet = static6_subnet[0]
778+ self.assertEqual('fc00:10:20:87::154', subnet.get('address'),
779+ 'IPv6 address of static subnet')
780+ self.assertEqual('64', subnet.get('netmask'),
781+ 'NetMask of static6 subnet')
782+
783+ route_set = set(['10.20.87.253', '10.20.87.105', '192.168.0.10'])
784+ for route in route_list:
785+ self.assertEqual(10000, route.get('metric'), 'metric of route')
786+ gateway = route.get('gateway')
787+ if gateway in route_set:
788+ route_set.discard(gateway)
789+ else:
790+ self.assertEqual(True, False, 'invalid gateway %s' % (gateway))
791+
792+ self.assertEqual('physical', nic2.get('type'), 'type of NIC2')
793+ self.assertEqual('NIC2', nic2.get('name'), 'name of NIC2')
794+ self.assertEqual('00:50:56:a6:ef:7d', nic2.get('mac_address'),
795+ 'mac address of NIC2')
796+
797+ subnets = nic2.get('subnets')
798+ self.assertEqual(1, len(subnets), 'Number of subnets for NIC2')
799+
800+ subnet = subnets[0]
801+ self.assertEqual('static', subnet.get('type'), 'Subnet type')
802+ self.assertEqual('192.168.6.102', subnet.get('address'),
803+ 'Subnet address')
804+ self.assertEqual('255.255.0.0', subnet.get('netmask'),
805+ 'Subnet netmask')
806+
807
808 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches