Merge ~dojordan/cloud-init:azure-preprovisioning into cloud-init:master

Proposed by Douglas Jordan
Status: Merged
Approved by: Chad Smith
Approved revision: 573332c54ac77114beb0cdf13e0e8865f241ea18
Merge reported by: Chad Smith
Merged at revision: c03bdd3d8ed762cada813c5e95a40b14d2047b57
Proposed branch: ~dojordan/cloud-init:azure-preprovisioning
Merge into: cloud-init:master
Diff against target: 757 lines (+397/-53)
11 files modified
.gitignore (+1/-0)
cloudinit/net/dhcp.py (+42/-1)
cloudinit/net/network_state.py (+12/-0)
cloudinit/sources/DataSourceAzure.py (+129/-9)
cloudinit/sources/DataSourceEc2.py (+7/-16)
cloudinit/sources/helpers/azure.py (+14/-8)
cloudinit/temp_utils.py (+8/-3)
cloudinit/url_helper.py (+20/-9)
tests/unittests/test_datasource/test_azure.py (+151/-6)
tests/unittests/test_datasource/test_ec2.py (+1/-1)
tests/unittests/test_net.py (+12/-0)
Reviewer Review Type Date Requested Status
Chad Smith Approve
Server Team CI bot continuous-integration Approve
Scott Moser Approve
Paul Meyer (community) Approve
Review via email: mp+334341@code.launchpad.net

Commit message

Azure VM Preprovisioning support.

This change will enable azure vms to report provisioning has completed
twice, first to tell the fabric it has completed then a second time to
enable customer settings. The datasource for the second provisioning is
the Instance Metadata Service (IMDS),and the VM will poll indefinitely for
the new ovf-env.xml from IMDS.

This branch introduces EphemeralDHCPv4 which encapsulates common logic
used by both DataSourceEc2 an DataSourceAzure for temporary DHCP
interactions without side-effects.

LP: #1734991

Description of the change

Azure VM Preprovisioning support.

This change will enable azure vms to report provisioning has completed twice, first to tell the fabric it has completed then a second time to enable customer settings. The datasource for the second provisioning is the Instance Metadata Service (IMDS), and the VM will poll indefinitely for the new ovf-env.xml from IMDS.

LP: 1734991

To post a comment you must log in.
Revision history for this message
Paul Meyer (paul-meyer) wrote :

Looks good to me (after you fix the tests and flake8's)

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

Fix your commit message (press 'Set commit message' above).

 Summary
 <blank>
 More info

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

FAILED: Continuous integration, rev:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/553/
Executed test runs:
    FAILED: Checkout

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Douglas Jordan (dojordan) wrote :

> FAILED: Continuous integration, rev:
> https://jenkins.ubuntu.com/server/job/cloud-init-ci/553/
> Executed test runs:
> FAILED: Checkout
>
> Click here to trigger a rebuild:
> https://jenkins.ubuntu.com/server/job/cloud-init-ci/553/rebuild

Looks like the git checkout failed. Thoughts?
stderr: remote: Authorisation required.
fatal: Authentication failed for 'https://git.launchpad.net/~dojordan/cloud-init:azure-preprovisioning/'

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

FAILED: Continuous integration, rev:72e423b4a0827dd954170749812b13aba761f399
https://jenkins.ubuntu.com/server/job/cloud-init-ci/562/
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/562/rebuild

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

FAILED: Continuous integration, rev:384b7731e99338903d91da641267ed84a7470669
https://jenkins.ubuntu.com/server/job/cloud-init-ci/567/
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/567/rebuild

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

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

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

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

PASSED: Continuous integration, rev:6021da2b61a55102ec7637d6fea54e02db1c1c92
https://jenkins.ubuntu.com/server/job/cloud-init-ci/576/
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/576/rebuild

review: Approve (continuous-integration)
Revision history for this message
Paul Meyer (paul-meyer) :
review: Approve
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Douglas Jordan (dojordan) wrote :

Thanks for the feedback. I've resolved your comments.

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

PASSED: Continuous integration, rev:ff0b7b0b5fdd1512c7ec2ccbee0f92afde5c733d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/579/
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/579/rebuild

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

Douglas, thanks for working this. I'm going to mark it work in progress as you address or respond to comments. When you'd like another review pass please just mark it back to "Needs review"

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

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

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

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

PASSED: Continuous integration, rev:e06a762741953a5c55d843b0dcfd454111655cea
https://jenkins.ubuntu.com/server/job/cloud-init-ci/595/
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/595/rebuild

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

Thanks for the rework and comments. One more pass on this. I'll test against stock azure xenial today and report here.

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

PASSED: Continuous integration, rev:c500a0184b8bc7c7ba03b1a884896702a761959f
https://jenkins.ubuntu.com/server/job/cloud-init-ci/612/
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/612/rebuild

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

Ok one last nit from me and it looks good. I need a bit of testing still on this but content looks good.

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

one last try on the nit

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

PASSED: Continuous integration, rev:3c9509671f05d8dfc990b5ce35a615c9cd7b442c
https://jenkins.ubuntu.com/server/job/cloud-init-ci/618/
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/618/rebuild

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

There are some comments in line.
I'm not sure I fully understand all of this.

I could be wrong here, but I think you're using
bounce_network_with_azure_hostname in order to interact with IMDS_URL.

We have 2 ways now of brining up ephemeral networking for this purpose:
 a.) Chad has recently added code in the EC2 metadata service using cloudinit/net/dhcp.py .
 b.) the digital ocean datasource has code to negotiate a ipv4 link local address.

If 'b' is sufficient, I'd prefer that, but either one I'd prefer to the bounce_network_ which i think may actually not work for you if you rebased to trunk.

As mentioned in IRC, I'm still concerned about systemd giving up and deciding that boot has failed after some amount of time polling on a metadata service. As Douglas pointed out, cloud-init has timeouts set to 0 and is a 'oneshot', so *its* timeout is not an issue, but I think that things that it runs 'Before' or (pre-networking or other) might end up timing out.

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

some things there need fixing, definitely need rebase to trunk (I suspect you'll have conflicts), but if not, some thought is needed.

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

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

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

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

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

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

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

PASSED: Continuous integration, rev:93d88e25a1db5220cf087bba80a1ff15dd7f7fd8
https://jenkins.ubuntu.com/server/job/cloud-init-ci/670/
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/670/rebuild

review: Approve (continuous-integration)
Revision history for this message
Douglas Jordan (dojordan) wrote :

Thanks for the comments Scott-

The reason for the bounce_network_with_azure_hostname is actually to get the new IP address. We can't use the digital ocean ipv4 link local approach as the way our IMDS server identifies which VM is talking to it is via the mac address and the ip address. When the control plane updates the DHCP server with the new IP, the vm has to trigger dhcp and acquire the new address. In our polling loop, we call bounce_networking_with_azure_hostname to trigger dhcp when we get an exception. This is because IMDS will not even handle our request if the mac/ip does not match what was expected. In windows, we get around this by just disconnecting and reconnecting the NIC, however on Ubuntu 16.04, we observed the link had to be disconnected for a very long time (>10s) for this behavior to occur. Hopefully, with systemd-networkd, this issue will be fixed. For now however, we are targeting 16.04 for pre=provisioning, and we will continue to test artful/bionic with the link state disconnect / reconnect approach.

> There are some comments in line.
> I'm not sure I fully understand all of this.
>
> I could be wrong here, but I think you're using
> bounce_network_with_azure_hostname in order to interact with IMDS_URL.
>
> We have 2 ways now of brining up ephemeral networking for this purpose:
> a.) Chad has recently added code in the EC2 metadata service using
> cloudinit/net/dhcp.py .
> b.) the digital ocean datasource has code to negotiate a ipv4 link local
> address.
>
> If 'b' is sufficient, I'd prefer that, but either one I'd prefer to the
> bounce_network_ which i think may actually not work for you if you rebased to
> trunk.
>
> As mentioned in IRC, I'm still concerned about systemd giving up and deciding
> that boot has failed after some amount of time polling on a metadata service.
> As Douglas pointed out, cloud-init has timeouts set to 0 and is a 'oneshot',
> so *its* timeout is not an issue, but I think that things that it runs
> 'Before' or (pre-networking or other) might end up timing out.

Revision history for this message
Sushant Sharma (sushantsharma) :
Revision history for this message
Sushant Sharma (sushantsharma) wrote :

<not sure why the inline comment does not show up>

Hi Douglas, Can you please add a comment before line 83 (where you catch exception and re-DHCP) saying that this is temporary. We plan to add a networking module specific to azure in cloud-init and will address re-DHCP need in that module. The plan is to submit that module for review in a week or so. Thanks!

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

PASSED: Continuous integration, rev:fc0154011d4a3f0418fa71cfa5330bd9ac837dfd
https://jenkins.ubuntu.com/server/job/cloud-init-ci/679/
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/679/rebuild

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

As it is right now, 'bounce_network_with_azure_hostname' is a no-op
on Ubuntu 18.04 (bionic) or any system without 'ifup'.
So while you want this fixed in 16.04, the general Ubuntu development
path insists that we make things work on the development release first
and the SRU them back to 16.04.

In summary: I can't let this in as broken on bionic. We'll need to find
a way to make that work on 18.04.

Your commit message says:
 | This change will enable azure vms to report provisioning has completed
 | twice, first to tell the fabric it has completed then a second time to
 | enable customer settings.

Is that true, will it *always* report reprovisioning has completed twice?

there are some small inlinel things also.

those are the big things though. thanks for your work.

Revision history for this message
Douglas Jordan (dojordan) wrote :

Regarding the DHCP stuff: We are exploring an alternate solution to bounce the nic from hyper-v, but in the mean time we would like to get this checked in. So an alternate solution for bionic would be to simply change the hostname. This way, systemd-networkd will keep re triggering DHCP. Once we get the final ovf_env.xml from IMDS, we will actually apply the real, customer provided hostname.

Regarding "Is that true, will it *always* report reprovisioning has completed twice?"-
tldr; yes. Technically, it will report "provisioning" has completed twice, while we are calling the second incarnation "reprovisioning"

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

PASSED: Continuous integration, rev:7f23e5c4808a9c647cd4d5277625a723a58b132b
https://jenkins.ubuntu.com/server/job/cloud-init-ci/685/
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/685/rebuild

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

Chad and I had experience that lead us to believe that systemd-networkd just re-requested lease on hostname change. If that is true, then this seems reasonable.

Michael Hudson-Doyle in bug 1739516 indicates that we were mistaken.

Need to sort that out, and then I think i'm happy with this.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

PASSED: Continuous integration, rev:6610952f0326eb7cfbbe984b92197ef6373e11f9
https://jenkins.ubuntu.com/server/job/cloud-init-ci/699/
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/699/rebuild

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

Overall, I like it.

Some comments / questions inline.

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

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

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

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

PASSED: Continuous integration, rev:e83238aae58c5b0bf09c07bc3cc96f27596bbe3a
https://jenkins.ubuntu.com/server/job/cloud-init-ci/701/
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/701/rebuild

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

PASSED: Continuous integration, rev:280c3c383fcd04678d354e4928410cdf947fefd6
https://jenkins.ubuntu.com/server/job/cloud-init-ci/702/
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/702/rebuild

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

I have a couple comments.
a.) i don't like the use of 'unknown-245'. we really should fix that somewhere centrally, but i wouldnt put that on Douglas to do. We should have something that finds that name and names it more reasonably.

b.) i want to make sure we're not doing multiple dhcp in the *non* provisioning path now.

c.) at some point we can start using the metdata service for data always?

d.) as discussed in #cloud-init http://paste.ubuntu.com/26412425/

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

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

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

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

PASSED: Continuous integration, rev:f030e24e6fe1e518a52ff4a864583b831065dcd1
https://jenkins.ubuntu.com/server/job/cloud-init-ci/705/
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/705/rebuild

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

Thanks for the additional passes on the Douglas. Looks really good. Some quick inline comments/nits.

Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Scott Moser (smoser) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

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

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

PASSED: Continuous integration, rev:a73a578fd9634ee6633f900150862e318cf1e020
https://jenkins.ubuntu.com/server/job/cloud-init-ci/709/
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/709/rebuild

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

looks good to me. will test on azure/ec2 before landing, minor inline comments on latest branch, but I'm good to land after ec2/azure testing.

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

PASSED: Continuous integration, rev:d114c6377c3f7cbc04d20255b2ae5a8be04dfcb7
https://jenkins.ubuntu.com/server/job/cloud-init-ci/712/
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/712/rebuild

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

Validated this patchset on amazon with no errors on clean install or upgrade path.

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

I'd like a response to a comment in my last review:
| without passing in a nic to EphemeralDHCPv4, it will end up calling
| 'find_fallback_nic' each time. I wonder if that behavior is better or
| worse than picking one device and just sticking with it throuh the use
| of this class.

I dont expect that this is a big deal, but a hotplug of a nic during
the wait for the IMDS could result in the interface that is used for
dhcp to be different on retries than on the first.

Revision history for this message
Douglas Jordan (dojordan) wrote :

Sorry about that, totally missed this one. I agree, I think we should pin the context manager to the nic. When we get an exception, I think it is reasonable to look for the fallback nic again. I'll make that change now.

> I'd like a response to a comment in my last review:
> | without passing in a nic to EphemeralDHCPv4, it will end up calling
> | 'find_fallback_nic' each time. I wonder if that behavior is better or
> | worse than picking one device and just sticking with it throuh the use
> | of this class.
>
> I dont expect that this is a big deal, but a hotplug of a nic during
> the wait for the IMDS could result in the interface that is used for
> dhcp to be different on retries than on the first.

Revision history for this message
Douglas Jordan (dojordan) wrote :

Actually, ignore my comment. I think that is the correct behavior. For some reason, if we do hot plug a nic and it gets a new interface name, we would like to use it during the polling loop.

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

thanks Douglas.
I like it.
If c-i approves, then so do I.

Thanks for your patience.

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

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

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

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

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

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

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

PASSED: Continuous integration, rev:f466bd87777e5375f07d44d0fc1b5bfe725b26ef
https://jenkins.ubuntu.com/server/job/cloud-init-ci/717/
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/717/rebuild

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

On checking latest commit on this branch I can't get ahold of the azure instance after reboot on bionic. douglas just confirmed same behavior on artful too.

sudo cloud-init clean --logs --reboot with a deb from this branch doesn't come back up for some reason.

Douglas picked up my boot logs for me https://paste.ubuntu.com/26447610/
haven't dug into what is the matter here. I briefly saw cloud-init get through the datasource get_data and modules:config, but no ssh is running on the ip.

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

marked needs review so I don't accidentally publish this. Until we get to the bottom of why bionic artful aren't coming back after reboot.

0a4a1a0... by Douglas Jordan

Fixing bool('false') == true bug.

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

PASSED: Continuous integration, rev:0a4a1a092bc87b25f181887e892007937c236001
https://jenkins.ubuntu.com/server/job/cloud-init-ci/730/
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/730/rebuild

review: Approve (continuous-integration)
3aa23e9... by Douglas Jordan

Using warning log to hit console.

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

PASSED: Continuous integration, rev:3aa23e90ab35ac5d7aeee40b11e02a81bb8f7fdd
https://jenkins.ubuntu.com/server/job/cloud-init-ci/731/
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/731/rebuild

review: Approve (continuous-integration)
573332c... by Douglas Jordan

Using proper util for string to bool.

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

PASSED: Continuous integration, rev:573332c54ac77114beb0cdf13e0e8865f241ea18
https://jenkins.ubuntu.com/server/job/cloud-init-ci/732/
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/732/rebuild

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

Validated on xenial and bionic that latest branch no longer blocks clean boot scenario.

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

Hello Douglas and Paul, or anyone else affected,

As part of a stable release update, this changeset is committed and accepted cloud-init into artful-proposed and xenial-proposed as cloud-init version 17.2-35-gf576b2a2-0ubuntu1. The package is now available in the specific -proposed repository and can be installed and tested on artful and xenial instances with something like the following:

sed -i 's/ xenial / xenial-proposed /' /etc/apt/sources
sudo apt-get update
sudo apt-get install cloud-init;
sudo cloud-init clean --logs --reboot; # For a fresh install run

If there are concerns please comment on LP: #1747059.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/.gitignore b/.gitignore
index b0500a6..75565ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ parts
10prime10prime
11stage11stage
12*.snap12*.snap
13*.cover
diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
index 875a460..087c0c0 100644
--- a/cloudinit/net/dhcp.py
+++ b/cloudinit/net/dhcp.py
@@ -10,7 +10,9 @@ import os
10import re10import re
11import signal11import signal
1212
13from cloudinit.net import find_fallback_nic, get_devicelist13from cloudinit.net import (
14 EphemeralIPv4Network, find_fallback_nic, get_devicelist)
15from cloudinit.net.network_state import mask_and_ipv4_to_bcast_addr as bcip
14from cloudinit import temp_utils16from cloudinit import temp_utils
15from cloudinit import util17from cloudinit import util
16from six import StringIO18from six import StringIO
@@ -29,6 +31,45 @@ class InvalidDHCPLeaseFileError(Exception):
29 pass31 pass
3032
3133
34class NoDHCPLeaseError(Exception):
35 """Raised when unable to get a DHCP lease."""
36 pass
37
38
39class EphemeralDHCPv4(object):
40 def __init__(self, iface=None):
41 self.iface = iface
42 self._ephipv4 = None
43
44 def __enter__(self):
45 try:
46 leases = maybe_perform_dhcp_discovery(self.iface)
47 except InvalidDHCPLeaseFileError:
48 raise NoDHCPLeaseError()
49 if not leases:
50 raise NoDHCPLeaseError()
51 lease = leases[-1]
52 LOG.debug("Received dhcp lease on %s for %s/%s",
53 lease['interface'], lease['fixed-address'],
54 lease['subnet-mask'])
55 nmap = {'interface': 'interface', 'ip': 'fixed-address',
56 'prefix_or_mask': 'subnet-mask',
57 'broadcast': 'broadcast-address',
58 'router': 'routers'}
59 kwargs = dict([(k, lease.get(v)) for k, v in nmap.items()])
60 if not kwargs['broadcast']:
61 kwargs['broadcast'] = bcip(kwargs['prefix_or_mask'], kwargs['ip'])
62 ephipv4 = EphemeralIPv4Network(**kwargs)
63 ephipv4.__enter__()
64 self._ephipv4 = ephipv4
65 return lease
66
67 def __exit__(self, excp_type, excp_value, excp_traceback):
68 if not self._ephipv4:
69 return
70 self._ephipv4.__exit__(excp_type, excp_value, excp_traceback)
71
72
32def maybe_perform_dhcp_discovery(nic=None):73def maybe_perform_dhcp_discovery(nic=None):
33 """Perform dhcp discovery if nic valid and dhclient command exists.74 """Perform dhcp discovery if nic valid and dhclient command exists.
3475
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index 31738c7..fe667d8 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -961,4 +961,16 @@ def mask_to_net_prefix(mask):
961 return ipv4_mask_to_net_prefix(mask)961 return ipv4_mask_to_net_prefix(mask)
962962
963963
964def mask_and_ipv4_to_bcast_addr(mask, ip):
965 """Calculate the broadcast address from the subnet mask and ip addr.
966
967 Supports ipv4 only."""
968 ip_bin = int(''.join([bin(int(x) + 256)[3:] for x in ip.split('.')]), 2)
969 mask_dec = ipv4_mask_to_net_prefix(mask)
970 bcast_bin = ip_bin | (2**(32 - mask_dec) - 1)
971 bcast_str = '.'.join([str(bcast_bin >> (i << 3) & 0xFF)
972 for i in range(4)[::-1]])
973 return bcast_str
974
975
964# vi: ts=4 expandtab976# vi: ts=4 expandtab
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index d1d0975..4bcbf3a 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -11,13 +11,16 @@ from functools import partial
11import os11import os
12import os.path12import os.path
13import re13import re
14from time import time
14from xml.dom import minidom15from xml.dom import minidom
15import xml.etree.ElementTree as ET16import xml.etree.ElementTree as ET
1617
17from cloudinit import log as logging18from cloudinit import log as logging
18from cloudinit import net19from cloudinit import net
20from cloudinit.net.dhcp import EphemeralDHCPv4
19from cloudinit import sources21from cloudinit import sources
20from cloudinit.sources.helpers.azure import get_metadata_from_fabric22from cloudinit.sources.helpers.azure import get_metadata_from_fabric
23from cloudinit.url_helper import readurl, wait_for_url, UrlError
21from cloudinit import util24from cloudinit import util
2225
23LOG = logging.getLogger(__name__)26LOG = logging.getLogger(__name__)
@@ -44,6 +47,9 @@ LEASE_FILE = '/var/lib/dhcp/dhclient.eth0.leases'
44DEFAULT_FS = 'ext4'47DEFAULT_FS = 'ext4'
45# DMI chassis-asset-tag is set static for all azure instances48# DMI chassis-asset-tag is set static for all azure instances
46AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77'49AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77'
50REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds"
51IMDS_URL = "http://169.254.169.254/metadata/reprovisiondata"
52IMDS_RETRIES = 5
4753
4854
49def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):55def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):
@@ -276,19 +282,20 @@ class DataSourceAzure(sources.DataSource):
276282
277 with temporary_hostname(azure_hostname, self.ds_cfg,283 with temporary_hostname(azure_hostname, self.ds_cfg,
278 hostname_command=hostname_command) \284 hostname_command=hostname_command) \
279 as previous_hostname:285 as previous_hn:
280 if (previous_hostname is not None and286 if (previous_hn is not None and
281 util.is_true(self.ds_cfg.get('set_hostname'))):287 util.is_true(self.ds_cfg.get('set_hostname'))):
282 cfg = self.ds_cfg['hostname_bounce']288 cfg = self.ds_cfg['hostname_bounce']
283289
284 # "Bouncing" the network290 # "Bouncing" the network
285 try:291 try:
286 perform_hostname_bounce(hostname=azure_hostname,292 return perform_hostname_bounce(hostname=azure_hostname,
287 cfg=cfg,293 cfg=cfg,
288 prev_hostname=previous_hostname)294 prev_hostname=previous_hn)
289 except Exception as e:295 except Exception as e:
290 LOG.warning("Failed publishing hostname: %s", e)296 LOG.warning("Failed publishing hostname: %s", e)
291 util.logexc(LOG, "handling set_hostname failed")297 util.logexc(LOG, "handling set_hostname failed")
298 return False
292299
293 def get_metadata_from_agent(self):300 def get_metadata_from_agent(self):
294 temp_hostname = self.metadata.get('local-hostname')301 temp_hostname = self.metadata.get('local-hostname')
@@ -345,15 +352,20 @@ class DataSourceAzure(sources.DataSource):
345 ddir = self.ds_cfg['data_dir']352 ddir = self.ds_cfg['data_dir']
346353
347 candidates = [self.seed_dir]354 candidates = [self.seed_dir]
355 if os.path.isfile(REPROVISION_MARKER_FILE):
356 candidates.insert(0, "IMDS")
348 candidates.extend(list_possible_azure_ds_devs())357 candidates.extend(list_possible_azure_ds_devs())
349 if ddir:358 if ddir:
350 candidates.append(ddir)359 candidates.append(ddir)
351360
352 found = None361 found = None
353362 reprovision = False
354 for cdev in candidates:363 for cdev in candidates:
355 try:364 try:
356 if cdev.startswith("/dev/"):365 if cdev == "IMDS":
366 ret = None
367 reprovision = True
368 elif cdev.startswith("/dev/"):
357 if util.is_FreeBSD():369 if util.is_FreeBSD():
358 ret = util.mount_cb(cdev, load_azure_ds_dir,370 ret = util.mount_cb(cdev, load_azure_ds_dir,
359 mtype="udf", sync=False)371 mtype="udf", sync=False)
@@ -370,6 +382,8 @@ class DataSourceAzure(sources.DataSource):
370 LOG.warning("%s was not mountable", cdev)382 LOG.warning("%s was not mountable", cdev)
371 continue383 continue
372384
385 if reprovision or self._should_reprovision(ret):
386 ret = self._reprovision()
373 (md, self.userdata_raw, cfg, files) = ret387 (md, self.userdata_raw, cfg, files) = ret
374 self.seed = cdev388 self.seed = cdev
375 self.metadata = util.mergemanydict([md, DEFAULT_METADATA])389 self.metadata = util.mergemanydict([md, DEFAULT_METADATA])
@@ -428,6 +442,83 @@ class DataSourceAzure(sources.DataSource):
428 LOG.debug("negotiating already done for %s",442 LOG.debug("negotiating already done for %s",
429 self.get_instance_id())443 self.get_instance_id())
430444
445 def _poll_imds(self, report_ready=True):
446 """Poll IMDS for the new provisioning data until we get a valid
447 response. Then return the returned JSON object."""
448 url = IMDS_URL + "?api-version=2017-04-02"
449 headers = {"Metadata": "true"}
450 LOG.debug("Start polling IMDS")
451
452 def sleep_cb(response, loop_n):
453 return 1
454
455 def exception_cb(msg, exception):
456 if isinstance(exception, UrlError) and exception.code == 404:
457 return
458 LOG.warning("Exception during polling. Will try DHCP.",
459 exc_info=True)
460
461 # If we get an exception while trying to call IMDS, we
462 # call DHCP and setup the ephemeral network to acquire the new IP.
463 raise exception
464
465 need_report = report_ready
466 for i in range(IMDS_RETRIES):
467 try:
468 with EphemeralDHCPv4() as lease:
469 if need_report:
470 self._report_ready(lease=lease)
471 need_report = False
472 wait_for_url([url], max_wait=None, timeout=60,
473 status_cb=LOG.info,
474 headers_cb=lambda url: headers, sleep_time=1,
475 exception_cb=exception_cb,
476 sleep_time_cb=sleep_cb)
477 return str(readurl(url, headers=headers))
478 except Exception:
479 LOG.debug("Exception during polling-retrying dhcp" +
480 " %d more time(s).", (IMDS_RETRIES - i),
481 exc_info=True)
482
483 def _report_ready(self, lease):
484 """Tells the fabric provisioning has completed
485 before we go into our polling loop."""
486 try:
487 get_metadata_from_fabric(None, lease['unknown-245'])
488 except Exception as exc:
489 LOG.warning(
490 "Error communicating with Azure fabric; You may experience."
491 "connectivity issues.", exc_info=True)
492
493 def _should_reprovision(self, ret):
494 """Whether or not we should poll IMDS for reprovisioning data.
495 Also sets a marker file to poll IMDS.
496
497 The marker file is used for the following scenario: the VM boots into
498 this polling loop, which we expect to be proceeding infinitely until
499 the VM is picked. If for whatever reason the platform moves us to a
500 new host (for instance a hardware issue), we need to keep polling.
501 However, since the VM reports ready to the Fabric, we will not attach
502 the ISO, thus cloud-init needs to have a way of knowing that it should
503 jump back into the polling loop in order to retrieve the ovf_env."""
504 if not ret:
505 return False
506 (md, self.userdata_raw, cfg, files) = ret
507 path = REPROVISION_MARKER_FILE
508 if (cfg.get('PreprovisionedVm') is True or
509 os.path.isfile(path)):
510 if not os.path.isfile(path):
511 LOG.info("Creating a marker file to poll imds")
512 util.write_file(path, "%s: %s\n" % (os.getpid(), time()))
513 return True
514 return False
515
516 def _reprovision(self):
517 """Initiate the reprovisioning workflow."""
518 contents = self._poll_imds()
519 md, ud, cfg = read_azure_ovf(contents)
520 return (md, ud, cfg, {'ovf-env.xml': contents})
521
431 def _negotiate(self):522 def _negotiate(self):
432 """Negotiate with fabric and return data from it.523 """Negotiate with fabric and return data from it.
433524
@@ -453,7 +544,7 @@ class DataSourceAzure(sources.DataSource):
453 "Error communicating with Azure fabric; You may experience."544 "Error communicating with Azure fabric; You may experience."
454 "connectivity issues.", exc_info=True)545 "connectivity issues.", exc_info=True)
455 return False546 return False
456547 util.del_file(REPROVISION_MARKER_FILE)
457 return fabric_data548 return fabric_data
458549
459 def activate(self, cfg, is_new_instance):550 def activate(self, cfg, is_new_instance):
@@ -595,6 +686,7 @@ def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120,
595def perform_hostname_bounce(hostname, cfg, prev_hostname):686def perform_hostname_bounce(hostname, cfg, prev_hostname):
596 # set the hostname to 'hostname' if it is not already set to that.687 # set the hostname to 'hostname' if it is not already set to that.
597 # then, if policy is not off, bounce the interface using command688 # then, if policy is not off, bounce the interface using command
689 # Returns True if the network was bounced, False otherwise.
598 command = cfg['command']690 command = cfg['command']
599 interface = cfg['interface']691 interface = cfg['interface']
600 policy = cfg['policy']692 policy = cfg['policy']
@@ -614,7 +706,8 @@ def perform_hostname_bounce(hostname, cfg, prev_hostname):
614 else:706 else:
615 LOG.debug(707 LOG.debug(
616 "Skipping network bounce: ifupdown utils aren't present.")708 "Skipping network bounce: ifupdown utils aren't present.")
617 return # Don't bounce as networkd handles hostname DDNS updates709 # Don't bounce as networkd handles hostname DDNS updates
710 return False
618 LOG.debug("pubhname: publishing hostname [%s]", msg)711 LOG.debug("pubhname: publishing hostname [%s]", msg)
619 shell = not isinstance(command, (list, tuple))712 shell = not isinstance(command, (list, tuple))
620 # capture=False, see comments in bug 1202758 and bug 1206164.713 # capture=False, see comments in bug 1202758 and bug 1206164.
@@ -622,6 +715,7 @@ def perform_hostname_bounce(hostname, cfg, prev_hostname):
622 get_uptime=True, func=util.subp,715 get_uptime=True, func=util.subp,
623 kwargs={'args': command, 'shell': shell, 'capture': False,716 kwargs={'args': command, 'shell': shell, 'capture': False,
624 'env': env})717 'env': env})
718 return True
625719
626720
627def crtfile_to_pubkey(fname, data=None):721def crtfile_to_pubkey(fname, data=None):
@@ -838,9 +932,35 @@ def read_azure_ovf(contents):
838 if 'ssh_pwauth' not in cfg and password:932 if 'ssh_pwauth' not in cfg and password:
839 cfg['ssh_pwauth'] = True933 cfg['ssh_pwauth'] = True
840934
935 cfg['PreprovisionedVm'] = _extract_preprovisioned_vm_setting(dom)
936
841 return (md, ud, cfg)937 return (md, ud, cfg)
842938
843939
940def _extract_preprovisioned_vm_setting(dom):
941 """Read the preprovision flag from the ovf. It should not
942 exist unless true."""
943 platform_settings_section = find_child(
944 dom.documentElement,
945 lambda n: n.localName == "PlatformSettingsSection")
946 if not platform_settings_section or len(platform_settings_section) == 0:
947 LOG.debug("PlatformSettingsSection not found")
948 return False
949 platform_settings = find_child(
950 platform_settings_section[0],
951 lambda n: n.localName == "PlatformSettings")
952 if not platform_settings or len(platform_settings) == 0:
953 LOG.debug("PlatformSettings not found")
954 return False
955 preprovisionedVm = find_child(
956 platform_settings[0],
957 lambda n: n.localName == "PreprovisionedVm")
958 if not preprovisionedVm or len(preprovisionedVm) == 0:
959 LOG.debug("PreprovisionedVm not found")
960 return False
961 return util.translate_bool(preprovisionedVm[0].firstChild.nodeValue)
962
963
844def encrypt_pass(password, salt_id="$6$"):964def encrypt_pass(password, salt_id="$6$"):
845 return crypt.crypt(password, salt_id + util.rand_str(strlen=16))965 return crypt.crypt(password, salt_id + util.rand_str(strlen=16))
846966
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 0f89f34..e14553b 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -14,7 +14,7 @@ import time
14from cloudinit import ec2_utils as ec214from cloudinit import ec2_utils as ec2
15from cloudinit import log as logging15from cloudinit import log as logging
16from cloudinit import net16from cloudinit import net
17from cloudinit.net import dhcp17from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError
18from cloudinit import sources18from cloudinit import sources
19from cloudinit import url_helper as uhelp19from cloudinit import url_helper as uhelp
20from cloudinit import util20from cloudinit import util
@@ -102,22 +102,13 @@ class DataSourceEc2(sources.DataSource):
102 if util.is_FreeBSD():102 if util.is_FreeBSD():
103 LOG.debug("FreeBSD doesn't support running dhclient with -sf")103 LOG.debug("FreeBSD doesn't support running dhclient with -sf")
104 return False104 return False
105 dhcp_leases = dhcp.maybe_perform_dhcp_discovery(105 try:
106 self.fallback_interface)106 with EphemeralDHCPv4(self.fallback_interface):
107 if not dhcp_leases:107 return util.log_time(
108 # DataSourceEc2Local failed in init-local stage. DataSourceEc2108 logfunc=LOG.debug, msg='Crawl of metadata service',
109 # will still run in init-network stage.109 func=self._crawl_metadata)
110 except NoDHCPLeaseError:
110 return False111 return False
111 dhcp_opts = dhcp_leases[-1]
112 net_params = {'interface': dhcp_opts.get('interface'),
113 'ip': dhcp_opts.get('fixed-address'),
114 'prefix_or_mask': dhcp_opts.get('subnet-mask'),
115 'broadcast': dhcp_opts.get('broadcast-address'),
116 'router': dhcp_opts.get('routers')}
117 with net.EphemeralIPv4Network(**net_params):
118 return util.log_time(
119 logfunc=LOG.debug, msg='Crawl of metadata service',
120 func=self._crawl_metadata)
121 else:112 else:
122 return self._crawl_metadata()113 return self._crawl_metadata()
123114
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index 6cda572..90c12df 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -199,10 +199,10 @@ class WALinuxAgentShim(object):
199 ' </Container>',199 ' </Container>',
200 '</Health>'])200 '</Health>'])
201201
202 def __init__(self, fallback_lease_file=None):202 def __init__(self, fallback_lease_file=None, dhcp_options=None):
203 LOG.debug('WALinuxAgentShim instantiated, fallback_lease_file=%s',203 LOG.debug('WALinuxAgentShim instantiated, fallback_lease_file=%s',
204 fallback_lease_file)204 fallback_lease_file)
205 self.dhcpoptions = None205 self.dhcpoptions = dhcp_options
206 self._endpoint = None206 self._endpoint = None
207 self.openssl_manager = None207 self.openssl_manager = None
208 self.values = {}208 self.values = {}
@@ -220,7 +220,8 @@ class WALinuxAgentShim(object):
220 @property220 @property
221 def endpoint(self):221 def endpoint(self):
222 if self._endpoint is None:222 if self._endpoint is None:
223 self._endpoint = self.find_endpoint(self.lease_file)223 self._endpoint = self.find_endpoint(self.lease_file,
224 self.dhcpoptions)
224 return self._endpoint225 return self._endpoint
225226
226 @staticmethod227 @staticmethod
@@ -292,10 +293,14 @@ class WALinuxAgentShim(object):
292 return _value293 return _value
293294
294 @staticmethod295 @staticmethod
295 def find_endpoint(fallback_lease_file=None):296 def find_endpoint(fallback_lease_file=None, dhcp245=None):
296 value = None297 value = None
297 LOG.debug('Finding Azure endpoint from networkd...')298 if dhcp245 is not None:
298 value = WALinuxAgentShim._networkd_get_value_from_leases()299 value = dhcp245
300 LOG.debug("Using Azure Endpoint from dhcp options")
301 if value is None:
302 LOG.debug('Finding Azure endpoint from networkd...')
303 value = WALinuxAgentShim._networkd_get_value_from_leases()
299 if value is None:304 if value is None:
300 # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json305 # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json
301 # a dhclient exit hook that calls cloud-init-dhclient-hook306 # a dhclient exit hook that calls cloud-init-dhclient-hook
@@ -367,8 +372,9 @@ class WALinuxAgentShim(object):
367 LOG.info('Reported ready to Azure fabric.')372 LOG.info('Reported ready to Azure fabric.')
368373
369374
370def get_metadata_from_fabric(fallback_lease_file=None):375def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None):
371 shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file)376 shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
377 dhcp_options=dhcp_opts)
372 try:378 try:
373 return shim.register_with_azure_and_fetch_data()379 return shim.register_with_azure_and_fetch_data()
374 finally:380 finally:
diff --git a/cloudinit/temp_utils.py b/cloudinit/temp_utils.py
index 5d7adf7..c98a1b5 100644
--- a/cloudinit/temp_utils.py
+++ b/cloudinit/temp_utils.py
@@ -28,13 +28,18 @@ def _tempfile_dir_arg(odir=None, needs_exe=False):
28 if odir is not None:28 if odir is not None:
29 return odir29 return odir
3030
31 if needs_exe:
32 tdir = _EXE_ROOT_TMPDIR
33 if not os.path.isdir(tdir):
34 os.makedirs(tdir)
35 os.chmod(tdir, 0o1777)
36 return tdir
37
31 global _TMPDIR38 global _TMPDIR
32 if _TMPDIR:39 if _TMPDIR:
33 return _TMPDIR40 return _TMPDIR
3441
35 if needs_exe:42 if os.getuid() == 0:
36 tdir = _EXE_ROOT_TMPDIR
37 elif os.getuid() == 0:
38 tdir = _ROOT_TMPDIR43 tdir = _ROOT_TMPDIR
39 else:44 else:
40 tdir = os.environ.get('TMPDIR', '/tmp')45 tdir = os.environ.get('TMPDIR', '/tmp')
diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py
index 0e0f5b4..0a5be0b 100644
--- a/cloudinit/url_helper.py
+++ b/cloudinit/url_helper.py
@@ -273,7 +273,7 @@ def readurl(url, data=None, timeout=None, retries=0, sec_between=1,
273273
274def wait_for_url(urls, max_wait=None, timeout=None,274def wait_for_url(urls, max_wait=None, timeout=None,
275 status_cb=None, headers_cb=None, sleep_time=1,275 status_cb=None, headers_cb=None, sleep_time=1,
276 exception_cb=None):276 exception_cb=None, sleep_time_cb=None):
277 """277 """
278 urls: a list of urls to try278 urls: a list of urls to try
279 max_wait: roughly the maximum time to wait before giving up279 max_wait: roughly the maximum time to wait before giving up
@@ -286,6 +286,8 @@ def wait_for_url(urls, max_wait=None, timeout=None,
286 for request.286 for request.
287 exception_cb: call method with 2 arguments 'msg' (per status_cb) and287 exception_cb: call method with 2 arguments 'msg' (per status_cb) and
288 'exception', the exception that occurred.288 'exception', the exception that occurred.
289 sleep_time_cb: call method with 2 arguments (response, loop_n) that
290 generates the next sleep time.
289291
290 the idea of this routine is to wait for the EC2 metdata service to292 the idea of this routine is to wait for the EC2 metdata service to
291 come up. On both Eucalyptus and EC2 we have seen the case where293 come up. On both Eucalyptus and EC2 we have seen the case where
@@ -301,6 +303,8 @@ def wait_for_url(urls, max_wait=None, timeout=None,
301 service but is not going to find one. It is possible that the instance303 service but is not going to find one. It is possible that the instance
302 data host (169.254.169.254) may be firewalled off Entirely for a sytem,304 data host (169.254.169.254) may be firewalled off Entirely for a sytem,
303 meaning that the connection will block forever unless a timeout is set.305 meaning that the connection will block forever unless a timeout is set.
306
307 A value of None for max_wait will retry indefinitely.
304 """308 """
305 start_time = time.time()309 start_time = time.time()
306310
@@ -311,18 +315,24 @@ def wait_for_url(urls, max_wait=None, timeout=None,
311 status_cb = log_status_cb315 status_cb = log_status_cb
312316
313 def timeup(max_wait, start_time):317 def timeup(max_wait, start_time):
314 return ((max_wait <= 0 or max_wait is None) or318 if (max_wait is None):
315 (time.time() - start_time > max_wait))319 return False
320 return ((max_wait <= 0) or (time.time() - start_time > max_wait))
316321
317 loop_n = 0322 loop_n = 0
323 response = None
318 while True:324 while True:
319 sleep_time = int(loop_n / 5) + 1325 if sleep_time_cb is not None:
326 sleep_time = sleep_time_cb(response, loop_n)
327 else:
328 sleep_time = int(loop_n / 5) + 1
320 for url in urls:329 for url in urls:
321 now = time.time()330 now = time.time()
322 if loop_n != 0:331 if loop_n != 0:
323 if timeup(max_wait, start_time):332 if timeup(max_wait, start_time):
324 break333 break
325 if timeout and (now + timeout > (start_time + max_wait)):334 if (max_wait is not None and
335 timeout and (now + timeout > (start_time + max_wait))):
326 # shorten timeout to not run way over max_time336 # shorten timeout to not run way over max_time
327 timeout = int((start_time + max_wait) - now)337 timeout = int((start_time + max_wait) - now)
328338
@@ -354,10 +364,11 @@ def wait_for_url(urls, max_wait=None, timeout=None,
354 url_exc = e364 url_exc = e
355365
356 time_taken = int(time.time() - start_time)366 time_taken = int(time.time() - start_time)
357 status_msg = "Calling '%s' failed [%s/%ss]: %s" % (url,367 max_wait_str = "%ss" % max_wait if max_wait else "unlimited"
358 time_taken,368 status_msg = "Calling '%s' failed [%s/%s]: %s" % (url,
359 max_wait,369 time_taken,
360 reason)370 max_wait_str,
371 reason)
361 status_cb(status_msg)372 status_cb(status_msg)
362 if exception_cb:373 if exception_cb:
363 # This can be used to alter the headers that will be sent374 # This can be used to alter the headers that will be sent
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 6341e1e..254e987 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -5,7 +5,7 @@ from cloudinit.util import b64e, decode_binary, load_file, write_file
5from cloudinit.sources import DataSourceAzure as dsaz5from cloudinit.sources import DataSourceAzure as dsaz
6from cloudinit.util import find_freebsd_part6from cloudinit.util import find_freebsd_part
7from cloudinit.util import get_path_dev_freebsd7from cloudinit.util import get_path_dev_freebsd
88from cloudinit.version import version_string as vs
9from cloudinit.tests.helpers import (CiTestCase, TestCase, populate_dir, mock,9from cloudinit.tests.helpers import (CiTestCase, TestCase, populate_dir, mock,
10 ExitStack, PY26, SkipTest)10 ExitStack, PY26, SkipTest)
1111
@@ -16,7 +16,8 @@ import xml.etree.ElementTree as ET
16import yaml16import yaml
1717
1818
19def construct_valid_ovf_env(data=None, pubkeys=None, userdata=None):19def construct_valid_ovf_env(data=None, pubkeys=None,
20 userdata=None, platform_settings=None):
20 if data is None:21 if data is None:
21 data = {'HostName': 'FOOHOST'}22 data = {'HostName': 'FOOHOST'}
22 if pubkeys is None:23 if pubkeys is None:
@@ -66,10 +67,12 @@ def construct_valid_ovf_env(data=None, pubkeys=None, userdata=None):
66 xmlns:i="http://www.w3.org/2001/XMLSchema-instance">67 xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
67 <KmsServerHostname>kms.core.windows.net</KmsServerHostname>68 <KmsServerHostname>kms.core.windows.net</KmsServerHostname>
68 <ProvisionGuestAgent>false</ProvisionGuestAgent>69 <ProvisionGuestAgent>false</ProvisionGuestAgent>
69 <GuestAgentPackageName i:nil="true" />70 <GuestAgentPackageName i:nil="true" />"""
70 </PlatformSettings></wa:PlatformSettingsSection>71 if platform_settings:
71</Environment>72 for k, v in platform_settings.items():
72 """73 content += "<%s>%s</%s>\n" % (k, v, k)
74 content += """</PlatformSettings></wa:PlatformSettingsSection>
75</Environment>"""
7376
74 return content77 return content
7578
@@ -1107,4 +1110,146 @@ class TestAzureNetExists(CiTestCase):
1107 self.assertTrue(hasattr(dsaz, "DataSourceAzureNet"))1110 self.assertTrue(hasattr(dsaz, "DataSourceAzureNet"))
11081111
11091112
1113@mock.patch('cloudinit.sources.DataSourceAzure.util.subp')
1114@mock.patch.object(dsaz, 'get_hostname')
1115@mock.patch.object(dsaz, 'set_hostname')
1116class TestAzureDataSourcePreprovisioning(CiTestCase):
1117
1118 def setUp(self):
1119 super(TestAzureDataSourcePreprovisioning, self).setUp()
1120 tmp = self.tmp_dir()
1121 self.waagent_d = self.tmp_path('/var/lib/waagent', tmp)
1122 self.paths = helpers.Paths({'cloud_dir': tmp})
1123 dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
1124
1125 def test_read_azure_ovf_with_true_flag(self, *args):
1126 """The read_azure_ovf method should set the PreprovisionedVM
1127 cfg flag if the proper setting is present."""
1128 content = construct_valid_ovf_env(
1129 platform_settings={"PreprovisionedVm": "True"})
1130 ret = dsaz.read_azure_ovf(content)
1131 cfg = ret[2]
1132 self.assertTrue(cfg['PreprovisionedVm'])
1133
1134 def test_read_azure_ovf_with_false_flag(self, *args):
1135 """The read_azure_ovf method should set the PreprovisionedVM
1136 cfg flag to false if the proper setting is false."""
1137 content = construct_valid_ovf_env(
1138 platform_settings={"PreprovisionedVm": "False"})
1139 ret = dsaz.read_azure_ovf(content)
1140 cfg = ret[2]
1141 self.assertFalse(cfg['PreprovisionedVm'])
1142
1143 def test_read_azure_ovf_without_flag(self, *args):
1144 """The read_azure_ovf method should not set the
1145 PreprovisionedVM cfg flag."""
1146 content = construct_valid_ovf_env()
1147 ret = dsaz.read_azure_ovf(content)
1148 cfg = ret[2]
1149 self.assertFalse(cfg['PreprovisionedVm'])
1150
1151 @mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
1152 @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
1153 @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
1154 @mock.patch('requests.Session.request')
1155 def test_poll_imds_returns_ovf_env(self, fake_resp, m_dhcp, m_net,
1156 m_is_bsd, *args):
1157 """The _poll_imds method should return the ovf_env.xml."""
1158 m_is_bsd.return_value = False
1159 m_dhcp.return_value = [{
1160 'interface': 'eth9', 'fixed-address': '192.168.2.9',
1161 'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0'}]
1162 url = 'http://{0}/metadata/reprovisiondata?api-version=2017-04-02'
1163 host = "169.254.169.254"
1164 full_url = url.format(host)
1165 fake_resp.return_value = mock.MagicMock(status_code=200, text="ovf")
1166 dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
1167 self.assertTrue(len(dsa._poll_imds()) > 0)
1168 self.assertEqual(fake_resp.call_args_list,
1169 [mock.call(allow_redirects=True,
1170 headers={'Metadata': 'true',
1171 'User-Agent':
1172 'Cloud-Init/%s' % vs()
1173 }, method='GET', timeout=60.0,
1174 url=full_url),
1175 mock.call(allow_redirects=True,
1176 headers={'Metadata': 'true',
1177 'User-Agent':
1178 'Cloud-Init/%s' % vs()
1179 }, method='GET', url=full_url)])
1180 self.assertEqual(m_dhcp.call_count, 1)
1181 m_net.assert_any_call(
1182 broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9',
1183 prefix_or_mask='255.255.255.0', router='192.168.2.1')
1184 self.assertEqual(m_net.call_count, 1)
1185
1186 @mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
1187 @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
1188 @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
1189 @mock.patch('requests.Session.request')
1190 def test__reprovision_calls__poll_imds(self, fake_resp, m_dhcp, m_net,
1191 m_is_bsd, *args):
1192 """The _reprovision method should call poll IMDS."""
1193 m_is_bsd.return_value = False
1194 m_dhcp.return_value = [{
1195 'interface': 'eth9', 'fixed-address': '192.168.2.9',
1196 'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0',
1197 'unknown-245': '624c3620'}]
1198 url = 'http://{0}/metadata/reprovisiondata?api-version=2017-04-02'
1199 host = "169.254.169.254"
1200 full_url = url.format(host)
1201 hostname = "myhost"
1202 username = "myuser"
1203 odata = {'HostName': hostname, 'UserName': username}
1204 content = construct_valid_ovf_env(data=odata)
1205 fake_resp.return_value = mock.MagicMock(status_code=200, text=content)
1206 dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
1207 md, ud, cfg, d = dsa._reprovision()
1208 self.assertEqual(md['local-hostname'], hostname)
1209 self.assertEqual(cfg['system_info']['default_user']['name'], username)
1210 self.assertEqual(fake_resp.call_args_list,
1211 [mock.call(allow_redirects=True,
1212 headers={'Metadata': 'true',
1213 'User-Agent':
1214 'Cloud-Init/%s' % vs()},
1215 method='GET', timeout=60.0, url=full_url),
1216 mock.call(allow_redirects=True,
1217 headers={'Metadata': 'true',
1218 'User-Agent':
1219 'Cloud-Init/%s' % vs()},
1220 method='GET', url=full_url)])
1221 self.assertEqual(m_dhcp.call_count, 1)
1222 m_net.assert_any_call(
1223 broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9',
1224 prefix_or_mask='255.255.255.0', router='192.168.2.1')
1225 self.assertEqual(m_net.call_count, 1)
1226
1227 @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
1228 @mock.patch('os.path.isfile')
1229 def test__should_reprovision_with_true_cfg(self, isfile, write_f, *args):
1230 """The _should_reprovision method should return true with config
1231 flag present."""
1232 isfile.return_value = False
1233 dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
1234 self.assertTrue(dsa._should_reprovision(
1235 (None, None, {'PreprovisionedVm': True}, None)))
1236
1237 @mock.patch('os.path.isfile')
1238 def test__should_reprovision_with_file_existing(self, isfile, *args):
1239 """The _should_reprovision method should return True if the sentinal
1240 exists."""
1241 isfile.return_value = True
1242 dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
1243 self.assertTrue(dsa._should_reprovision(
1244 (None, None, {'preprovisionedvm': False}, None)))
1245
1246 @mock.patch('os.path.isfile')
1247 def test__should_reprovision_returns_false(self, isfile, *args):
1248 """The _should_reprovision method should return False
1249 if config and sentinal are not present."""
1250 isfile.return_value = False
1251 dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
1252 self.assertFalse(dsa._should_reprovision((None, None, {}, None)))
1253
1254
1110# vi: ts=4 expandtab1255# vi: ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index f0dc833..0f7267b 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -425,7 +425,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
425 self.logs.getvalue())425 self.logs.getvalue())
426426
427 @httpretty.activate427 @httpretty.activate
428 @mock.patch('cloudinit.net.EphemeralIPv4Network')428 @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
429 @mock.patch('cloudinit.net.find_fallback_nic')429 @mock.patch('cloudinit.net.find_fallback_nic')
430 @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')430 @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
431 @mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')431 @mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index ddea13d..ac33e8e 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -2948,4 +2948,16 @@ class TestRenameInterfaces(CiTestCase):
2948 mock_subp.assert_has_calls(expected)2948 mock_subp.assert_has_calls(expected)
29492949
29502950
2951class TestNetworkState(CiTestCase):
2952
2953 def test_bcast_addr(self):
2954 """Test mask_and_ipv4_to_bcast_addr proper execution."""
2955 bcast_addr = network_state.mask_and_ipv4_to_bcast_addr
2956 self.assertEqual("192.168.1.255",
2957 bcast_addr("255.255.255.0", "192.168.1.1"))
2958 self.assertEqual("128.42.7.255",
2959 bcast_addr("255.255.248.0", "128.42.5.4"))
2960 self.assertEqual("10.1.21.255",
2961 bcast_addr("255.255.255.0", "10.1.21.4"))
2962
2951# vi: ts=4 expandtab2963# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches