Merge ~msaikia/cloud-init:topic-msaikia-vmware-custom-script into cloud-init:master
- Git
- lp:~msaikia/cloud-init
- topic-msaikia-vmware-custom-script
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | bde436d2775fdc1882a6f08c69aa528c0f638be5 |
Merged at revision: | ce33e423cde806a0590fec635778d62836e1bd37 |
Proposed branch: | ~msaikia/cloud-init:topic-msaikia-vmware-custom-script |
Merge into: | cloud-init:master |
Diff against target: |
650 lines (+459/-42) 8 files modified
cloudinit/sources/DataSourceOVF.py (+88/-37) cloudinit/sources/helpers/vmware/imc/config.py (+4/-0) cloudinit/sources/helpers/vmware/imc/config_custom_script.py (+153/-0) cloudinit/sources/helpers/vmware/imc/config_nic.py (+1/-1) tests/unittests/test_datasource/test_ovf.py (+107/-4) tests/unittests/test_vmware/__init__.py (+0/-0) tests/unittests/test_vmware/test_custom_script.py (+99/-0) tests/unittests/test_vmware_config_file.py (+7/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+330105@code.launchpad.net |
This proposal supersedes a proposal from 2017-08-23.
Commit message
VMware: Support for user provided pre and post-customization scripts
In the VMware customization workflow, we have some options for the user
to upload scripts for additional customization. Based on user request,
those custom scripts can be either run before regular customization or
after. For post customization scripts, we decide whether to run the scripts
just after customization or post system reboot.
Description of the change
Support for users to upload custom scripts in VMware customization workflow.
In the VMware workflow, we have some options for the user to upload custom scripts for additional customization. Based on user request those custom scripts can be either run before regular customization or after. For post customization scripts, we decide whether to run the scripts just after customization or post reboot.
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:a339c450b53
https:/
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:/
Chad Smith (chad.smith) wrote : Posted in a previous version of this proposal | # |
Thanks for this contribution, I have a couple of notes to get us started here. Will test more on this and respond Monday.
Chad Smith (chad.smith) : Posted in a previous version of this proposal | # |
Chad Smith (chad.smith) wrote : Posted in a previous version of this proposal | # |
Leaving as needs fixing for the review comments mentioned. Looks good again, thanks for the work here. Please mark this back to "needs review" when comments have been addressed.
Maitreyee Saikia (msaikia) wrote : Posted in a previous version of this proposal | # |
Addressed review comments.
Thanks!
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:3b626e16069
https:/
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:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:3b626e16069
https:/
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:/
- 3316786... by Joshua Powers
-
tox: add nose timer output
This adds the output of the nose timer plugin to the py3 environment to
tox. This will print out the 10 longest running tests and automatically
turn tests longer than 1 second "red" after the coverage output. - b39eb0a... by Joshua Powers
-
tox: add nose timer output
This adds the output of the nose timer plugin to the py3 environment to
tox. This will print out the 10 longest running tests and automatically
turn tests longer than 1 second "red" after the coverage output. - f759ce5... by Chad Smith
-
schema and docs: Add jsonschema to resizefs and bootcmd modules
Add schema definitions to both cc_resizefs and cc_bootcmd modules. Extend
schema.py to parse and document enumerated json types. Schema definitions
are used to generate module documention and log warnings for schema
infractions.This branch also does the following:
- drops vestigial 'resize_rootfs_tmp' option from cc_resizefs. That
option only created the specified directory and didn't make use of
that directory for any resize operations.
- Drop yaml.dumps calls from schema documentation generation to avoid
yaml import costs on module load
- Add __doc__ = get_schema_doc(schema) definitions it each module to
supplement python help() calls for cc_runcmd, cc_bootcmd, cc_ntp and
cc_resizefs
- Add a SCHEMA_EXAMPLES_ SPACER_ TEMPLATE string to docs for modules which
contain more than one example - 75d806c... by Chad Smith
-
schema and docs: Add jsonschema to resizefs and bootcmd modules
Add schema definitions to both cc_resizefs and cc_bootcmd modules. Extend
schema.py to parse and document enumerated json types. Schema definitions
are used to generate module documention and log warnings for schema
infractions.This branch also does the following:
- drops vestigial 'resize_rootfs_tmp' option from cc_resizefs. That
option only created the specified directory and didn't make use of
that directory for any resize operations.
- Drop yaml.dumps calls from schema documentation generation to avoid
yaml import costs on module load
- Add __doc__ = get_schema_doc(schema) definitions it each module to
supplement python help() calls for cc_runcmd, cc_bootcmd, cc_ntp and
cc_resizefs
- Add a SCHEMA_EXAMPLES_ SPACER_ TEMPLATE string to docs for modules which
contain more than one example - 0ea19ba... by Chad Smith
-
schema and docs: Add jsonschema to resizefs and bootcmd modules
Add schema definitions to both cc_resizefs and cc_bootcmd modules. Extend
schema.py to parse and document enumerated json types. Schema definitions
are used to generate module documention and log warnings for schema
infractions.This branch also does the following:
- drops vestigial 'resize_rootfs_tmp' option from cc_resizefs. That
option only created the specified directory and didn't make use of
that directory for any resize operations.
- Drop yaml.dumps calls from schema documentation generation to avoid
yaml import costs on module load
- Add __doc__ = get_schema_doc(schema) definitions it each module to
supplement python help() calls for cc_runcmd, cc_bootcmd, cc_ntp and
cc_resizefs
- Add a SCHEMA_EXAMPLES_ SPACER_ TEMPLATE string to docs for modules which
contain more than one example - 33f9546... by Lars Kellogg-Stedman
-
relocate tests/unittests
/helpers. py to cloudinit/tests This moves the base test case classes into into cloudinit/tests and
updates all the corresponding imports. - 9f80a27... by Lars Kellogg-Stedman
-
relocate tests/unittests
/helpers. py to cloudinit/tests This moves the base test case classes into into cloudinit/tests and
updates all the corresponding imports. - eeb754d... by Scott Moser
-
ds-identify: Make OpenStack return maybe on arch other than intel.
OpenStack Nova identifies itself only to Intel guests.
Make ds-identify return 'MAYBE' for OpenStack on non-intel arches.An unnecessary change here is to rename the 'policy_nodmi' kwarg
to 'policy_no_dmi' in the related unit tests.LP: #1715241
- 7f54e3a... by Scott Moser
-
ds-identify: Make OpenStack return maybe on arch other than intel.
OpenStack Nova identifies itself only to Intel guests.
Make ds-identify return 'MAYBE' for OpenStack on non-intel arches.An unnecessary change here is to rename the 'policy_nodmi' kwarg
to 'policy_no_dmi' in the related unit tests.LP: #1715241
- 78b02e3... by Chad Smith
-
tests: mock missed openstack metadata uri network_data.json
This missed mock in test_openstack resulted in a costly unit test timeout.
LP: #1714376
- 83777af... by Chad Smith
-
tests: mock missed openstack metadata uri network_data.json
This missed mock in test_openstack resulted in a costly unit test timeout.
LP: #1714376
- f08b473... by Scott Moser
-
Ec2: only attempt to operate at local mode on known platforms.
This change makes the DataSourceEc2Local do nothing unless it is on
actual AWS platform. The motivation is twofold:a.) It is generally safer to only make this function available to Ec2
clones that explicitly identify themselves to the guest. (It also
gives them a reason to supply identification code to cloud-init.)b.) On non-intel OpenStack platforms ds-identify would enable both the Ec2
and OpenStack sources. That is because there is not good data (such as
dmi) to positively identify the platform. Previously that would be fine
as OpenStack would run first and be successful. The change to add Ec2Local
meant that an Ec2 now runs first.The best case for 'b' would be a slow down as attempts at the Ec2 metadata
service time out. The discovered case was worse.Additionally we add a simple check for datatype of 'network' in the
metadata before attempting to read it.LP: #1715128
- 1a738fa... by Scott Moser
-
Ec2: only attempt to operate at local mode on known platforms.
This change makes the DataSourceEc2Local do nothing unless it is on
actual AWS platform. The motivation is twofold:a.) It is generally safer to only make this function available to Ec2
clones that explicitly identify themselves to the guest. (It also
gives them a reason to supply identification code to cloud-init.)b.) On non-intel OpenStack platforms ds-identify would enable both the Ec2
and OpenStack sources. That is because there is not good data (such as
dmi) to positively identify the platform. Previously that would be fine
as OpenStack would run first and be successful. The change to add Ec2Local
meant that an Ec2 now runs first.The best case for 'b' would be a slow down as attempts at the Ec2 metadata
service time out. The discovered case was worse.Additionally we add a simple check for datatype of 'network' in the
metadata before attempting to read it.LP: #1715128
- 5708cb2... by Sankar Tanguturi
-
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.This branch also fixes /etc/network/
interfaces replacing the line
"source /etc/network/interfaces. d/*.cfg" which is incorrectly removed
when VMWare's Perl Customization Engine writes /etc/network/interfaces. Modify the code to read the customization configuration and return the
converted data.Added few tests.
LP: #1675063
- 8336907... by Sankar Tanguturi
-
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.This branch also fixes /etc/network/
interfaces replacing the line
"source /etc/network/interfaces. d/*.cfg" which is incorrectly removed
when VMWare's Perl Customization Engine writes /etc/network/interfaces. Modify the code to read the customization configuration and return the
converted data.Added few tests.
LP: #1675063
- 190dbc7... by Sankar Tanguturi
-
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.This branch also fixes /etc/network/
interfaces replacing the line
"source /etc/network/interfaces. d/*.cfg" which is incorrectly removed
when VMWare's Perl Customization Engine writes /etc/network/interfaces. Modify the code to read the customization configuration and return the
converted data.Added few tests.
LP: #1675063
- 78de454... by Joshua Powers
-
tests: execute: support command as string
If a string is passed to execute, then invoke 'bash', '-c',
'string'. That allows the less verbose execution of simple
commands:
image.execute("ls /run")
compared to the more explicit but longer winded:
image.execute( ["ls", "/run"]) If 'env' was ever modified in execute or a method that it called,
then the next invocation's default value would be changed. Instead
use None and then set to a new empty dict in the method. - 55e8a71... by Joshua Powers
-
tests: execute: support command as string
If a string is passed to execute, then invoke 'bash', '-c',
'string'. That allows the less verbose execution of simple
commands:
image.execute("ls /run")
compared to the more explicit but longer winded:
image.execute( ["ls", "/run"]) If 'env' was ever modified in execute or a method that it called,
then the next invocation's default value would be changed. Instead
use None and then set to a new empty dict in the method. - 4ed7511... by Joshua Powers
-
tools: Add xkvm script, wrapper around qemu-system
The xkvm script will be utilized by pending NoCloud qemu testing.
If this turns out to not be the case, then we will drop it. - 37a3d71... by Joshua Powers
-
tools: Add xkvm script, wrapper around qemu-system
The xkvm script will be utilized by pending NoCloud qemu testing.
If this turns out to not be the case, then we will drop it. - 157a632... by Maitreyee Saikia
-
Rebasing master
- 45194dd... by Maitreyee Saikia
-
Rebasing master
- cef363b... by Ethan Apodaca
-
chef: Add option to pin chef omnibus install version
Most users of chef will want to pin the version that is installed.
Typically new versions of chef have to be evaluated for breakage etc.This change proposes a new optional `omnibus_version` field to the chef
configuration. The changeset also adds documentation referencing the new
field.LP: #1462693
- ae1683b... by Ethan Apodaca
-
chef: Add option to pin chef omnibus install version
Most users of chef will want to pin the version that is installed.
Typically new versions of chef have to be evaluated for breakage etc.This change proposes a new optional `omnibus_version` field to the chef
configuration. The changeset also adds documentation referencing the new
field.LP: #1462693
- b57a5d7... by Ethan Apodaca
-
chef: Add option to pin chef omnibus install version
Most users of chef will want to pin the version that is installed.
Typically new versions of chef have to be evaluated for breakage etc.This change proposes a new optional `omnibus_version` field to the chef
configuration. The changeset also adds documentation referencing the new
field.LP: #1462693
- f4a2806... by Maitreyee Saikia
-
Merge branch 'topic-
msaikia- vmware- custom- script' of ssh://git. launchpad. net/~msaikia/ cloud-init into topic-msaikia- vmware- custom- script
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f4a2806ff2f
https:/
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:/
- 8179b05... by Maitreyee Saikia
-
Merge branch 'topic-
msaikia- vmware- custom- script' of ssh://git. launchpad. net/~msaikia/ cloud-init into topic-msaikia- vmware- custom- script
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:8179b05c540
https:/
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:/
Chad Smith (chad.smith) wrote : | # |
Thanks again for your patience here. A couple more review comments and then I *think* we are there.
I'm still seeing merge conflicts with this branch that will likely require a git rebase to sort.
- c664a44... by Maitreyee Saikia
-
Rebase master and pull from topic branch
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:c664a448219
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- d58e6af... by David Mulford
-
rh_subscription: Perform null checks for enabled and disabled repos.
The rh_subscription module doesn't perform null checks when attempting to
iterate on the enabled and disable repos arrays. When only one is
specified, cloud-init fails to run. - 92e9944... by Maitreyee Saikia
-
Addressed review comments
- 7711408... by Maitreyee Saikia
-
Merge latest from main
- a5d82c6... by Maitreyee Saikia
-
pull from remote branch
- 8785b65... by Maitreyee Saikia
-
test fix
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:8785b65633b
https:/
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:/
Maitreyee Saikia (msaikia) wrote : | # |
Hi Chad, thanks for taking a look at the review request. I have published a new review request after addressing your concerns. Please also find response to some other comments.
Thanks
Chad Smith (chad.smith) : | # |
Maitreyee Saikia (msaikia) : | # |
Maitreyee Saikia (msaikia) wrote : | # |
Thanks Chad. Please find the responses below. Will address the rest.
Maitreyee Saikia (msaikia) wrote : | # |
Thanks Chad for the review. I think the diff comments did not get updated in my previous reply. Re-posting comment.
- 3f27def... by Scott Moser
-
Replace the temporary i9n.brickies.net with i9n.cloud-init.io.
We had used some dns records in i9n.brickies.net (my personal domain)
as a temporary solution until we got names registered in the cloud-init.io
namespace.We now have CNAME records for:
ubuntu.i9n.cloud- init.io
cloudinit1.cloud-init. io
cloudinit2.cloud-init. io - f530c36... by Robert Schweikert
-
Improve warning message when a template is not found.
At present the location for the template file look up upon failure
includes the template file itself. However based on the wording of the
message it should only contain the template directory issueLP: #1731035
- 4694ebc... by Robert Schweikert
-
hosts: Fix openSUSE and SLES setup for /etc/hosts and clarify docs.
The etc/hosts file is was not properly setup for openSUSE or SLES
when manage_etc_hosts is set in the config file.Improve the doc to address the fact that the 'localhost' ip is
distribution dependent (not always 127.0.0.1).LP: #1731022
Chad Smith (chad.smith) : | # |
- 7189c59... by Maitreyee Saikia
-
Addressed review comments
- c773da4... by Maitreyee Saikia
-
Merge branch 'master' into topic-msaikia-
vmware- custom- script - adf16c7... by Maitreyee Saikia
-
tox error
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:adf16c77ee2
https:/
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:/
- de5d6e9... by Scott Moser
-
Azure: don't generate network configuration for SRIOV devices
Azure kernel now configures the SRIOV devices itself so cloud-init
does not need to provide any SRIOV device configuration or udev
naming rules.LP: #1721579
Maitreyee Saikia (msaikia) wrote : | # |
Addressed review comments.
Chad Smith (chad.smith) wrote : | # |
I'm +1 on this now. I know we have cleanup we need to address in the future, but for vmware's pre/post script flow, this current iteration makes sense.
Chad Smith (chad.smith) : | # |
Chad Smith (chad.smith) wrote : | # |
Maitreyee, one last pass on this from me. Could you please add a couple unit tests which exercise the OVF get_data execution path with customizationscript present (and erroring) to ensure that behavior is properly handled. (probably looking for tests in tests/unittests
Maitreyee Saikia (msaikia) wrote : | # |
> Maitreyee, one last pass on this from me. Could you please add a couple unit
> tests which exercise the OVF get_data execution path with customizationscript
> present (and erroring) to ensure that behavior is properly handled. (probably
> looking for tests in tests/unittests
Hi Chad,
We have some tests in tests/unittests
Do you want me to test the calling function? The calling function does not error out if the variable "customscript" is not set. The if condition here is only to determine whether to go to that workflow or not? If not, we just move to the next configuration. All the error conditions are tested in the actual function, as to whether the correct error is raised or not.
Do you still want me to add tests on the calling function?
Chad Smith (chad.smith) wrote : | # |
Thanks Maritreyee for those tests specific to the custom script. And yes please to add a couple tests special_
We are trying to get cloud-init up out of the 60% range for unit test coverage so that we can have more certainty that we aren't breaking consumers out there when we add new features or refactor some code.
This is hard I realize as it doesn't seem like there are any get_data tests for OVF datasource currently. Here's a patch reference with a couple things in it for your branch:
1. Some tests for new functions you've added
2. Sample test for OVF.get_data()
3. Shuffle your marker files under /var/lib/cloud as that's where other cloud-init specific semaphores also live. It's better not to leak files into root '/'.
Here's a paste of the start http://
Maitreyee Saikia (msaikia) wrote : | # |
Thanks for the patch Chad.
Working on the tests now.
But I have a little concern about putting the marker file in /var/lib/cloud.
What if someone cleans up the entire /var/lib/cloud?
In that case, we will lose the marker file for the next run, and as such will
go through the entire workflow, which is not what we expect. Is there any way to handle that?
- 679b393... by Maitreyee Saikia
-
Adding unit tests for Datasource_OVF
Chad Smith (chad.smith) wrote : | # |
Maitreyee, I believe that is exactly the behavior we want to be common across all of cloud-init. Right now, if /var/lib/cloud is cleaned up and the machine is rebooted, we expect cloud-init to re-perform all configuration steps as if it is run on against clean system. In fact we are adding a cloud-init clean commandline function to do just that:
https:/
Also, per filesystem heirarchy standard, we also shouldn't be leaking files to other directories beyond what the cloud-init package 'owns'. It's okay if we haven't yet sorted the details of leaving around vmware marker files (or cleaning up former config changes due to those customizations. This lack of 'cleanup' handling is something that cloud-init will have to tackle for ssh keys or users created which may have been left on the system by the first cloud-init run, and may need removing before reboot.
- 50a025c... by Maitreyee Saikia
-
Merge branch 'master' into topic-msaikia-
vmware- custom- script - 582c798... by Maitreyee Saikia
-
Fix spacing in unittest
- 0cb4226... by Maitreyee Saikia
-
Fix spacing in unittest
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:582c798a3af
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- da14017... by Maitreyee Saikia
-
Merge branch 'topic-
msaikia- vmware- custom- script' of ssh://git. launchpad. net/~msaikia/ cloud-init into topic-msaikia- vmware- custom- script
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:da14017dec1
https:/
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:/
Maitreyee Saikia (msaikia) wrote : | # |
Thanks Chad,
I have addressed your comments.
Please take a look at the review request.
Regards,
> Maitreyee, I believe that is exactly the behavior we want to be common across
> all of cloud-init. Right now, if /var/lib/cloud is cleaned up and the machine
> is rebooted, we expect cloud-init to re-perform all configuration steps as if
> it is run on against clean system. In fact we are adding a cloud-init clean
> commandline function to do just that:
>
> https:/
> init/+merge/333513
>
> Also, per filesystem heirarchy standard, we also shouldn't be leaking files to
> other directories beyond what the cloud-init package 'owns'. It's okay if we
> haven't yet sorted the details of leaving around vmware marker files (or
> cleaning up former config changes due to those customizations. This lack of
> 'cleanup' handling is something that cloud-init will have to tackle for ssh
> keys or users created which may have been left on the system by the first
> cloud-init run, and may need removing before reboot.
Chad Smith (chad.smith) wrote : | # |
just a couple of tiny comments, thanks for adding the unit tests for datasourceovf.
- 2f2070f... by Maitreyee Saikia
-
test_ovf review comments
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:2f2070fdafe
https:/
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:/
Maitreyee Saikia (msaikia) wrote : | # |
> just a couple of tiny comments, thanks for adding the unit tests for
> datasourceovf.
Thanks. Updated new diff.
- 028d945... by Maitreyee Saikia
-
Return error in older config_nic file
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:028d945d24b
https:/
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:/
Chad Smith (chad.smith) : | # |
Chad Smith (chad.smith) wrote : | # |
Thank you for all the work here Maitreyee. One minor comment above about where to store the marker files after discussion with the team.
I've tested your content on non-vmware kvm instances and validated that it doesn't break other consumer paths as well. Thank you again for the good work. If you agree to the above final change suggestion, I'll merge this proposal.
Maitreyee Saikia (msaikia) wrote : | # |
> Thank you for all the work here Maitreyee. One minor comment above about where
> to store the marker files after discussion with the team.
>
> I've tested your content on non-vmware kvm instances and validated that it
> doesn't break other consumer paths as well. Thank you again for the good work.
> If you agree to the above final change suggestion, I'll merge this proposal.
Thank you Chad.
I am ok with the suggestion.
Should I push a new diff?
- 06624c9... by Maitreyee Saikia
-
Merge branch 'master' into topic-msaikia-
vmware- custom- script - bde436d... by Maitreyee Saikia
-
Move marker file to /var/lib/cloud/data
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:bde436d2775
https:/
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:/
Chad Smith (chad.smith) wrote : | # |
Validated this branch against a non-vmware OVF kvm instance and the logic didn't adversely affect non-vmware paths. We should land this.
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py | |||
2 | index ccebf11..489c583 100644 | |||
3 | --- a/cloudinit/sources/DataSourceOVF.py | |||
4 | +++ b/cloudinit/sources/DataSourceOVF.py | |||
5 | @@ -21,6 +21,8 @@ from cloudinit import util | |||
6 | 21 | 21 | ||
7 | 22 | from cloudinit.sources.helpers.vmware.imc.config \ | 22 | from cloudinit.sources.helpers.vmware.imc.config \ |
8 | 23 | import Config | 23 | import Config |
9 | 24 | from cloudinit.sources.helpers.vmware.imc.config_custom_script \ | ||
10 | 25 | import PreCustomScript, PostCustomScript | ||
11 | 24 | from cloudinit.sources.helpers.vmware.imc.config_file \ | 26 | from cloudinit.sources.helpers.vmware.imc.config_file \ |
12 | 25 | import ConfigFile | 27 | import ConfigFile |
13 | 26 | from cloudinit.sources.helpers.vmware.imc.config_nic \ | 28 | from cloudinit.sources.helpers.vmware.imc.config_nic \ |
14 | @@ -30,7 +32,7 @@ from cloudinit.sources.helpers.vmware.imc.config_passwd \ | |||
15 | 30 | from cloudinit.sources.helpers.vmware.imc.guestcust_error \ | 32 | from cloudinit.sources.helpers.vmware.imc.guestcust_error \ |
16 | 31 | import GuestCustErrorEnum | 33 | import GuestCustErrorEnum |
17 | 32 | from cloudinit.sources.helpers.vmware.imc.guestcust_event \ | 34 | from cloudinit.sources.helpers.vmware.imc.guestcust_event \ |
19 | 33 | import GuestCustEventEnum | 35 | import GuestCustEventEnum as GuestCustEvent |
20 | 34 | from cloudinit.sources.helpers.vmware.imc.guestcust_state \ | 36 | from cloudinit.sources.helpers.vmware.imc.guestcust_state \ |
21 | 35 | import GuestCustStateEnum | 37 | import GuestCustStateEnum |
22 | 36 | from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( | 38 | from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( |
23 | @@ -124,17 +126,31 @@ class DataSourceOVF(sources.DataSource): | |||
24 | 124 | self._vmware_cust_conf = Config(cf) | 126 | self._vmware_cust_conf = Config(cf) |
25 | 125 | (md, ud, cfg) = read_vmware_imc(self._vmware_cust_conf) | 127 | (md, ud, cfg) = read_vmware_imc(self._vmware_cust_conf) |
26 | 126 | self._vmware_nics_to_enable = get_nics_to_enable(nicspath) | 128 | self._vmware_nics_to_enable = get_nics_to_enable(nicspath) |
29 | 127 | markerid = self._vmware_cust_conf.marker_id | 129 | imcdirpath = os.path.dirname(vmwareImcConfigFilePath) |
30 | 128 | markerexists = check_marker_exists(markerid) | 130 | product_marker = self._vmware_cust_conf.marker_id |
31 | 131 | hasmarkerfile = check_marker_exists( | ||
32 | 132 | product_marker, os.path.join(self.paths.cloud_dir, 'data')) | ||
33 | 133 | special_customization = product_marker and not hasmarkerfile | ||
34 | 134 | customscript = self._vmware_cust_conf.custom_script_name | ||
35 | 129 | except Exception as e: | 135 | except Exception as e: |
44 | 130 | LOG.debug("Error parsing the customization Config File") | 136 | _raise_error_status( |
45 | 131 | LOG.exception(e) | 137 | "Error parsing the customization Config File", |
46 | 132 | set_customization_status( | 138 | e, |
47 | 133 | GuestCustStateEnum.GUESTCUST_STATE_RUNNING, | 139 | GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED, |
48 | 134 | GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED) | 140 | vmwareImcConfigFilePath) |
49 | 135 | raise e | 141 | |
50 | 136 | finally: | 142 | if special_customization: |
51 | 137 | util.del_dir(os.path.dirname(vmwareImcConfigFilePath)) | 143 | if customscript: |
52 | 144 | try: | ||
53 | 145 | precust = PreCustomScript(customscript, imcdirpath) | ||
54 | 146 | precust.execute() | ||
55 | 147 | except Exception as e: | ||
56 | 148 | _raise_error_status( | ||
57 | 149 | "Error executing pre-customization script", | ||
58 | 150 | e, | ||
59 | 151 | GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED, | ||
60 | 152 | vmwareImcConfigFilePath) | ||
61 | 153 | |||
62 | 138 | try: | 154 | try: |
63 | 139 | LOG.debug("Preparing the Network configuration") | 155 | LOG.debug("Preparing the Network configuration") |
64 | 140 | self._network_config = get_network_config_from_conf( | 156 | self._network_config = get_network_config_from_conf( |
65 | @@ -143,13 +159,13 @@ class DataSourceOVF(sources.DataSource): | |||
66 | 143 | True, | 159 | True, |
67 | 144 | self.distro.osfamily) | 160 | self.distro.osfamily) |
68 | 145 | except Exception as e: | 161 | except Exception as e: |
74 | 146 | LOG.exception(e) | 162 | _raise_error_status( |
75 | 147 | set_customization_status( | 163 | "Error preparing Network Configuration", |
76 | 148 | GuestCustStateEnum.GUESTCUST_STATE_RUNNING, | 164 | e, |
77 | 149 | GuestCustEventEnum.GUESTCUST_EVENT_NETWORK_SETUP_FAILED) | 165 | GuestCustEvent.GUESTCUST_EVENT_NETWORK_SETUP_FAILED, |
78 | 150 | raise e | 166 | vmwareImcConfigFilePath) |
79 | 151 | 167 | ||
81 | 152 | if markerid and not markerexists: | 168 | if special_customization: |
82 | 153 | LOG.debug("Applying password customization") | 169 | LOG.debug("Applying password customization") |
83 | 154 | pwdConfigurator = PasswordConfigurator() | 170 | pwdConfigurator = PasswordConfigurator() |
84 | 155 | adminpwd = self._vmware_cust_conf.admin_password | 171 | adminpwd = self._vmware_cust_conf.admin_password |
85 | @@ -161,27 +177,41 @@ class DataSourceOVF(sources.DataSource): | |||
86 | 161 | else: | 177 | else: |
87 | 162 | LOG.debug("Changing password is not needed") | 178 | LOG.debug("Changing password is not needed") |
88 | 163 | except Exception as e: | 179 | except Exception as e: |
96 | 164 | LOG.debug("Error applying Password Configuration: %s", e) | 180 | _raise_error_status( |
97 | 165 | set_customization_status( | 181 | "Error applying Password Configuration", |
98 | 166 | GuestCustStateEnum.GUESTCUST_STATE_RUNNING, | 182 | e, |
99 | 167 | GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED) | 183 | GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED, |
100 | 168 | return False | 184 | vmwareImcConfigFilePath) |
101 | 169 | if markerid: | 185 | |
102 | 170 | LOG.debug("Handle marker creation") | 186 | if customscript: |
103 | 187 | try: | ||
104 | 188 | postcust = PostCustomScript(customscript, imcdirpath) | ||
105 | 189 | postcust.execute() | ||
106 | 190 | except Exception as e: | ||
107 | 191 | _raise_error_status( | ||
108 | 192 | "Error executing post-customization script", | ||
109 | 193 | e, | ||
110 | 194 | GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED, | ||
111 | 195 | vmwareImcConfigFilePath) | ||
112 | 196 | |||
113 | 197 | if product_marker: | ||
114 | 171 | try: | 198 | try: |
116 | 172 | setup_marker_files(markerid) | 199 | setup_marker_files( |
117 | 200 | product_marker, | ||
118 | 201 | os.path.join(self.paths.cloud_dir, 'data')) | ||
119 | 173 | except Exception as e: | 202 | except Exception as e: |
125 | 174 | LOG.debug("Error creating marker files: %s", e) | 203 | _raise_error_status( |
126 | 175 | set_customization_status( | 204 | "Error creating marker files", |
127 | 176 | GuestCustStateEnum.GUESTCUST_STATE_RUNNING, | 205 | e, |
128 | 177 | GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED) | 206 | GuestCustEvent.GUESTCUST_EVENT_CUSTOMIZE_FAILED, |
129 | 178 | return False | 207 | vmwareImcConfigFilePath) |
130 | 179 | 208 | ||
131 | 180 | self._vmware_cust_found = True | 209 | self._vmware_cust_found = True |
132 | 181 | found.append('vmware-tools') | 210 | found.append('vmware-tools') |
133 | 182 | 211 | ||
134 | 183 | # TODO: Need to set the status to DONE only when the | 212 | # TODO: Need to set the status to DONE only when the |
135 | 184 | # customization is done successfully. | 213 | # customization is done successfully. |
136 | 214 | util.del_dir(os.path.dirname(vmwareImcConfigFilePath)) | ||
137 | 185 | enable_nics(self._vmware_nics_to_enable) | 215 | enable_nics(self._vmware_nics_to_enable) |
138 | 186 | set_customization_status( | 216 | set_customization_status( |
139 | 187 | GuestCustStateEnum.GUESTCUST_STATE_DONE, | 217 | GuestCustStateEnum.GUESTCUST_STATE_DONE, |
140 | @@ -536,31 +566,52 @@ def get_datasource_list(depends): | |||
141 | 536 | 566 | ||
142 | 537 | 567 | ||
143 | 538 | # To check if marker file exists | 568 | # To check if marker file exists |
145 | 539 | def check_marker_exists(markerid): | 569 | def check_marker_exists(markerid, marker_dir): |
146 | 540 | """ | 570 | """ |
147 | 541 | Check the existence of a marker file. | 571 | Check the existence of a marker file. |
148 | 542 | Presence of marker file determines whether a certain code path is to be | 572 | Presence of marker file determines whether a certain code path is to be |
149 | 543 | executed. It is needed for partial guest customization in VMware. | 573 | executed. It is needed for partial guest customization in VMware. |
150 | 574 | @param markerid: is an unique string representing a particular product | ||
151 | 575 | marker. | ||
152 | 576 | @param: marker_dir: The directory in which markers exist. | ||
153 | 544 | """ | 577 | """ |
154 | 545 | if not markerid: | 578 | if not markerid: |
155 | 546 | return False | 579 | return False |
157 | 547 | markerfile = "/.markerfile-" + markerid | 580 | markerfile = os.path.join(marker_dir, ".markerfile-" + markerid + ".txt") |
158 | 548 | if os.path.exists(markerfile): | 581 | if os.path.exists(markerfile): |
159 | 549 | return True | 582 | return True |
160 | 550 | return False | 583 | return False |
161 | 551 | 584 | ||
162 | 552 | 585 | ||
163 | 553 | # Create a marker file | 586 | # Create a marker file |
165 | 554 | def setup_marker_files(markerid): | 587 | def setup_marker_files(markerid, marker_dir): |
166 | 555 | """ | 588 | """ |
167 | 556 | Create a new marker file. | 589 | Create a new marker file. |
168 | 557 | Marker files are unique to a full customization workflow in VMware | 590 | Marker files are unique to a full customization workflow in VMware |
169 | 558 | environment. | 591 | environment. |
170 | 592 | @param markerid: is an unique string representing a particular product | ||
171 | 593 | marker. | ||
172 | 594 | @param: marker_dir: The directory in which markers exist. | ||
173 | 595 | |||
174 | 559 | """ | 596 | """ |
179 | 560 | if not markerid: | 597 | LOG.debug("Handle marker creation") |
180 | 561 | return | 598 | markerfile = os.path.join(marker_dir, ".markerfile-" + markerid + ".txt") |
181 | 562 | markerfile = "/.markerfile-" + markerid | 599 | for fname in os.listdir(marker_dir): |
182 | 563 | util.del_file("/.markerfile-*.txt") | 600 | if fname.startswith(".markerfile"): |
183 | 601 | util.del_file(os.path.join(marker_dir, fname)) | ||
184 | 564 | open(markerfile, 'w').close() | 602 | open(markerfile, 'w').close() |
185 | 565 | 603 | ||
186 | 604 | |||
187 | 605 | def _raise_error_status(prefix, error, event, config_file): | ||
188 | 606 | """ | ||
189 | 607 | Raise error and send customization status to the underlying VMware | ||
190 | 608 | Virtualization Platform. Also, cleanup the imc directory. | ||
191 | 609 | """ | ||
192 | 610 | LOG.debug('%s: %s', prefix, error) | ||
193 | 611 | set_customization_status( | ||
194 | 612 | GuestCustStateEnum.GUESTCUST_STATE_RUNNING, | ||
195 | 613 | event) | ||
196 | 614 | util.del_dir(os.path.dirname(config_file)) | ||
197 | 615 | raise error | ||
198 | 616 | |||
199 | 566 | # vi: ts=4 expandtab | 617 | # vi: ts=4 expandtab |
200 | diff --git a/cloudinit/sources/helpers/vmware/imc/config.py b/cloudinit/sources/helpers/vmware/imc/config.py | |||
201 | index 49d441d..2eaeff3 100644 | |||
202 | --- a/cloudinit/sources/helpers/vmware/imc/config.py | |||
203 | +++ b/cloudinit/sources/helpers/vmware/imc/config.py | |||
204 | @@ -100,4 +100,8 @@ class Config(object): | |||
205 | 100 | """Returns marker id.""" | 100 | """Returns marker id.""" |
206 | 101 | return self._configFile.get(Config.MARKERID, None) | 101 | return self._configFile.get(Config.MARKERID, None) |
207 | 102 | 102 | ||
208 | 103 | @property | ||
209 | 104 | def custom_script_name(self): | ||
210 | 105 | """Return the name of custom (pre/post) script.""" | ||
211 | 106 | return self._configFile.get(Config.CUSTOM_SCRIPT, None) | ||
212 | 103 | # vi: ts=4 expandtab | 107 | # vi: ts=4 expandtab |
213 | diff --git a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py | |||
214 | 104 | new file mode 100644 | 108 | new file mode 100644 |
215 | index 0000000..a7d4ad9 | |||
216 | --- /dev/null | |||
217 | +++ b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py | |||
218 | @@ -0,0 +1,153 @@ | |||
219 | 1 | # Copyright (C) 2017 Canonical Ltd. | ||
220 | 2 | # Copyright (C) 2017 VMware Inc. | ||
221 | 3 | # | ||
222 | 4 | # Author: Maitreyee Saikia <msaikia@vmware.com> | ||
223 | 5 | # | ||
224 | 6 | # This file is part of cloud-init. See LICENSE file for license information. | ||
225 | 7 | |||
226 | 8 | import logging | ||
227 | 9 | import os | ||
228 | 10 | import stat | ||
229 | 11 | from textwrap import dedent | ||
230 | 12 | |||
231 | 13 | from cloudinit import util | ||
232 | 14 | |||
233 | 15 | LOG = logging.getLogger(__name__) | ||
234 | 16 | |||
235 | 17 | |||
236 | 18 | class CustomScriptNotFound(Exception): | ||
237 | 19 | pass | ||
238 | 20 | |||
239 | 21 | |||
240 | 22 | class CustomScriptConstant(object): | ||
241 | 23 | RC_LOCAL = "/etc/rc.local" | ||
242 | 24 | POST_CUST_TMP_DIR = "/root/.customization" | ||
243 | 25 | POST_CUST_RUN_SCRIPT_NAME = "post-customize-guest.sh" | ||
244 | 26 | POST_CUST_RUN_SCRIPT = os.path.join(POST_CUST_TMP_DIR, | ||
245 | 27 | POST_CUST_RUN_SCRIPT_NAME) | ||
246 | 28 | POST_REBOOT_PENDING_MARKER = "/.guest-customization-post-reboot-pending" | ||
247 | 29 | |||
248 | 30 | |||
249 | 31 | class RunCustomScript(object): | ||
250 | 32 | def __init__(self, scriptname, directory): | ||
251 | 33 | self.scriptname = scriptname | ||
252 | 34 | self.directory = directory | ||
253 | 35 | self.scriptpath = os.path.join(directory, scriptname) | ||
254 | 36 | |||
255 | 37 | def prepare_script(self): | ||
256 | 38 | if not os.path.exists(self.scriptpath): | ||
257 | 39 | raise CustomScriptNotFound("Script %s not found!! " | ||
258 | 40 | "Cannot execute custom script!" | ||
259 | 41 | % self.scriptpath) | ||
260 | 42 | # Strip any CR characters from the decoded script | ||
261 | 43 | util.load_file(self.scriptpath).replace("\r", "") | ||
262 | 44 | st = os.stat(self.scriptpath) | ||
263 | 45 | os.chmod(self.scriptpath, st.st_mode | stat.S_IEXEC) | ||
264 | 46 | |||
265 | 47 | |||
266 | 48 | class PreCustomScript(RunCustomScript): | ||
267 | 49 | def execute(self): | ||
268 | 50 | """Executing custom script with precustomization argument.""" | ||
269 | 51 | LOG.debug("Executing pre-customization script") | ||
270 | 52 | self.prepare_script() | ||
271 | 53 | util.subp(["/bin/sh", self.scriptpath, "precustomization"]) | ||
272 | 54 | |||
273 | 55 | |||
274 | 56 | class PostCustomScript(RunCustomScript): | ||
275 | 57 | def __init__(self, scriptname, directory): | ||
276 | 58 | super(PostCustomScript, self).__init__(scriptname, directory) | ||
277 | 59 | # Determine when to run custom script. When postreboot is True, | ||
278 | 60 | # the user uploaded script will run as part of rc.local after | ||
279 | 61 | # the machine reboots. This is determined by presence of rclocal. | ||
280 | 62 | # When postreboot is False, script will run as part of cloud-init. | ||
281 | 63 | self.postreboot = False | ||
282 | 64 | |||
283 | 65 | def _install_post_reboot_agent(self, rclocal): | ||
284 | 66 | """ | ||
285 | 67 | Install post-reboot agent for running custom script after reboot. | ||
286 | 68 | As part of this process, we are editing the rclocal file to run a | ||
287 | 69 | VMware script, which in turn is resposible for handling the user | ||
288 | 70 | script. | ||
289 | 71 | @param: path to rc local. | ||
290 | 72 | """ | ||
291 | 73 | LOG.debug("Installing post-reboot customization from %s to %s", | ||
292 | 74 | self.directory, rclocal) | ||
293 | 75 | if not self.has_previous_agent(rclocal): | ||
294 | 76 | LOG.info("Adding post-reboot customization agent to rc.local") | ||
295 | 77 | new_content = dedent(""" | ||
296 | 78 | # Run post-reboot guest customization | ||
297 | 79 | /bin/sh %s | ||
298 | 80 | exit 0 | ||
299 | 81 | """) % CustomScriptConstant.POST_CUST_RUN_SCRIPT | ||
300 | 82 | existing_rclocal = util.load_file(rclocal).replace('exit 0\n', '') | ||
301 | 83 | st = os.stat(rclocal) | ||
302 | 84 | # "x" flag should be set | ||
303 | 85 | mode = st.st_mode | stat.S_IEXEC | ||
304 | 86 | util.write_file(rclocal, existing_rclocal + new_content, mode) | ||
305 | 87 | |||
306 | 88 | else: | ||
307 | 89 | # We don't need to update rclocal file everytime a customization | ||
308 | 90 | # is requested. It just needs to be done for the first time. | ||
309 | 91 | LOG.info("Post-reboot guest customization agent is already " | ||
310 | 92 | "registered in rc.local") | ||
311 | 93 | LOG.debug("Installing post-reboot customization agent finished: %s", | ||
312 | 94 | self.postreboot) | ||
313 | 95 | |||
314 | 96 | def has_previous_agent(self, rclocal): | ||
315 | 97 | searchstring = "# Run post-reboot guest customization" | ||
316 | 98 | if searchstring in open(rclocal).read(): | ||
317 | 99 | return True | ||
318 | 100 | return False | ||
319 | 101 | |||
320 | 102 | def find_rc_local(self): | ||
321 | 103 | """ | ||
322 | 104 | Determine if rc local is present. | ||
323 | 105 | """ | ||
324 | 106 | rclocal = "" | ||
325 | 107 | if os.path.exists(CustomScriptConstant.RC_LOCAL): | ||
326 | 108 | LOG.debug("rc.local detected.") | ||
327 | 109 | # resolving in case of symlink | ||
328 | 110 | rclocal = os.path.realpath(CustomScriptConstant.RC_LOCAL) | ||
329 | 111 | LOG.debug("rc.local resolved to %s", rclocal) | ||
330 | 112 | else: | ||
331 | 113 | LOG.warning("Can't find rc.local, post-customization " | ||
332 | 114 | "will be run before reboot") | ||
333 | 115 | return rclocal | ||
334 | 116 | |||
335 | 117 | def install_agent(self): | ||
336 | 118 | rclocal = self.find_rc_local() | ||
337 | 119 | if rclocal: | ||
338 | 120 | self._install_post_reboot_agent(rclocal) | ||
339 | 121 | self.postreboot = True | ||
340 | 122 | |||
341 | 123 | def execute(self): | ||
342 | 124 | """ | ||
343 | 125 | This method executes post-customization script before or after reboot | ||
344 | 126 | based on the presence of rc local. | ||
345 | 127 | """ | ||
346 | 128 | self.prepare_script() | ||
347 | 129 | self.install_agent() | ||
348 | 130 | if not self.postreboot: | ||
349 | 131 | LOG.warning("Executing post-customization script inline") | ||
350 | 132 | util.subp(["/bin/sh", self.scriptpath, "postcustomization"]) | ||
351 | 133 | else: | ||
352 | 134 | LOG.debug("Scheduling custom script to run post reboot") | ||
353 | 135 | if not os.path.isdir(CustomScriptConstant.POST_CUST_TMP_DIR): | ||
354 | 136 | os.mkdir(CustomScriptConstant.POST_CUST_TMP_DIR) | ||
355 | 137 | # Script "post-customize-guest.sh" and user uploaded script are | ||
356 | 138 | # are present in the same directory and needs to copied to a temp | ||
357 | 139 | # directory to be executed post reboot. User uploaded script is | ||
358 | 140 | # saved as customize.sh in the temp directory. | ||
359 | 141 | # post-customize-guest.sh excutes customize.sh after reboot. | ||
360 | 142 | LOG.debug("Copying post-customization script") | ||
361 | 143 | util.copy(self.scriptpath, | ||
362 | 144 | CustomScriptConstant.POST_CUST_TMP_DIR + "/customize.sh") | ||
363 | 145 | LOG.debug("Copying script to run post-customization script") | ||
364 | 146 | util.copy( | ||
365 | 147 | os.path.join(self.directory, | ||
366 | 148 | CustomScriptConstant.POST_CUST_RUN_SCRIPT_NAME), | ||
367 | 149 | CustomScriptConstant.POST_CUST_RUN_SCRIPT) | ||
368 | 150 | LOG.info("Creating post-reboot pending marker") | ||
369 | 151 | util.ensure_file(CustomScriptConstant.POST_REBOOT_PENDING_MARKER) | ||
370 | 152 | |||
371 | 153 | # vi: ts=4 expandtab | ||
372 | diff --git a/cloudinit/sources/helpers/vmware/imc/config_nic.py b/cloudinit/sources/helpers/vmware/imc/config_nic.py | |||
373 | index 2fb07c5..2d8900e 100644 | |||
374 | --- a/cloudinit/sources/helpers/vmware/imc/config_nic.py | |||
375 | +++ b/cloudinit/sources/helpers/vmware/imc/config_nic.py | |||
376 | @@ -161,7 +161,7 @@ class NicConfigurator(object): | |||
377 | 161 | if nic.primary and v4.gateways: | 161 | if nic.primary and v4.gateways: |
378 | 162 | self.ipv4PrimaryGateway = v4.gateways[0] | 162 | self.ipv4PrimaryGateway = v4.gateways[0] |
379 | 163 | subnet.update({'gateway': self.ipv4PrimaryGateway}) | 163 | subnet.update({'gateway': self.ipv4PrimaryGateway}) |
381 | 164 | return [subnet] | 164 | return ([subnet], route_list) |
382 | 165 | 165 | ||
383 | 166 | # Add routes if there is no primary nic | 166 | # Add routes if there is no primary nic |
384 | 167 | if not self._primaryNic: | 167 | if not self._primaryNic: |
385 | diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py | |||
386 | index 700da86..fc4eb36 100644 | |||
387 | --- a/tests/unittests/test_datasource/test_ovf.py | |||
388 | +++ b/tests/unittests/test_datasource/test_ovf.py | |||
389 | @@ -5,11 +5,17 @@ | |||
390 | 5 | # This file is part of cloud-init. See LICENSE file for license information. | 5 | # This file is part of cloud-init. See LICENSE file for license information. |
391 | 6 | 6 | ||
392 | 7 | import base64 | 7 | import base64 |
394 | 8 | from collections import OrderedDict | 8 | import os |
395 | 9 | 9 | ||
397 | 10 | from cloudinit.tests import helpers as test_helpers | 10 | from collections import OrderedDict |
398 | 11 | from textwrap import dedent | ||
399 | 11 | 12 | ||
400 | 13 | from cloudinit import util | ||
401 | 14 | from cloudinit.tests.helpers import CiTestCase, wrap_and_call | ||
402 | 15 | from cloudinit.helpers import Paths | ||
403 | 12 | from cloudinit.sources import DataSourceOVF as dsovf | 16 | from cloudinit.sources import DataSourceOVF as dsovf |
404 | 17 | from cloudinit.sources.helpers.vmware.imc.config_custom_script import ( | ||
405 | 18 | CustomScriptNotFound) | ||
406 | 13 | 19 | ||
407 | 14 | OVF_ENV_CONTENT = """<?xml version="1.0" encoding="UTF-8"?> | 20 | OVF_ENV_CONTENT = """<?xml version="1.0" encoding="UTF-8"?> |
408 | 15 | <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" | 21 | <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" |
409 | @@ -42,7 +48,7 @@ def fill_properties(props, template=OVF_ENV_CONTENT): | |||
410 | 42 | return template.format(properties=properties) | 48 | return template.format(properties=properties) |
411 | 43 | 49 | ||
412 | 44 | 50 | ||
414 | 45 | class TestReadOvfEnv(test_helpers.TestCase): | 51 | class TestReadOvfEnv(CiTestCase): |
415 | 46 | def test_with_b64_userdata(self): | 52 | def test_with_b64_userdata(self): |
416 | 47 | user_data = "#!/bin/sh\necho hello world\n" | 53 | user_data = "#!/bin/sh\necho hello world\n" |
417 | 48 | user_data_b64 = base64.b64encode(user_data.encode()).decode() | 54 | user_data_b64 = base64.b64encode(user_data.encode()).decode() |
418 | @@ -72,7 +78,104 @@ class TestReadOvfEnv(test_helpers.TestCase): | |||
419 | 72 | self.assertIsNone(ud) | 78 | self.assertIsNone(ud) |
420 | 73 | 79 | ||
421 | 74 | 80 | ||
423 | 75 | class TestTransportIso9660(test_helpers.CiTestCase): | 81 | class TestMarkerFiles(CiTestCase): |
424 | 82 | |||
425 | 83 | def setUp(self): | ||
426 | 84 | super(TestMarkerFiles, self).setUp() | ||
427 | 85 | self.tdir = self.tmp_dir() | ||
428 | 86 | |||
429 | 87 | def test_false_when_markerid_none(self): | ||
430 | 88 | """Return False when markerid provided is None.""" | ||
431 | 89 | self.assertFalse( | ||
432 | 90 | dsovf.check_marker_exists(markerid=None, marker_dir=self.tdir)) | ||
433 | 91 | |||
434 | 92 | def test_markerid_file_exist(self): | ||
435 | 93 | """Return False when markerid file path does not exist, | ||
436 | 94 | True otherwise.""" | ||
437 | 95 | self.assertFalse( | ||
438 | 96 | dsovf.check_marker_exists('123', self.tdir)) | ||
439 | 97 | |||
440 | 98 | marker_file = self.tmp_path('.markerfile-123.txt', self.tdir) | ||
441 | 99 | util.write_file(marker_file, '') | ||
442 | 100 | self.assertTrue( | ||
443 | 101 | dsovf.check_marker_exists('123', self.tdir) | ||
444 | 102 | ) | ||
445 | 103 | |||
446 | 104 | def test_marker_file_setup(self): | ||
447 | 105 | """Test creation of marker files.""" | ||
448 | 106 | markerfilepath = self.tmp_path('.markerfile-hi.txt', self.tdir) | ||
449 | 107 | self.assertFalse(os.path.exists(markerfilepath)) | ||
450 | 108 | dsovf.setup_marker_files(markerid='hi', marker_dir=self.tdir) | ||
451 | 109 | self.assertTrue(os.path.exists(markerfilepath)) | ||
452 | 110 | |||
453 | 111 | |||
454 | 112 | class TestDatasourceOVF(CiTestCase): | ||
455 | 113 | |||
456 | 114 | with_logs = True | ||
457 | 115 | |||
458 | 116 | def setUp(self): | ||
459 | 117 | super(TestDatasourceOVF, self).setUp() | ||
460 | 118 | self.datasource = dsovf.DataSourceOVF | ||
461 | 119 | self.tdir = self.tmp_dir() | ||
462 | 120 | |||
463 | 121 | def test_get_data_false_on_none_dmi_data(self): | ||
464 | 122 | """When dmi for system-product-name is None, get_data returns False.""" | ||
465 | 123 | paths = Paths({'seed_dir': self.tdir}) | ||
466 | 124 | ds = self.datasource(sys_cfg={}, distro={}, paths=paths) | ||
467 | 125 | retcode = wrap_and_call( | ||
468 | 126 | 'cloudinit.sources.DataSourceOVF', | ||
469 | 127 | {'util.read_dmi_data': None}, | ||
470 | 128 | ds.get_data) | ||
471 | 129 | self.assertFalse(retcode, 'Expected False return from ds.get_data') | ||
472 | 130 | self.assertIn( | ||
473 | 131 | 'DEBUG: No system-product-name found', self.logs.getvalue()) | ||
474 | 132 | |||
475 | 133 | def test_get_data_no_vmware_customization_disabled(self): | ||
476 | 134 | """When vmware customization is disabled via sys_cfg log a message.""" | ||
477 | 135 | paths = Paths({'seed_dir': self.tdir}) | ||
478 | 136 | ds = self.datasource( | ||
479 | 137 | sys_cfg={'disable_vmware_customization': True}, distro={}, | ||
480 | 138 | paths=paths) | ||
481 | 139 | retcode = wrap_and_call( | ||
482 | 140 | 'cloudinit.sources.DataSourceOVF', | ||
483 | 141 | {'util.read_dmi_data': 'vmware'}, | ||
484 | 142 | ds.get_data) | ||
485 | 143 | self.assertFalse(retcode, 'Expected False return from ds.get_data') | ||
486 | 144 | self.assertIn( | ||
487 | 145 | 'DEBUG: Customization for VMware platform is disabled.', | ||
488 | 146 | self.logs.getvalue()) | ||
489 | 147 | |||
490 | 148 | def test_get_data_vmware_customization_disabled(self): | ||
491 | 149 | """When cloud-init workflow for vmware is enabled via sys_cfg log a | ||
492 | 150 | message. | ||
493 | 151 | """ | ||
494 | 152 | paths = Paths({'seed_dir': self.tdir}) | ||
495 | 153 | ds = self.datasource( | ||
496 | 154 | sys_cfg={'disable_vmware_customization': False}, distro={}, | ||
497 | 155 | paths=paths) | ||
498 | 156 | conf_file = self.tmp_path('test-cust', self.tdir) | ||
499 | 157 | conf_content = dedent("""\ | ||
500 | 158 | [CUSTOM-SCRIPT] | ||
501 | 159 | SCRIPT-NAME = test-script | ||
502 | 160 | [MISC] | ||
503 | 161 | MARKER-ID = 12345345 | ||
504 | 162 | """) | ||
505 | 163 | util.write_file(conf_file, conf_content) | ||
506 | 164 | with self.assertRaises(CustomScriptNotFound) as context: | ||
507 | 165 | wrap_and_call( | ||
508 | 166 | 'cloudinit.sources.DataSourceOVF', | ||
509 | 167 | {'util.read_dmi_data': 'vmware', | ||
510 | 168 | 'util.del_dir': True, | ||
511 | 169 | 'search_file': self.tdir, | ||
512 | 170 | 'wait_for_imc_cfg_file': conf_file, | ||
513 | 171 | 'get_nics_to_enable': ''}, | ||
514 | 172 | ds.get_data) | ||
515 | 173 | customscript = self.tmp_path('test-script', self.tdir) | ||
516 | 174 | self.assertIn('Script %s not found!!' % customscript, | ||
517 | 175 | str(context.exception)) | ||
518 | 176 | |||
519 | 177 | |||
520 | 178 | class TestTransportIso9660(CiTestCase): | ||
521 | 76 | 179 | ||
522 | 77 | def setUp(self): | 180 | def setUp(self): |
523 | 78 | super(TestTransportIso9660, self).setUp() | 181 | super(TestTransportIso9660, self).setUp() |
524 | diff --git a/tests/unittests/test_vmware/__init__.py b/tests/unittests/test_vmware/__init__.py | |||
525 | 79 | new file mode 100644 | 182 | new file mode 100644 |
526 | index 0000000..e69de29 | |||
527 | --- /dev/null | |||
528 | +++ b/tests/unittests/test_vmware/__init__.py | |||
529 | diff --git a/tests/unittests/test_vmware/test_custom_script.py b/tests/unittests/test_vmware/test_custom_script.py | |||
530 | 80 | new file mode 100644 | 183 | new file mode 100644 |
531 | index 0000000..2d9519b | |||
532 | --- /dev/null | |||
533 | +++ b/tests/unittests/test_vmware/test_custom_script.py | |||
534 | @@ -0,0 +1,99 @@ | |||
535 | 1 | # Copyright (C) 2015 Canonical Ltd. | ||
536 | 2 | # Copyright (C) 2017 VMware INC. | ||
537 | 3 | # | ||
538 | 4 | # Author: Maitreyee Saikia <msaikia@vmware.com> | ||
539 | 5 | # | ||
540 | 6 | # This file is part of cloud-init. See LICENSE file for license information. | ||
541 | 7 | |||
542 | 8 | from cloudinit import util | ||
543 | 9 | from cloudinit.sources.helpers.vmware.imc.config_custom_script import ( | ||
544 | 10 | CustomScriptConstant, | ||
545 | 11 | CustomScriptNotFound, | ||
546 | 12 | PreCustomScript, | ||
547 | 13 | PostCustomScript, | ||
548 | 14 | ) | ||
549 | 15 | from cloudinit.tests.helpers import CiTestCase, mock | ||
550 | 16 | |||
551 | 17 | |||
552 | 18 | class TestVmwareCustomScript(CiTestCase): | ||
553 | 19 | def setUp(self): | ||
554 | 20 | self.tmpDir = self.tmp_dir() | ||
555 | 21 | |||
556 | 22 | def test_prepare_custom_script(self): | ||
557 | 23 | """ | ||
558 | 24 | This test is designed to verify the behavior based on the presence of | ||
559 | 25 | custom script. Mainly needed for scenario where a custom script is | ||
560 | 26 | expected, but was not properly copied. "CustomScriptNotFound" exception | ||
561 | 27 | is raised in such cases. | ||
562 | 28 | """ | ||
563 | 29 | # Custom script does not exist. | ||
564 | 30 | preCust = PreCustomScript("random-vmw-test", self.tmpDir) | ||
565 | 31 | self.assertEqual("random-vmw-test", preCust.scriptname) | ||
566 | 32 | self.assertEqual(self.tmpDir, preCust.directory) | ||
567 | 33 | self.assertEqual(self.tmp_path("random-vmw-test", self.tmpDir), | ||
568 | 34 | preCust.scriptpath) | ||
569 | 35 | with self.assertRaises(CustomScriptNotFound): | ||
570 | 36 | preCust.prepare_script() | ||
571 | 37 | |||
572 | 38 | # Custom script exists. | ||
573 | 39 | custScript = self.tmp_path("test-cust", self.tmpDir) | ||
574 | 40 | util.write_file(custScript, "test-CR-strip/r/r") | ||
575 | 41 | postCust = PostCustomScript("test-cust", self.tmpDir) | ||
576 | 42 | self.assertEqual("test-cust", postCust.scriptname) | ||
577 | 43 | self.assertEqual(self.tmpDir, postCust.directory) | ||
578 | 44 | self.assertEqual(custScript, postCust.scriptpath) | ||
579 | 45 | self.assertFalse(postCust.postreboot) | ||
580 | 46 | postCust.prepare_script() | ||
581 | 47 | # Check if all carraige returns are stripped from script. | ||
582 | 48 | self.assertFalse("/r" in custScript) | ||
583 | 49 | |||
584 | 50 | def test_rc_local_exists(self): | ||
585 | 51 | """ | ||
586 | 52 | This test is designed to verify the different scenarios associated | ||
587 | 53 | with the presence of rclocal. | ||
588 | 54 | """ | ||
589 | 55 | # test when rc local does not exist | ||
590 | 56 | postCust = PostCustomScript("test-cust", self.tmpDir) | ||
591 | 57 | with mock.patch.object(CustomScriptConstant, "RC_LOCAL", "/no/path"): | ||
592 | 58 | rclocal = postCust.find_rc_local() | ||
593 | 59 | self.assertEqual("", rclocal) | ||
594 | 60 | |||
595 | 61 | # test when rc local exists | ||
596 | 62 | rclocalFile = self.tmp_path("vmware-rclocal", self.tmpDir) | ||
597 | 63 | util.write_file(rclocalFile, "# Run post-reboot guest customization", | ||
598 | 64 | omode="w") | ||
599 | 65 | with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalFile): | ||
600 | 66 | rclocal = postCust.find_rc_local() | ||
601 | 67 | self.assertEqual(rclocalFile, rclocal) | ||
602 | 68 | self.assertTrue(postCust.has_previous_agent, rclocal) | ||
603 | 69 | |||
604 | 70 | # test when rc local is a symlink | ||
605 | 71 | rclocalLink = self.tmp_path("dummy-rclocal-link", self.tmpDir) | ||
606 | 72 | util.sym_link(rclocalFile, rclocalLink, True) | ||
607 | 73 | with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalLink): | ||
608 | 74 | rclocal = postCust.find_rc_local() | ||
609 | 75 | self.assertEqual(rclocalFile, rclocal) | ||
610 | 76 | |||
611 | 77 | def test_execute_post_cust(self): | ||
612 | 78 | """ | ||
613 | 79 | This test is to identify if rclocal was properly populated to be | ||
614 | 80 | run after reboot. | ||
615 | 81 | """ | ||
616 | 82 | customscript = self.tmp_path("vmware-post-cust-script", self.tmpDir) | ||
617 | 83 | rclocal = self.tmp_path("vmware-rclocal", self.tmpDir) | ||
618 | 84 | # Create a temporary rclocal file | ||
619 | 85 | open(customscript, "w") | ||
620 | 86 | util.write_file(rclocal, "tests\nexit 0", omode="w") | ||
621 | 87 | postCust = PostCustomScript("vmware-post-cust-script", self.tmpDir) | ||
622 | 88 | with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocal): | ||
623 | 89 | # Test that guest customization agent is not installed initially. | ||
624 | 90 | self.assertFalse(postCust.postreboot) | ||
625 | 91 | self.assertIs(postCust.has_previous_agent(rclocal), False) | ||
626 | 92 | postCust.install_agent() | ||
627 | 93 | |||
628 | 94 | # Assert rclocal has been modified to have guest customization | ||
629 | 95 | # agent. | ||
630 | 96 | self.assertTrue(postCust.postreboot) | ||
631 | 97 | self.assertTrue(postCust.has_previous_agent, rclocal) | ||
632 | 98 | |||
633 | 99 | # vi: ts=4 expandtab | ||
634 | diff --git a/tests/unittests/test_vmware_config_file.py b/tests/unittests/test_vmware_config_file.py | |||
635 | index 808d303..455d6c2 100644 | |||
636 | --- a/tests/unittests/test_vmware_config_file.py | |||
637 | +++ b/tests/unittests/test_vmware_config_file.py | |||
638 | @@ -334,5 +334,12 @@ class TestVmwareConfigFile(CiTestCase): | |||
639 | 334 | self.assertEqual('255.255.0.0', subnet.get('netmask'), | 334 | self.assertEqual('255.255.0.0', subnet.get('netmask'), |
640 | 335 | 'Subnet netmask') | 335 | 'Subnet netmask') |
641 | 336 | 336 | ||
642 | 337 | def test_custom_script(self): | ||
643 | 338 | cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg") | ||
644 | 339 | conf = Config(cf) | ||
645 | 340 | self.assertIsNone(conf.custom_script_name) | ||
646 | 341 | cf._insertKey("CUSTOM-SCRIPT|SCRIPT-NAME", "test-script") | ||
647 | 342 | conf = Config(cf) | ||
648 | 343 | self.assertEqual("test-script", conf.custom_script_name) | ||
649 | 337 | 344 | ||
650 | 338 | # vi: ts=4 expandtab | 345 | # vi: ts=4 expandtab |
PASSED: Continuous integration, rev:dc7653be740 c682a325f31162a 59dbaf344b8505 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 188/
https:/
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: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 188/rebuild
https:/