Merge ~chad.smith/cloud-init:unify-datasource-get-data into cloud-init:master

Proposed by Chad Smith
Status: Merged
Approved by: Chad Smith
Approved revision: 88aefe1fc382327420b91b71246226e7fc378006
Merged at revision: 0cf6db3617e0cebeb89c4809396f84360827e96c
Proposed branch: ~chad.smith/cloud-init:unify-datasource-get-data
Merge into: cloud-init:master
Prerequisite: ~chad.smith/cloud-init:feature/move-base-testcase
Diff against target: 1726 lines (+517/-153)
47 files modified
cloudinit/analyze/__main__.py (+3/-1)
cloudinit/analyze/dump.py (+1/-7)
cloudinit/net/tests/test_dhcp.py (+0/-4)
cloudinit/sources/DataSourceAliYun.py (+1/-0)
cloudinit/sources/DataSourceAltCloud.py (+4/-1)
cloudinit/sources/DataSourceAzure.py (+3/-1)
cloudinit/sources/DataSourceBigstep.py (+4/-1)
cloudinit/sources/DataSourceCloudSigma.py (+4/-1)
cloudinit/sources/DataSourceCloudStack.py (+4/-1)
cloudinit/sources/DataSourceConfigDrive.py (+4/-1)
cloudinit/sources/DataSourceDigitalOcean.py (+4/-1)
cloudinit/sources/DataSourceEc2.py (+9/-3)
cloudinit/sources/DataSourceGCE.py (+4/-1)
cloudinit/sources/DataSourceMAAS.py (+4/-1)
cloudinit/sources/DataSourceNoCloud.py (+4/-1)
cloudinit/sources/DataSourceNone.py (+4/-1)
cloudinit/sources/DataSourceOVF.py (+4/-1)
cloudinit/sources/DataSourceOpenNebula.py (+4/-1)
cloudinit/sources/DataSourceOpenStack.py (+4/-1)
cloudinit/sources/DataSourceScaleway.py (+3/-1)
cloudinit/sources/DataSourceSmartOS.py (+4/-1)
cloudinit/sources/__init__.py (+116/-13)
cloudinit/sources/tests/__init__.py (+0/-0)
cloudinit/sources/tests/test_init.py (+202/-0)
cloudinit/tests/helpers.py (+0/-10)
cloudinit/util.py (+24/-9)
tests/unittests/test_datasource/test_aliyun.py (+1/-1)
tests/unittests/test_datasource/test_altcloud.py (+13/-9)
tests/unittests/test_datasource/test_azure.py (+15/-13)
tests/unittests/test_datasource/test_azure_helper.py (+0/-4)
tests/unittests/test_datasource/test_cloudsigma.py (+9/-4)
tests/unittests/test_datasource/test_cloudstack.py (+13/-10)
tests/unittests/test_datasource/test_configdrive.py (+2/-1)
tests/unittests/test_datasource/test_digitalocean.py (+7/-4)
tests/unittests/test_datasource/test_ec2.py (+2/-1)
tests/unittests/test_datasource/test_gce.py (+2/-1)
tests/unittests/test_datasource/test_nocloud.py (+6/-8)
tests/unittests/test_datasource/test_opennebula.py (+5/-7)
tests/unittests/test_datasource/test_openstack.py (+8/-4)
tests/unittests/test_datasource/test_scaleway.py (+9/-4)
tests/unittests/test_datasource/test_smartos.py (+2/-1)
tests/unittests/test_ds_identify.py (+2/-2)
tests/unittests/test_handler/test_handler_chef.py (+0/-4)
tests/unittests/test_handler/test_handler_lxd.py (+0/-5)
tests/unittests/test_runs/test_merge_run.py (+1/-0)
tests/unittests/test_runs/test_simple_run.py (+2/-1)
tests/unittests/test_vmware_config_file.py (+0/-6)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+330115@code.launchpad.net

Description of the change

Datasources: Formalize DataSource get_data and related properties.

Each DataSource subclass must define its own get_data method. This branch
formalizes our DataSource class to require that subclasses define an
explicit dsname for sourcing cloud-config datasource configuration.
Subclasses must also override the _get_data method or a
NotImplementedError is raised.

The branch also writes /run/cloud-init/instance-data.json. This file
contains all meta-data, user-data and vendor-data and a standardized set
of metadata keys in a json blob which other utilities with root-access
could make use of. Because some meta-data or user-data is potentially
sensitive the file is only readable by root.

Generally most metadata content types should be json serializable. If
specific keys or values are not serializable, those specific values will
be base64encoded and the key path will be listed under the top-level key
'base64-encoded-keys' in instance-data.json. If json writing fails due to
other TypeErrors or UnicodeDecodeErrors, a warning log will be emitted to
/var/log/cloud-init.log and no instance-data.json will be created.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

In commit message:

s/it's/its

I'd replace 'caches' metadata, to dumps; cache implies (to me) reuse but cloud-init doesn't use the json file at all (nor would it since it's already part of the cloud object).

Drop the 'Subsequent' sentence; while you may follow up with that it's not needed as part of the commit message.

Finally, question on the aborting if we have unserializable data; is it possible to skip/replace the binary data so we don't have an all-or-nothing situation?

Revision history for this message
Ryan Harper (raharper) :
2da6f0d... by Chad Smith

move json_loads up out of helpers, sources.__init__ and analyze and into cloudinit.util

c60d25d... by Chad Smith

use util.load_json instead of json.loads in unit tests

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

Thanks for the review Ryan, pushed changes per your review comments. We should be able to handle TypeError issues during json errors and attempt encoding or exclusion of those items instead of bailing on everything.

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

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

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

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

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

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

review: Needs Fixing (continuous-integration)
145541c... by Chad Smith

flakes and allow util.json_loads to redact unseralizable content

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

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

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

userdata_raw for an instance should *always* be bytes, which is *always* unserializble in json:

>>> json.dumps({'key': b'bytes value'})
TypeError: Object of type 'bytes' is not JSON serializable

heres a test that shows more actual content of user-data:
 http://paste.ubuntu.com/25594310/

So that means that unless something is wrong in the Datasource, the 'user-data' (and maybe vendor-data) blobs are going to be redacted with the Warning.

Is meta-data one of our "normalized" values ?

I kind of expected that our exported data looks like:
 {'instance-id': <string>,
  'hostname': <hostname>,
  'user-data'
  'vendor-data':

  'base64-keys': ['user-data', 'vendor-data', 'ec2/blobs/blob1', 'ec2/more-blobs/0']
  '_datasource': {
    'ec2-specific-key1': <string>,
    'blobs': { 'blob1': <bytes> },
    'more-blobs': [b'blobby'],
  }

So we'd have generally available keys in the top (many of them taken out of 'meta-data') with well known names, and then '_datasource' or other name with non-guaranteed stuff under that key.

Maybe i'm thinknig to far ahead, but I'm afraid of putting something in /run/cloud-init/INSTANCE_JSON_FILE that then someone reads and we're stuck with the format of.

the 'base64-keys' thought above is that those would be paths in the data to keys whose value is binary data. We should also make a distinction between a value whos contents raise error on .decode('utf-8') and a value that should generally be regarded as binary. Ie, we shouldnt just "try and see" if we can decode the values because then that leads to oddness for the user where sometimes a given key path might be in base64-keys and other times not.

I don't like that the value of a key can be a string 'Warning:' ... that seems
hard for someone to identify the difference between a possible but strange
value and "didnt work". We should never come to this case I do not think.
And if we do, raising exception is prbably ok.

25481bf... by Chad Smith

attempt to base64encode json unserializable content

14b44a8... by Chad Smith

Datasource base class updates

 1. require dname defined in subclassess to avoid magic prefix/suffix drops when loading datasource config.
 2. Add cached cloud_name property which can be overridden with meta-data['cloud-name']
 3. Add _get_standardized_metadata to emit standard/expected metadata keys for all datasources
 4. Move unstandardized meta-data, instance-data and vendor-data under _datasource key

3020dfa... by Chad Smith

add unit tests for instance-data.json

24ffc07... by Chad Smith

add explicit dsname definitions for all datasource subclasses. Alow Ec2 to identify it's cloud_name via _get_cloud_name() which calls DataSourceEc2.cloud_platform

eb66299... by Chad Smith

flakes

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

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

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

review: Needs Fixing (continuous-integration)
71fe9b0... by Chad Smith

use uncommon ci-b64: prefix for to mark cloud-init's base64 encoding prefix when json serializing unserializable content. Add unit test for py2 which serializes bytes values

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

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

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

review: Needs Fixing (continuous-integration)
477ddd6... by Chad Smith

handle non-utf8 in python2

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

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

review: Approve (continuous-integration)
6274b97... by Chad Smith

keep all standardized fields in instance-data.json under standard-v1 key

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

@smoser: addressed discussions we had at the Ubuntu Rally about standardizing metadata keys in instance-data.json and versioning it. The branch is getting big (and therefore risky) so I didn't address some cloud-name renames and surfacing 'platform' in this branch as it would involve a bit of renaming in DataSourceEc2.Platforms too and I want to tackle that a separate branch to get fresh eyes on it.

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

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

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

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

review: Approve (continuous-integration)
d4ae4e6... by Chad Smith

shuffle instance-data.json field names to make it easier to use in jinja templates

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

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

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

Latest example of instance-data.json on EC2 with the following user-data:

$ echo 'I2Nsb3VkLWNvbmZpZwp0aW1lem9uZTogUGFjaWZpYy9Ib25vbHVsdQo=' | base64 -d
#cloud-config
timezone: Pacific/Honolulu

http://paste.ubuntu.com/26007492/

23b31d5... by Chad Smith

datasource meta-data will all contain hyphens anyway. Let's just leave the keys all hyphenated, when handling jinja we'll convert and instance-data.json hyphens to underscores for templating

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

let's just use hyphens throughout http://paste.ubuntu.com/26007592/

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

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

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

you have some merge conflicts in the diff.
i'll review more later.
this does look good though.

Revision history for this message
Scott Moser (smoser) :
Revision history for this message
Scott Moser (smoser) :
7bbc057... by Chad Smith

drop indented scope and exit early

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

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

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

review: Needs Fixing (continuous-integration)
05c7419... by Chad Smith

use atomic_helper write_json in Datasource. fix unit tests

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

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

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

One more pass of instance-data from lxc and ec2 validated no Tracebacks as well.

http://pastebin.ubuntu.com/26075320/

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

Since we are playing around, here's Azure instance-data. not much there http://pastebin.ubuntu.com/26075842/

d936c64... by Chad Smith

drop unpopulated ipv4 and ipv6 addresses from standardized metadata

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

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

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

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

And openstack instance data for the win
http://pastebin.ubuntu.com/26084548/

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

you do still have merge conflicts.

88aefe1... by Chad Smith

flakes

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

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

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

Two things
a.) i'd like some doc/ I think this can be followed up with in another branch
b.) your commit message has a long line. i read and quickly fixed a small thing
or two.

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

GCE instance-data for reference
http://paste.ubuntu.com/26165085/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/analyze/__main__.py b/cloudinit/analyze/__main__.py
index 69b9e43..3ba5903 100644
--- a/cloudinit/analyze/__main__.py
+++ b/cloudinit/analyze/__main__.py
@@ -6,6 +6,8 @@ import argparse
6import re6import re
7import sys7import sys
88
9from cloudinit.util import json_dumps
10
9from . import dump11from . import dump
10from . import show12from . import show
1113
@@ -112,7 +114,7 @@ def analyze_show(name, args):
112def analyze_dump(name, args):114def analyze_dump(name, args):
113 """Dump cloud-init events in json format"""115 """Dump cloud-init events in json format"""
114 (infh, outfh) = configure_io(args)116 (infh, outfh) = configure_io(args)
115 outfh.write(dump.json_dumps(_get_events(infh)) + '\n')117 outfh.write(json_dumps(_get_events(infh)) + '\n')
116118
117119
118def _get_events(infile):120def _get_events(infile):
diff --git a/cloudinit/analyze/dump.py b/cloudinit/analyze/dump.py
index ca4da49..b071aa1 100644
--- a/cloudinit/analyze/dump.py
+++ b/cloudinit/analyze/dump.py
@@ -2,7 +2,6 @@
22
3import calendar3import calendar
4from datetime import datetime4from datetime import datetime
5import json
6import sys5import sys
76
8from cloudinit import util7from cloudinit import util
@@ -132,11 +131,6 @@ def parse_ci_logline(line):
132 return event131 return event
133132
134133
135def json_dumps(data):
136 return json.dumps(data, indent=1, sort_keys=True,
137 separators=(',', ': '))
138
139
140def dump_events(cisource=None, rawdata=None):134def dump_events(cisource=None, rawdata=None):
141 events = []135 events = []
142 event = None136 event = None
@@ -169,7 +163,7 @@ def main():
169 else:163 else:
170 cisource = sys.stdin164 cisource = sys.stdin
171165
172 return json_dumps(dump_events(cisource))166 return util.json_dumps(dump_events(cisource))
173167
174168
175if __name__ == "__main__":169if __name__ == "__main__":
diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py
index fad4e49..db25b6f 100644
--- a/cloudinit/net/tests/test_dhcp.py
+++ b/cloudinit/net/tests/test_dhcp.py
@@ -8,12 +8,8 @@ from cloudinit.net.dhcp import (
8 InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery,8 InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery,
9 parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases)9 parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases)
10from cloudinit.util import ensure_file, write_file10from cloudinit.util import ensure_file, write_file
11<<<<<<< cloudinit/net/tests/test_dhcp.py
12from cloudinit.tests.helpers import (11from cloudinit.tests.helpers import (
13 CiTestCase, mock, populate_dir, wrap_and_call)12 CiTestCase, mock, populate_dir, wrap_and_call)
14=======
15from cloudinit.tests.helpers import CiTestCase
16>>>>>>> cloudinit/net/tests/test_dhcp.py
1713
1814
19class TestParseDHCPLeasesFile(CiTestCase):15class TestParseDHCPLeasesFile(CiTestCase):
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
index 43a7e42..7ac8288 100644
--- a/cloudinit/sources/DataSourceAliYun.py
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -11,6 +11,7 @@ ALIYUN_PRODUCT = "Alibaba Cloud ECS"
1111
12class DataSourceAliYun(EC2.DataSourceEc2):12class DataSourceAliYun(EC2.DataSourceEc2):
1313
14 dsname = 'AliYun'
14 metadata_urls = ['http://100.100.100.200']15 metadata_urls = ['http://100.100.100.200']
1516
16 # The minimum supported metadata_version from the ec2 metadata apis17 # The minimum supported metadata_version from the ec2 metadata apis
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index c78ad9e..be2d6cf 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -74,6 +74,9 @@ def read_user_data_callback(mount_dir):
7474
7575
76class DataSourceAltCloud(sources.DataSource):76class DataSourceAltCloud(sources.DataSource):
77
78 dsname = 'AltCloud'
79
77 def __init__(self, sys_cfg, distro, paths):80 def __init__(self, sys_cfg, distro, paths):
78 sources.DataSource.__init__(self, sys_cfg, distro, paths)81 sources.DataSource.__init__(self, sys_cfg, distro, paths)
79 self.seed = None82 self.seed = None
@@ -112,7 +115,7 @@ class DataSourceAltCloud(sources.DataSource):
112115
113 return 'UNKNOWN'116 return 'UNKNOWN'
114117
115 def get_data(self):118 def _get_data(self):
116 '''119 '''
117 Description:120 Description:
118 User Data is passed to the launching instance which121 User Data is passed to the launching instance which
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 14367e9..6978d4e 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -246,6 +246,8 @@ def temporary_hostname(temp_hostname, cfg, hostname_command='hostname'):
246246
247247
248class DataSourceAzure(sources.DataSource):248class DataSourceAzure(sources.DataSource):
249
250 dsname = 'Azure'
249 _negotiated = False251 _negotiated = False
250252
251 def __init__(self, sys_cfg, distro, paths):253 def __init__(self, sys_cfg, distro, paths):
@@ -330,7 +332,7 @@ class DataSourceAzure(sources.DataSource):
330 metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files)332 metadata['public-keys'] = key_value or pubkeys_from_crt_files(fp_files)
331 return metadata333 return metadata
332334
333 def get_data(self):335 def _get_data(self):
334 # azure removes/ejects the cdrom containing the ovf-env.xml336 # azure removes/ejects the cdrom containing the ovf-env.xml
335 # file on reboot. So, in order to successfully reboot we337 # file on reboot. So, in order to successfully reboot we
336 # need to look in the datadir and consider that valid338 # need to look in the datadir and consider that valid
diff --git a/cloudinit/sources/DataSourceBigstep.py b/cloudinit/sources/DataSourceBigstep.py
index d7fcd45..699a85b 100644
--- a/cloudinit/sources/DataSourceBigstep.py
+++ b/cloudinit/sources/DataSourceBigstep.py
@@ -16,13 +16,16 @@ LOG = logging.getLogger(__name__)
1616
1717
18class DataSourceBigstep(sources.DataSource):18class DataSourceBigstep(sources.DataSource):
19
20 dsname = 'Bigstep'
21
19 def __init__(self, sys_cfg, distro, paths):22 def __init__(self, sys_cfg, distro, paths):
20 sources.DataSource.__init__(self, sys_cfg, distro, paths)23 sources.DataSource.__init__(self, sys_cfg, distro, paths)
21 self.metadata = {}24 self.metadata = {}
22 self.vendordata_raw = ""25 self.vendordata_raw = ""
23 self.userdata_raw = ""26 self.userdata_raw = ""
2427
25 def get_data(self, apply_filter=False):28 def _get_data(self, apply_filter=False):
26 url = get_url_from_file()29 url = get_url_from_file()
27 if url is None:30 if url is None:
28 return False31 return False
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index 19df16b..4eaad47 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -23,6 +23,9 @@ class DataSourceCloudSigma(sources.DataSource):
23 For more information about CloudSigma's Server Context:23 For more information about CloudSigma's Server Context:
24 http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html24 http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html
25 """25 """
26
27 dsname = 'CloudSigma'
28
26 def __init__(self, sys_cfg, distro, paths):29 def __init__(self, sys_cfg, distro, paths):
27 self.cepko = Cepko()30 self.cepko = Cepko()
28 self.ssh_public_key = ''31 self.ssh_public_key = ''
@@ -46,7 +49,7 @@ class DataSourceCloudSigma(sources.DataSource):
46 LOG.warning("failed to query dmi data for system product name")49 LOG.warning("failed to query dmi data for system product name")
47 return False50 return False
4851
49 def get_data(self):52 def _get_data(self):
50 """53 """
51 Metadata is the whole server context and /meta/cloud-config is used54 Metadata is the whole server context and /meta/cloud-config is used
52 as userdata.55 as userdata.
diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
index 9dc473f..0df545f 100644
--- a/cloudinit/sources/DataSourceCloudStack.py
+++ b/cloudinit/sources/DataSourceCloudStack.py
@@ -65,6 +65,9 @@ class CloudStackPasswordServerClient(object):
6565
6666
67class DataSourceCloudStack(sources.DataSource):67class DataSourceCloudStack(sources.DataSource):
68
69 dsname = 'CloudStack'
70
68 def __init__(self, sys_cfg, distro, paths):71 def __init__(self, sys_cfg, distro, paths):
69 sources.DataSource.__init__(self, sys_cfg, distro, paths)72 sources.DataSource.__init__(self, sys_cfg, distro, paths)
70 self.seed_dir = os.path.join(paths.seed_dir, 'cs')73 self.seed_dir = os.path.join(paths.seed_dir, 'cs')
@@ -117,7 +120,7 @@ class DataSourceCloudStack(sources.DataSource):
117 def get_config_obj(self):120 def get_config_obj(self):
118 return self.cfg121 return self.cfg
119122
120 def get_data(self):123 def _get_data(self):
121 seed_ret = {}124 seed_ret = {}
122 if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):125 if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
123 self.userdata_raw = seed_ret['user-data']126 self.userdata_raw = seed_ret['user-data']
diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py
index ef374f3..870b368 100644
--- a/cloudinit/sources/DataSourceConfigDrive.py
+++ b/cloudinit/sources/DataSourceConfigDrive.py
@@ -32,6 +32,9 @@ OPTICAL_DEVICES = tuple(('/dev/%s%s' % (z, i) for z in POSSIBLE_MOUNTS
3232
3333
34class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):34class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
35
36 dsname = 'ConfigDrive'
37
35 def __init__(self, sys_cfg, distro, paths):38 def __init__(self, sys_cfg, distro, paths):
36 super(DataSourceConfigDrive, self).__init__(sys_cfg, distro, paths)39 super(DataSourceConfigDrive, self).__init__(sys_cfg, distro, paths)
37 self.source = None40 self.source = None
@@ -50,7 +53,7 @@ class DataSourceConfigDrive(openstack.SourceMixin, sources.DataSource):
50 mstr += "[source=%s]" % (self.source)53 mstr += "[source=%s]" % (self.source)
51 return mstr54 return mstr
5255
53 def get_data(self):56 def _get_data(self):
54 found = None57 found = None
55 md = {}58 md = {}
56 results = {}59 results = {}
diff --git a/cloudinit/sources/DataSourceDigitalOcean.py b/cloudinit/sources/DataSourceDigitalOcean.py
index 5e7e66b..e0ef665 100644
--- a/cloudinit/sources/DataSourceDigitalOcean.py
+++ b/cloudinit/sources/DataSourceDigitalOcean.py
@@ -27,6 +27,9 @@ MD_USE_IPV4LL = True
2727
2828
29class DataSourceDigitalOcean(sources.DataSource):29class DataSourceDigitalOcean(sources.DataSource):
30
31 dsname = 'DigitalOcean'
32
30 def __init__(self, sys_cfg, distro, paths):33 def __init__(self, sys_cfg, distro, paths):
31 sources.DataSource.__init__(self, sys_cfg, distro, paths)34 sources.DataSource.__init__(self, sys_cfg, distro, paths)
32 self.distro = distro35 self.distro = distro
@@ -44,7 +47,7 @@ class DataSourceDigitalOcean(sources.DataSource):
44 def _get_sysinfo(self):47 def _get_sysinfo(self):
45 return do_helper.read_sysinfo()48 return do_helper.read_sysinfo()
4649
47 def get_data(self):50 def _get_data(self):
48 (is_do, droplet_id) = self._get_sysinfo()51 (is_do, droplet_id) = self._get_sysinfo()
4952
50 # only proceed if we know we are on DigitalOcean53 # only proceed if we know we are on DigitalOcean
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 7bbbfb6..e5c8833 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -31,6 +31,7 @@ _unset = "_unset"
3131
3232
33class Platforms(object):33class Platforms(object):
34 # TODO Rename and move to cloudinit.cloud.CloudNames
34 ALIYUN = "AliYun"35 ALIYUN = "AliYun"
35 AWS = "AWS"36 AWS = "AWS"
36 BRIGHTBOX = "Brightbox"37 BRIGHTBOX = "Brightbox"
@@ -45,6 +46,7 @@ class Platforms(object):
4546
46class DataSourceEc2(sources.DataSource):47class DataSourceEc2(sources.DataSource):
4748
49 dsname = 'Ec2'
48 # Default metadata urls that will be used if none are provided50 # Default metadata urls that will be used if none are provided
49 # They will be checked for 'resolveability' and some of the51 # They will be checked for 'resolveability' and some of the
50 # following may be discarded if they do not resolve52 # following may be discarded if they do not resolve
@@ -68,11 +70,15 @@ class DataSourceEc2(sources.DataSource):
68 _fallback_interface = None70 _fallback_interface = None
6971
70 def __init__(self, sys_cfg, distro, paths):72 def __init__(self, sys_cfg, distro, paths):
71 sources.DataSource.__init__(self, sys_cfg, distro, paths)73 super(DataSourceEc2, self).__init__(sys_cfg, distro, paths)
72 self.metadata_address = None74 self.metadata_address = None
73 self.seed_dir = os.path.join(paths.seed_dir, "ec2")75 self.seed_dir = os.path.join(paths.seed_dir, "ec2")
7476
75 def get_data(self):77 def _get_cloud_name(self):
78 """Return the cloud name as identified during _get_data."""
79 return self.cloud_platform
80
81 def _get_data(self):
76 seed_ret = {}82 seed_ret = {}
77 if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):83 if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")):
78 self.userdata_raw = seed_ret['user-data']84 self.userdata_raw = seed_ret['user-data']
@@ -274,7 +280,7 @@ class DataSourceEc2(sources.DataSource):
274 return None280 return None
275281
276 @property282 @property
277 def cloud_platform(self):283 def cloud_platform(self): # TODO rename cloud_name
278 if self._cloud_platform is None:284 if self._cloud_platform is None:
279 self._cloud_platform = identify_platform()285 self._cloud_platform = identify_platform()
280 return self._cloud_platform286 return self._cloud_platform
diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py
index ccae420..ad6dae3 100644
--- a/cloudinit/sources/DataSourceGCE.py
+++ b/cloudinit/sources/DataSourceGCE.py
@@ -42,6 +42,9 @@ class GoogleMetadataFetcher(object):
4242
4343
44class DataSourceGCE(sources.DataSource):44class DataSourceGCE(sources.DataSource):
45
46 dsname = 'GCE'
47
45 def __init__(self, sys_cfg, distro, paths):48 def __init__(self, sys_cfg, distro, paths):
46 sources.DataSource.__init__(self, sys_cfg, distro, paths)49 sources.DataSource.__init__(self, sys_cfg, distro, paths)
47 self.metadata = dict()50 self.metadata = dict()
@@ -50,7 +53,7 @@ class DataSourceGCE(sources.DataSource):
50 BUILTIN_DS_CONFIG])53 BUILTIN_DS_CONFIG])
51 self.metadata_address = self.ds_cfg['metadata_url']54 self.metadata_address = self.ds_cfg['metadata_url']
5255
53 def get_data(self):56 def _get_data(self):
54 ret = util.log_time(57 ret = util.log_time(
55 LOG.debug, 'Crawl of GCE metadata service',58 LOG.debug, 'Crawl of GCE metadata service',
56 read_md, kwargs={'address': self.metadata_address})59 read_md, kwargs={'address': self.metadata_address})
diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py
index 77df5a5..496bd06 100644
--- a/cloudinit/sources/DataSourceMAAS.py
+++ b/cloudinit/sources/DataSourceMAAS.py
@@ -39,6 +39,9 @@ class DataSourceMAAS(sources.DataSource):
39 hostname39 hostname
40 vendor-data40 vendor-data
41 """41 """
42
43 dsname = "MAAS"
44
42 def __init__(self, sys_cfg, distro, paths):45 def __init__(self, sys_cfg, distro, paths):
43 sources.DataSource.__init__(self, sys_cfg, distro, paths)46 sources.DataSource.__init__(self, sys_cfg, distro, paths)
44 self.base_url = None47 self.base_url = None
@@ -62,7 +65,7 @@ class DataSourceMAAS(sources.DataSource):
62 root = sources.DataSource.__str__(self)65 root = sources.DataSource.__str__(self)
63 return "%s [%s]" % (root, self.base_url)66 return "%s [%s]" % (root, self.base_url)
6467
65 def get_data(self):68 def _get_data(self):
66 mcfg = self.ds_cfg69 mcfg = self.ds_cfg
6770
68 try:71 try:
diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
index e641244..5d3a8dd 100644
--- a/cloudinit/sources/DataSourceNoCloud.py
+++ b/cloudinit/sources/DataSourceNoCloud.py
@@ -20,6 +20,9 @@ LOG = logging.getLogger(__name__)
2020
2121
22class DataSourceNoCloud(sources.DataSource):22class DataSourceNoCloud(sources.DataSource):
23
24 dsname = "NoCloud"
25
23 def __init__(self, sys_cfg, distro, paths):26 def __init__(self, sys_cfg, distro, paths):
24 sources.DataSource.__init__(self, sys_cfg, distro, paths)27 sources.DataSource.__init__(self, sys_cfg, distro, paths)
25 self.seed = None28 self.seed = None
@@ -32,7 +35,7 @@ class DataSourceNoCloud(sources.DataSource):
32 root = sources.DataSource.__str__(self)35 root = sources.DataSource.__str__(self)
33 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)36 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)
3437
35 def get_data(self):38 def _get_data(self):
36 defaults = {39 defaults = {
37 "instance-id": "nocloud",40 "instance-id": "nocloud",
38 "dsmode": self.dsmode,41 "dsmode": self.dsmode,
diff --git a/cloudinit/sources/DataSourceNone.py b/cloudinit/sources/DataSourceNone.py
index 906bb27..e63a7e3 100644
--- a/cloudinit/sources/DataSourceNone.py
+++ b/cloudinit/sources/DataSourceNone.py
@@ -11,12 +11,15 @@ LOG = logging.getLogger(__name__)
1111
1212
13class DataSourceNone(sources.DataSource):13class DataSourceNone(sources.DataSource):
14
15 dsname = "None"
16
14 def __init__(self, sys_cfg, distro, paths, ud_proc=None):17 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
15 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)18 sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc)
16 self.metadata = {}19 self.metadata = {}
17 self.userdata_raw = ''20 self.userdata_raw = ''
1821
19 def get_data(self):22 def _get_data(self):
20 # If the datasource config has any provided 'fallback'23 # If the datasource config has any provided 'fallback'
21 # userdata or metadata, use it...24 # userdata or metadata, use it...
22 if 'userdata_raw' in self.ds_cfg:25 if 'userdata_raw' in self.ds_cfg:
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index ccebf11..6ac621f 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -43,6 +43,9 @@ LOG = logging.getLogger(__name__)
4343
4444
45class DataSourceOVF(sources.DataSource):45class DataSourceOVF(sources.DataSource):
46
47 dsname = "OVF"
48
46 def __init__(self, sys_cfg, distro, paths):49 def __init__(self, sys_cfg, distro, paths):
47 sources.DataSource.__init__(self, sys_cfg, distro, paths)50 sources.DataSource.__init__(self, sys_cfg, distro, paths)
48 self.seed = None51 self.seed = None
@@ -60,7 +63,7 @@ class DataSourceOVF(sources.DataSource):
60 root = sources.DataSource.__str__(self)63 root = sources.DataSource.__str__(self)
61 return "%s [seed=%s]" % (root, self.seed)64 return "%s [seed=%s]" % (root, self.seed)
6265
63 def get_data(self):66 def _get_data(self):
64 found = []67 found = []
65 md = {}68 md = {}
66 ud = ""69 ud = ""
diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py
index 5fdac19..5da1184 100644
--- a/cloudinit/sources/DataSourceOpenNebula.py
+++ b/cloudinit/sources/DataSourceOpenNebula.py
@@ -31,6 +31,9 @@ CONTEXT_DISK_FILES = ["context.sh"]
3131
3232
33class DataSourceOpenNebula(sources.DataSource):33class DataSourceOpenNebula(sources.DataSource):
34
35 dsname = "OpenNebula"
36
34 def __init__(self, sys_cfg, distro, paths):37 def __init__(self, sys_cfg, distro, paths):
35 sources.DataSource.__init__(self, sys_cfg, distro, paths)38 sources.DataSource.__init__(self, sys_cfg, distro, paths)
36 self.seed = None39 self.seed = None
@@ -40,7 +43,7 @@ class DataSourceOpenNebula(sources.DataSource):
40 root = sources.DataSource.__str__(self)43 root = sources.DataSource.__str__(self)
41 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)44 return "%s [seed=%s][dsmode=%s]" % (root, self.seed, self.dsmode)
4245
43 def get_data(self):46 def _get_data(self):
44 defaults = {"instance-id": DEFAULT_IID}47 defaults = {"instance-id": DEFAULT_IID}
45 results = None48 results = None
46 seed = None49 seed = None
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index b64a7f2..e55a763 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -24,6 +24,9 @@ DEFAULT_METADATA = {
2424
2525
26class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):26class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
27
28 dsname = "OpenStack"
29
27 def __init__(self, sys_cfg, distro, paths):30 def __init__(self, sys_cfg, distro, paths):
28 super(DataSourceOpenStack, self).__init__(sys_cfg, distro, paths)31 super(DataSourceOpenStack, self).__init__(sys_cfg, distro, paths)
29 self.metadata_address = None32 self.metadata_address = None
@@ -96,7 +99,7 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource):
96 self.metadata_address = url2base.get(avail_url)99 self.metadata_address = url2base.get(avail_url)
97 return bool(avail_url)100 return bool(avail_url)
98101
99 def get_data(self):102 def _get_data(self):
100 try:103 try:
101 if not self.wait_for_metadata_service():104 if not self.wait_for_metadata_service():
102 return False105 return False
diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py
index 3a8a8e8..b0b19c9 100644
--- a/cloudinit/sources/DataSourceScaleway.py
+++ b/cloudinit/sources/DataSourceScaleway.py
@@ -169,6 +169,8 @@ def query_data_api(api_type, api_address, retries, timeout):
169169
170class DataSourceScaleway(sources.DataSource):170class DataSourceScaleway(sources.DataSource):
171171
172 dsname = "Scaleway"
173
172 def __init__(self, sys_cfg, distro, paths):174 def __init__(self, sys_cfg, distro, paths):
173 super(DataSourceScaleway, self).__init__(sys_cfg, distro, paths)175 super(DataSourceScaleway, self).__init__(sys_cfg, distro, paths)
174176
@@ -184,7 +186,7 @@ class DataSourceScaleway(sources.DataSource):
184 self.retries = int(self.ds_cfg.get('retries', DEF_MD_RETRIES))186 self.retries = int(self.ds_cfg.get('retries', DEF_MD_RETRIES))
185 self.timeout = int(self.ds_cfg.get('timeout', DEF_MD_TIMEOUT))187 self.timeout = int(self.ds_cfg.get('timeout', DEF_MD_TIMEOUT))
186188
187 def get_data(self):189 def _get_data(self):
188 if not on_scaleway():190 if not on_scaleway():
189 return False191 return False
190192
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index 6c6902f..86bfa5d 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -159,6 +159,9 @@ LEGACY_USER_D = "/var/db"
159159
160160
161class DataSourceSmartOS(sources.DataSource):161class DataSourceSmartOS(sources.DataSource):
162
163 dsname = "Joyent"
164
162 _unset = "_unset"165 _unset = "_unset"
163 smartos_type = _unset166 smartos_type = _unset
164 md_client = _unset167 md_client = _unset
@@ -211,7 +214,7 @@ class DataSourceSmartOS(sources.DataSource):
211 os.rename('/'.join([svc_path, 'provisioning']),214 os.rename('/'.join([svc_path, 'provisioning']),
212 '/'.join([svc_path, 'provision_success']))215 '/'.join([svc_path, 'provision_success']))
213216
214 def get_data(self):217 def _get_data(self):
215 self._init()218 self._init()
216219
217 md = {}220 md = {}
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 9a43fbe..4b819ce 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -10,9 +10,11 @@
1010
11import abc11import abc
12import copy12import copy
13import json
13import os14import os
14import six15import six
1516
17from cloudinit.atomic_helper import write_json
16from cloudinit import importer18from cloudinit import importer
17from cloudinit import log as logging19from cloudinit import log as logging
18from cloudinit import type_utils20from cloudinit import type_utils
@@ -33,6 +35,12 @@ DEP_FILESYSTEM = "FILESYSTEM"
33DEP_NETWORK = "NETWORK"35DEP_NETWORK = "NETWORK"
34DS_PREFIX = 'DataSource'36DS_PREFIX = 'DataSource'
3537
38# File in which instance meta-data, user-data and vendor-data is written
39INSTANCE_JSON_FILE = 'instance-data.json'
40
41# Key which can be provide a cloud's official product name to cloud-init
42METADATA_CLOUD_NAME_KEY = 'cloud-name'
43
36LOG = logging.getLogger(__name__)44LOG = logging.getLogger(__name__)
3745
3846
@@ -40,12 +48,39 @@ class DataSourceNotFoundException(Exception):
40 pass48 pass
4149
4250
51def process_base64_metadata(metadata, key_path=''):
52 """Strip ci-b64 prefix and return metadata with base64-encoded-keys set."""
53 md_copy = copy.deepcopy(metadata)
54 md_copy['base64-encoded-keys'] = []
55 for key, val in metadata.items():
56 if key_path:
57 sub_key_path = key_path + '/' + key
58 else:
59 sub_key_path = key
60 if isinstance(val, str) and val.startswith('ci-b64:'):
61 md_copy['base64-encoded-keys'].append(sub_key_path)
62 md_copy[key] = val.replace('ci-b64:', '')
63 if isinstance(val, dict):
64 return_val = process_base64_metadata(val, sub_key_path)
65 md_copy['base64-encoded-keys'].extend(
66 return_val.pop('base64-encoded-keys'))
67 md_copy[key] = return_val
68 return md_copy
69
70
43@six.add_metaclass(abc.ABCMeta)71@six.add_metaclass(abc.ABCMeta)
44class DataSource(object):72class DataSource(object):
4573
46 dsmode = DSMODE_NETWORK74 dsmode = DSMODE_NETWORK
47 default_locale = 'en_US.UTF-8'75 default_locale = 'en_US.UTF-8'
4876
77 # Datasource name needs to be set by subclasses to determine which
78 # cloud-config datasource key is loaded
79 dsname = '_undef'
80
81 # Cached cloud_name as determined by _get_cloud_name
82 _cloud_name = None
83
49 def __init__(self, sys_cfg, distro, paths, ud_proc=None):84 def __init__(self, sys_cfg, distro, paths, ud_proc=None):
50 self.sys_cfg = sys_cfg85 self.sys_cfg = sys_cfg
51 self.distro = distro86 self.distro = distro
@@ -56,17 +91,8 @@ class DataSource(object):
56 self.vendordata = None91 self.vendordata = None
57 self.vendordata_raw = None92 self.vendordata_raw = None
5893
59 # find the datasource config name.94 self.ds_cfg = util.get_cfg_by_path(
60 # remove 'DataSource' from classname on front, and remove 'Net' on end.95 self.sys_cfg, ("datasource", self.dsname), {})
61 # Both Foo and FooNet sources expect config in cfg['sources']['Foo']
62 name = type_utils.obj_name(self)
63 if name.startswith(DS_PREFIX):
64 name = name[len(DS_PREFIX):]
65 if name.endswith('Net'):
66 name = name[0:-3]
67
68 self.ds_cfg = util.get_cfg_by_path(self.sys_cfg,
69 ("datasource", name), {})
70 if not self.ds_cfg:96 if not self.ds_cfg:
71 self.ds_cfg = {}97 self.ds_cfg = {}
7298
@@ -78,6 +104,51 @@ class DataSource(object):
78 def __str__(self):104 def __str__(self):
79 return type_utils.obj_name(self)105 return type_utils.obj_name(self)
80106
107 def _get_standardized_metadata(self):
108 """Return a dictionary of standardized metadata keys."""
109 return {'v1': {
110 'local-hostname': self.get_hostname(),
111 'instance-id': self.get_instance_id(),
112 'cloud-name': self.cloud_name,
113 'region': self.region,
114 'availability-zone': self.availability_zone}}
115
116 def get_data(self):
117 """Datasources implement _get_data to setup metadata and userdata_raw.
118
119 Minimally, the datasource should return a boolean True on success.
120 """
121 return_value = self._get_data()
122 json_file = os.path.join(self.paths.run_dir, INSTANCE_JSON_FILE)
123 if not return_value:
124 return return_value
125
126 instance_data = {
127 'ds': {
128 'meta-data': self.metadata,
129 'user-data': self.get_userdata_raw(),
130 'vendor-data': self.get_vendordata_raw()}}
131 instance_data.update(
132 self._get_standardized_metadata())
133 try:
134 # Process content base64encoding unserializable values
135 content = util.json_dumps(instance_data)
136 # Strip base64: prefix and return base64-encoded-keys
137 processed_data = process_base64_metadata(json.loads(content))
138 except TypeError as e:
139 LOG.warning('Error persisting instance-data.json: %s', str(e))
140 return return_value
141 except UnicodeDecodeError as e:
142 LOG.warning('Error persisting instance-data.json: %s', str(e))
143 return return_value
144 write_json(json_file, processed_data, mode=0o600)
145 return return_value
146
147 def _get_data(self):
148 raise NotImplementedError(
149 'Subclasses of DataSource must implement _get_data which'
150 ' sets self.metadata, vendordata_raw and userdata_raw.')
151
81 def get_userdata(self, apply_filter=False):152 def get_userdata(self, apply_filter=False):
82 if self.userdata is None:153 if self.userdata is None:
83 self.userdata = self.ud_proc.process(self.get_userdata_raw())154 self.userdata = self.ud_proc.process(self.get_userdata_raw())
@@ -91,6 +162,34 @@ class DataSource(object):
91 return self.vendordata162 return self.vendordata
92163
93 @property164 @property
165 def cloud_name(self):
166 """Return lowercase cloud name as determined by the datasource.
167
168 Datasource can determine or define its own cloud product name in
169 metadata.
170 """
171 if self._cloud_name:
172 return self._cloud_name
173 if self.metadata and self.metadata.get(METADATA_CLOUD_NAME_KEY):
174 cloud_name = self.metadata.get(METADATA_CLOUD_NAME_KEY)
175 if isinstance(cloud_name, six.string_types):
176 self._cloud_name = cloud_name.lower()
177 LOG.debug(
178 'Ignoring metadata provided key %s: non-string type %s',
179 METADATA_CLOUD_NAME_KEY, type(cloud_name))
180 else:
181 self._cloud_name = self._get_cloud_name().lower()
182 return self._cloud_name
183
184 def _get_cloud_name(self):
185 """Return the datasource name as it frequently matches cloud name.
186
187 Should be overridden in subclasses which can run on multiple
188 cloud names, such as DatasourceEc2.
189 """
190 return self.dsname
191
192 @property
94 def launch_index(self):193 def launch_index(self):
95 if not self.metadata:194 if not self.metadata:
96 return None195 return None
@@ -161,8 +260,11 @@ class DataSource(object):
161260
162 @property261 @property
163 def availability_zone(self):262 def availability_zone(self):
164 return self.metadata.get('availability-zone',263 top_level_az = self.metadata.get(
165 self.metadata.get('availability_zone'))264 'availability-zone', self.metadata.get('availability_zone'))
265 if top_level_az:
266 return top_level_az
267 return self.metadata.get('placement', {}).get('availability-zone')
166268
167 @property269 @property
168 def region(self):270 def region(self):
@@ -417,4 +519,5 @@ def list_from_depends(depends, ds_list):
417 ret_list.append(cls)519 ret_list.append(cls)
418 return ret_list520 return ret_list
419521
522
420# vi: ts=4 expandtab523# vi: ts=4 expandtab
diff --git a/cloudinit/sources/tests/__init__.py b/cloudinit/sources/tests/__init__.py
421new file mode 100644524new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudinit/sources/tests/__init__.py
diff --git a/cloudinit/sources/tests/test_init.py b/cloudinit/sources/tests/test_init.py
422new file mode 100644525new file mode 100644
index 0000000..af15115
--- /dev/null
+++ b/cloudinit/sources/tests/test_init.py
@@ -0,0 +1,202 @@
1# This file is part of cloud-init. See LICENSE file for license information.
2
3import os
4import six
5import stat
6
7from cloudinit.helpers import Paths
8from cloudinit.sources import (
9 INSTANCE_JSON_FILE, DataSource)
10from cloudinit.tests.helpers import CiTestCase, skipIf
11from cloudinit.user_data import UserDataProcessor
12from cloudinit import util
13
14
15class DataSourceTestSubclassNet(DataSource):
16
17 dsname = 'MyTestSubclass'
18
19 def __init__(self, sys_cfg, distro, paths, custom_userdata=None):
20 super(DataSourceTestSubclassNet, self).__init__(
21 sys_cfg, distro, paths)
22 self._custom_userdata = custom_userdata
23
24 def _get_cloud_name(self):
25 return 'SubclassCloudName'
26
27 def _get_data(self):
28 self.metadata = {'availability_zone': 'myaz',
29 'local-hostname': 'test-subclass-hostname',
30 'region': 'myregion'}
31 if self._custom_userdata:
32 self.userdata_raw = self._custom_userdata
33 else:
34 self.userdata_raw = 'userdata_raw'
35 self.vendordata_raw = 'vendordata_raw'
36 return True
37
38
39class InvalidDataSourceTestSubclassNet(DataSource):
40 pass
41
42
43class TestDataSource(CiTestCase):
44
45 with_logs = True
46
47 def setUp(self):
48 super(TestDataSource, self).setUp()
49 self.sys_cfg = {'datasource': {'_undef': {'key1': False}}}
50 self.distro = 'distrotest' # generally should be a Distro object
51 self.paths = Paths({})
52 self.datasource = DataSource(self.sys_cfg, self.distro, self.paths)
53
54 def test_datasource_init(self):
55 """DataSource initializes metadata attributes, ds_cfg and ud_proc."""
56 self.assertEqual(self.paths, self.datasource.paths)
57 self.assertEqual(self.sys_cfg, self.datasource.sys_cfg)
58 self.assertEqual(self.distro, self.datasource.distro)
59 self.assertIsNone(self.datasource.userdata)
60 self.assertEqual({}, self.datasource.metadata)
61 self.assertIsNone(self.datasource.userdata_raw)
62 self.assertIsNone(self.datasource.vendordata)
63 self.assertIsNone(self.datasource.vendordata_raw)
64 self.assertEqual({'key1': False}, self.datasource.ds_cfg)
65 self.assertIsInstance(self.datasource.ud_proc, UserDataProcessor)
66
67 def test_datasource_init_gets_ds_cfg_using_dsname(self):
68 """Init uses DataSource.dsname for sourcing ds_cfg."""
69 sys_cfg = {'datasource': {'MyTestSubclass': {'key2': False}}}
70 distro = 'distrotest' # generally should be a Distro object
71 paths = Paths({})
72 datasource = DataSourceTestSubclassNet(sys_cfg, distro, paths)
73 self.assertEqual({'key2': False}, datasource.ds_cfg)
74
75 def test_str_is_classname(self):
76 """The string representation of the datasource is the classname."""
77 self.assertEqual('DataSource', str(self.datasource))
78 self.assertEqual(
79 'DataSourceTestSubclassNet',
80 str(DataSourceTestSubclassNet('', '', self.paths)))
81
82 def test__get_data_unimplemented(self):
83 """Raise an error when _get_data is not implemented."""
84 with self.assertRaises(NotImplementedError) as context_manager:
85 self.datasource.get_data()
86 self.assertIn(
87 'Subclasses of DataSource must implement _get_data',
88 str(context_manager.exception))
89 datasource2 = InvalidDataSourceTestSubclassNet(
90 self.sys_cfg, self.distro, self.paths)
91 with self.assertRaises(NotImplementedError) as context_manager:
92 datasource2.get_data()
93 self.assertIn(
94 'Subclasses of DataSource must implement _get_data',
95 str(context_manager.exception))
96
97 def test_get_data_calls_subclass__get_data(self):
98 """Datasource.get_data uses the subclass' version of _get_data."""
99 tmp = self.tmp_dir()
100 datasource = DataSourceTestSubclassNet(
101 self.sys_cfg, self.distro, Paths({'run_dir': tmp}))
102 self.assertTrue(datasource.get_data())
103 self.assertEqual(
104 {'availability_zone': 'myaz',
105 'local-hostname': 'test-subclass-hostname',
106 'region': 'myregion'},
107 datasource.metadata)
108 self.assertEqual('userdata_raw', datasource.userdata_raw)
109 self.assertEqual('vendordata_raw', datasource.vendordata_raw)
110
111 def test_get_data_write_json_instance_data(self):
112 """get_data writes INSTANCE_JSON_FILE to run_dir as readonly root."""
113 tmp = self.tmp_dir()
114 datasource = DataSourceTestSubclassNet(
115 self.sys_cfg, self.distro, Paths({'run_dir': tmp}))
116 datasource.get_data()
117 json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp)
118 content = util.load_file(json_file)
119 expected = {
120 'base64-encoded-keys': [],
121 'v1': {
122 'availability-zone': 'myaz',
123 'cloud-name': 'subclasscloudname',
124 'instance-id': 'iid-datasource',
125 'local-hostname': 'test-subclass-hostname',
126 'region': 'myregion'},
127 'ds': {
128 'meta-data': {'availability_zone': 'myaz',
129 'local-hostname': 'test-subclass-hostname',
130 'region': 'myregion'},
131 'user-data': 'userdata_raw',
132 'vendor-data': 'vendordata_raw'}}
133 self.assertEqual(expected, util.load_json(content))
134 file_stat = os.stat(json_file)
135 self.assertEqual(0o600, stat.S_IMODE(file_stat.st_mode))
136
137 def test_get_data_handles_redacted_unserializable_content(self):
138 """get_data warns unserializable content in INSTANCE_JSON_FILE."""
139 tmp = self.tmp_dir()
140 datasource = DataSourceTestSubclassNet(
141 self.sys_cfg, self.distro, Paths({'run_dir': tmp}),
142 custom_userdata={'key1': 'val1', 'key2': {'key2.1': self.paths}})
143 self.assertTrue(datasource.get_data())
144 json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp)
145 content = util.load_file(json_file)
146 expected_userdata = {
147 'key1': 'val1',
148 'key2': {
149 'key2.1': "Warning: redacted unserializable type <class"
150 " 'cloudinit.helpers.Paths'>"}}
151 instance_json = util.load_json(content)
152 self.assertEqual(
153 expected_userdata, instance_json['ds']['user-data'])
154
155 @skipIf(not six.PY3, "json serialization on <= py2.7 handles bytes")
156 def test_get_data_base64encodes_unserializable_bytes(self):
157 """On py3, get_data base64encodes any unserializable content."""
158 tmp = self.tmp_dir()
159 datasource = DataSourceTestSubclassNet(
160 self.sys_cfg, self.distro, Paths({'run_dir': tmp}),
161 custom_userdata={'key1': 'val1', 'key2': {'key2.1': b'\x123'}})
162 self.assertTrue(datasource.get_data())
163 json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp)
164 content = util.load_file(json_file)
165 instance_json = util.load_json(content)
166 self.assertEqual(
167 ['ds/user-data/key2/key2.1'],
168 instance_json['base64-encoded-keys'])
169 self.assertEqual(
170 {'key1': 'val1', 'key2': {'key2.1': 'EjM='}},
171 instance_json['ds']['user-data'])
172
173 @skipIf(not six.PY2, "json serialization on <= py2.7 handles bytes")
174 def test_get_data_handles_bytes_values(self):
175 """On py2 get_data handles bytes values without having to b64encode."""
176 tmp = self.tmp_dir()
177 datasource = DataSourceTestSubclassNet(
178 self.sys_cfg, self.distro, Paths({'run_dir': tmp}),
179 custom_userdata={'key1': 'val1', 'key2': {'key2.1': b'\x123'}})
180 self.assertTrue(datasource.get_data())
181 json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp)
182 content = util.load_file(json_file)
183 instance_json = util.load_json(content)
184 self.assertEqual([], instance_json['base64-encoded-keys'])
185 self.assertEqual(
186 {'key1': 'val1', 'key2': {'key2.1': '\x123'}},
187 instance_json['ds']['user-data'])
188
189 @skipIf(not six.PY2, "Only python2 hits UnicodeDecodeErrors on non-utf8")
190 def test_non_utf8_encoding_logs_warning(self):
191 """When non-utf-8 values exist in py2 instance-data is not written."""
192 tmp = self.tmp_dir()
193 datasource = DataSourceTestSubclassNet(
194 self.sys_cfg, self.distro, Paths({'run_dir': tmp}),
195 custom_userdata={'key1': 'val1', 'key2': {'key2.1': b'ab\xaadef'}})
196 self.assertTrue(datasource.get_data())
197 json_file = self.tmp_path(INSTANCE_JSON_FILE, tmp)
198 self.assertFalse(os.path.exists(json_file))
199 self.assertIn(
200 "WARNING: Error persisting instance-data.json: 'utf8' codec can't"
201 " decode byte 0xaa in position 2: invalid start byte",
202 self.logs.getvalue())
diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
index e0adbda..feb884a 100644
--- a/cloudinit/tests/helpers.py
+++ b/cloudinit/tests/helpers.py
@@ -3,7 +3,6 @@
3from __future__ import print_function3from __future__ import print_function
44
5import functools5import functools
6import json
7import logging6import logging
8import os7import os
9import shutil8import shutil
@@ -104,7 +103,6 @@ class TestCase(unittest2.TestCase):
104 super(TestCase, self).setUp()103 super(TestCase, self).setUp()
105 self.reset_global_state()104 self.reset_global_state()
106105
107<<<<<<< cloudinit/tests/helpers.py
108 def add_patch(self, target, attr, **kwargs):106 def add_patch(self, target, attr, **kwargs):
109 """Patches specified target object and sets it as attr on test107 """Patches specified target object and sets it as attr on test
110 instance also schedules cleanup"""108 instance also schedules cleanup"""
@@ -115,8 +113,6 @@ class TestCase(unittest2.TestCase):
115 self.addCleanup(m.stop)113 self.addCleanup(m.stop)
116 setattr(self, attr, p)114 setattr(self, attr, p)
117115
118=======
119>>>>>>> cloudinit/tests/helpers.py
120116
121class CiTestCase(TestCase):117class CiTestCase(TestCase):
122 """This is the preferred test case base class unless user118 """This is the preferred test case base class unless user
@@ -340,12 +336,6 @@ def dir2dict(startdir, prefix=None):
340 return flist336 return flist
341337
342338
343def json_dumps(data):
344 # print data in nicely formatted json.
345 return json.dumps(data, indent=1, sort_keys=True,
346 separators=(',', ': '))
347
348
349def wrap_and_call(prefix, mocks, func, *args, **kwargs):339def wrap_and_call(prefix, mocks, func, *args, **kwargs):
350 """340 """
351 call func(args, **kwargs) with mocks applied, then unapplies mocks341 call func(args, **kwargs) with mocks applied, then unapplies mocks
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 6c014ba..30c5995 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -533,15 +533,6 @@ def multi_log(text, console=True, stderr=True,
533 log.log(log_level, text)533 log.log(log_level, text)
534534
535535
536def load_json(text, root_types=(dict,)):
537 decoded = json.loads(decode_binary(text))
538 if not isinstance(decoded, tuple(root_types)):
539 expected_types = ", ".join([str(t) for t in root_types])
540 raise TypeError("(%s) root types expected, got %s instead"
541 % (expected_types, type(decoded)))
542 return decoded
543
544
545def is_ipv4(instr):536def is_ipv4(instr):
546 """determine if input string is a ipv4 address. return boolean."""537 """determine if input string is a ipv4 address. return boolean."""
547 toks = instr.split('.')538 toks = instr.split('.')
@@ -1454,7 +1445,31 @@ def ensure_dirs(dirlist, mode=0o755):
1454 ensure_dir(d, mode)1445 ensure_dir(d, mode)
14551446
14561447
1448def load_json(text, root_types=(dict,)):
1449 decoded = json.loads(decode_binary(text))
1450 if not isinstance(decoded, tuple(root_types)):
1451 expected_types = ", ".join([str(t) for t in root_types])
1452 raise TypeError("(%s) root types expected, got %s instead"
1453 % (expected_types, type(decoded)))
1454 return decoded
1455
1456
1457def json_serialize_default(_obj):
1458 """Handler for types which aren't json serializable."""
1459 try:
1460 return 'ci-b64:{0}'.format(b64e(_obj))
1461 except AttributeError:
1462 return 'Warning: redacted unserializable type {0}'.format(type(_obj))
1463
1464
1465def json_dumps(data):
1466 """Return data in nicely formatted json."""
1467 return json.dumps(data, indent=1, sort_keys=True,
1468 separators=(',', ': '), default=json_serialize_default)
1469
1470
1457def yaml_dumps(obj, explicit_start=True, explicit_end=True):1471def yaml_dumps(obj, explicit_start=True, explicit_end=True):
1472 """Return data in nicely formatted yaml."""
1458 return yaml.safe_dump(obj,1473 return yaml.safe_dump(obj,
1459 line_break="\n",1474 line_break="\n",
1460 indent=4,1475 indent=4,
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index 82ee971..714f5da 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -67,7 +67,7 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
67 super(TestAliYunDatasource, self).setUp()67 super(TestAliYunDatasource, self).setUp()
68 cfg = {'datasource': {'AliYun': {'timeout': '1', 'max_wait': '1'}}}68 cfg = {'datasource': {'AliYun': {'timeout': '1', 'max_wait': '1'}}}
69 distro = {}69 distro = {}
70 paths = helpers.Paths({})70 paths = helpers.Paths({'run_dir': self.tmp_dir()})
71 self.ds = ay.DataSourceAliYun(cfg, distro, paths)71 self.ds = ay.DataSourceAliYun(cfg, distro, paths)
72 self.metadata_address = self.ds.metadata_urls[0]72 self.metadata_address = self.ds.metadata_urls[0]
7373
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index a4dfb54..3253f3a 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -18,7 +18,7 @@ import tempfile
18from cloudinit import helpers18from cloudinit import helpers
19from cloudinit import util19from cloudinit import util
2020
21from cloudinit.tests.helpers import TestCase21from cloudinit.tests.helpers import CiTestCase
2222
23import cloudinit.sources.DataSourceAltCloud as dsac23import cloudinit.sources.DataSourceAltCloud as dsac
2424
@@ -97,7 +97,7 @@ def _dmi_data(expected):
97 return _data97 return _data
9898
9999
100class TestGetCloudType(TestCase):100class TestGetCloudType(CiTestCase):
101 '''101 '''
102 Test to exercise method: DataSourceAltCloud.get_cloud_type()102 Test to exercise method: DataSourceAltCloud.get_cloud_type()
103 '''103 '''
@@ -143,14 +143,16 @@ class TestGetCloudType(TestCase):
143 self.assertEqual('UNKNOWN', dsrc.get_cloud_type())143 self.assertEqual('UNKNOWN', dsrc.get_cloud_type())
144144
145145
146class TestGetDataCloudInfoFile(TestCase):146class TestGetDataCloudInfoFile(CiTestCase):
147 '''147 '''
148 Test to exercise method: DataSourceAltCloud.get_data()148 Test to exercise method: DataSourceAltCloud.get_data()
149 With a contrived CLOUD_INFO_FILE149 With a contrived CLOUD_INFO_FILE
150 '''150 '''
151 def setUp(self):151 def setUp(self):
152 '''Set up.'''152 '''Set up.'''
153 self.paths = helpers.Paths({'cloud_dir': '/tmp'})153 self.tmp = self.tmp_dir()
154 self.paths = helpers.Paths(
155 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
154 self.cloud_info_file = tempfile.mkstemp()[1]156 self.cloud_info_file = tempfile.mkstemp()[1]
155 self.dmi_data = util.read_dmi_data157 self.dmi_data = util.read_dmi_data
156 dsac.CLOUD_INFO_FILE = self.cloud_info_file158 dsac.CLOUD_INFO_FILE = self.cloud_info_file
@@ -207,14 +209,16 @@ class TestGetDataCloudInfoFile(TestCase):
207 self.assertEqual(False, dsrc.get_data())209 self.assertEqual(False, dsrc.get_data())
208210
209211
210class TestGetDataNoCloudInfoFile(TestCase):212class TestGetDataNoCloudInfoFile(CiTestCase):
211 '''213 '''
212 Test to exercise method: DataSourceAltCloud.get_data()214 Test to exercise method: DataSourceAltCloud.get_data()
213 Without a CLOUD_INFO_FILE215 Without a CLOUD_INFO_FILE
214 '''216 '''
215 def setUp(self):217 def setUp(self):
216 '''Set up.'''218 '''Set up.'''
217 self.paths = helpers.Paths({'cloud_dir': '/tmp'})219 self.tmp = self.tmp_dir()
220 self.paths = helpers.Paths(
221 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
218 self.dmi_data = util.read_dmi_data222 self.dmi_data = util.read_dmi_data
219 dsac.CLOUD_INFO_FILE = \223 dsac.CLOUD_INFO_FILE = \
220 'no such file'224 'no such file'
@@ -254,7 +258,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
254 self.assertEqual(False, dsrc.get_data())258 self.assertEqual(False, dsrc.get_data())
255259
256260
257class TestUserDataRhevm(TestCase):261class TestUserDataRhevm(CiTestCase):
258 '''262 '''
259 Test to exercise method: DataSourceAltCloud.user_data_rhevm()263 Test to exercise method: DataSourceAltCloud.user_data_rhevm()
260 '''264 '''
@@ -320,7 +324,7 @@ class TestUserDataRhevm(TestCase):
320 self.assertEqual(False, dsrc.user_data_rhevm())324 self.assertEqual(False, dsrc.user_data_rhevm())
321325
322326
323class TestUserDataVsphere(TestCase):327class TestUserDataVsphere(CiTestCase):
324 '''328 '''
325 Test to exercise method: DataSourceAltCloud.user_data_vsphere()329 Test to exercise method: DataSourceAltCloud.user_data_vsphere()
326 '''330 '''
@@ -368,7 +372,7 @@ class TestUserDataVsphere(TestCase):
368 self.assertEqual(1, m_mount_cb.call_count)372 self.assertEqual(1, m_mount_cb.call_count)
369373
370374
371class TestReadUserDataCallback(TestCase):375class TestReadUserDataCallback(CiTestCase):
372 '''376 '''
373 Test to exercise method: DataSourceAltCloud.read_user_data_callback()377 Test to exercise method: DataSourceAltCloud.read_user_data_callback()
374 '''378 '''
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 7cb1812..226c214 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -11,9 +11,7 @@ from cloudinit.tests.helpers import (CiTestCase, TestCase, populate_dir, mock,
1111
12import crypt12import crypt
13import os13import os
14import shutil
15import stat14import stat
16import tempfile
17import xml.etree.ElementTree as ET15import xml.etree.ElementTree as ET
18import yaml16import yaml
1917
@@ -84,11 +82,11 @@ class TestAzureDataSource(CiTestCase):
84 super(TestAzureDataSource, self).setUp()82 super(TestAzureDataSource, self).setUp()
85 if PY26:83 if PY26:
86 raise SkipTest("Does not work on python 2.6")84 raise SkipTest("Does not work on python 2.6")
87 self.tmp = tempfile.mkdtemp()85 self.tmp = self.tmp_dir()
88 self.addCleanup(shutil.rmtree, self.tmp)
8986
90 # patch cloud_dir, so our 'seed_dir' is guaranteed empty87 # patch cloud_dir, so our 'seed_dir' is guaranteed empty
91 self.paths = helpers.Paths({'cloud_dir': self.tmp})88 self.paths = helpers.Paths(
89 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
92 self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent')90 self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent')
9391
94 self.patches = ExitStack()92 self.patches = ExitStack()
@@ -642,7 +640,7 @@ fdescfs /dev/fd fdescfs rw 0 0
642 self.assertEqual(netconfig, expected_config)640 self.assertEqual(netconfig, expected_config)
643641
644642
645class TestAzureBounce(TestCase):643class TestAzureBounce(CiTestCase):
646644
647 def mock_out_azure_moving_parts(self):645 def mock_out_azure_moving_parts(self):
648 self.patches.enter_context(646 self.patches.enter_context(
@@ -669,10 +667,10 @@ class TestAzureBounce(TestCase):
669667
670 def setUp(self):668 def setUp(self):
671 super(TestAzureBounce, self).setUp()669 super(TestAzureBounce, self).setUp()
672 self.tmp = tempfile.mkdtemp()670 self.tmp = self.tmp_dir()
673 self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent')671 self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent')
674 self.paths = helpers.Paths({'cloud_dir': self.tmp})672 self.paths = helpers.Paths(
675 self.addCleanup(shutil.rmtree, self.tmp)673 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
676 dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d674 dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
677 self.patches = ExitStack()675 self.patches = ExitStack()
678 self.mock_out_azure_moving_parts()676 self.mock_out_azure_moving_parts()
@@ -714,21 +712,24 @@ class TestAzureBounce(TestCase):
714712
715 def test_disabled_bounce_does_not_change_hostname(self):713 def test_disabled_bounce_does_not_change_hostname(self):
716 cfg = {'hostname_bounce': {'policy': 'off'}}714 cfg = {'hostname_bounce': {'policy': 'off'}}
717 self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg)).get_data()715 ds = self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg))
716 ds.get_data()
718 self.assertEqual(0, self.set_hostname.call_count)717 self.assertEqual(0, self.set_hostname.call_count)
719718
720 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')719 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
721 def test_disabled_bounce_does_not_perform_bounce(720 def test_disabled_bounce_does_not_perform_bounce(
722 self, perform_hostname_bounce):721 self, perform_hostname_bounce):
723 cfg = {'hostname_bounce': {'policy': 'off'}}722 cfg = {'hostname_bounce': {'policy': 'off'}}
724 self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg)).get_data()723 ds = self._get_ds(self.get_ovf_env_with_dscfg('test-host', cfg))
724 ds.get_data()
725 self.assertEqual(0, perform_hostname_bounce.call_count)725 self.assertEqual(0, perform_hostname_bounce.call_count)
726726
727 def test_same_hostname_does_not_change_hostname(self):727 def test_same_hostname_does_not_change_hostname(self):
728 host_name = 'unchanged-host-name'728 host_name = 'unchanged-host-name'
729 self.get_hostname.return_value = host_name729 self.get_hostname.return_value = host_name
730 cfg = {'hostname_bounce': {'policy': 'yes'}}730 cfg = {'hostname_bounce': {'policy': 'yes'}}
731 self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg)).get_data()731 ds = self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg))
732 ds.get_data()
732 self.assertEqual(0, self.set_hostname.call_count)733 self.assertEqual(0, self.set_hostname.call_count)
733734
734 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')735 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
@@ -737,7 +738,8 @@ class TestAzureBounce(TestCase):
737 host_name = 'unchanged-host-name'738 host_name = 'unchanged-host-name'
738 self.get_hostname.return_value = host_name739 self.get_hostname.return_value = host_name
739 cfg = {'hostname_bounce': {'policy': 'yes'}}740 cfg = {'hostname_bounce': {'policy': 'yes'}}
740 self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg)).get_data()741 ds = self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg))
742 ds.get_data()
741 self.assertEqual(0, perform_hostname_bounce.call_count)743 self.assertEqual(0, perform_hostname_bounce.call_count)
742744
743 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')745 @mock.patch('cloudinit.sources.DataSourceAzure.perform_hostname_bounce')
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
index 0c468dc..b42b073 100644
--- a/tests/unittests/test_datasource/test_azure_helper.py
+++ b/tests/unittests/test_datasource/test_azure_helper.py
@@ -4,11 +4,7 @@ import os
4from textwrap import dedent4from textwrap import dedent
55
6from cloudinit.sources.helpers import azure as azure_helper6from cloudinit.sources.helpers import azure as azure_helper
7<<<<<<< tests/unittests/test_datasource/test_azure_helper.py
8from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir7from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir
9=======
10from cloudinit.tests.helpers import ExitStack, mock, TestCase
11>>>>>>> tests/unittests/test_datasource/test_azure_helper.py
128
13from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim9from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim
1410
diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py
index e4c5990..f6a59b6 100644
--- a/tests/unittests/test_datasource/test_cloudsigma.py
+++ b/tests/unittests/test_datasource/test_cloudsigma.py
@@ -3,6 +3,7 @@
3import copy3import copy
44
5from cloudinit.cs_utils import Cepko5from cloudinit.cs_utils import Cepko
6from cloudinit import helpers
6from cloudinit import sources7from cloudinit import sources
7from cloudinit.sources import DataSourceCloudSigma8from cloudinit.sources import DataSourceCloudSigma
89
@@ -38,10 +39,12 @@ class CepkoMock(Cepko):
38 return self39 return self
3940
4041
41class DataSourceCloudSigmaTest(test_helpers.TestCase):42class DataSourceCloudSigmaTest(test_helpers.CiTestCase):
42 def setUp(self):43 def setUp(self):
43 super(DataSourceCloudSigmaTest, self).setUp()44 super(DataSourceCloudSigmaTest, self).setUp()
44 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "")45 self.paths = helpers.Paths({'run_dir': self.tmp_dir()})
46 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma(
47 "", "", paths=self.paths)
45 self.datasource.is_running_in_cloudsigma = lambda: True48 self.datasource.is_running_in_cloudsigma = lambda: True
46 self.datasource.cepko = CepkoMock(SERVER_CONTEXT)49 self.datasource.cepko = CepkoMock(SERVER_CONTEXT)
47 self.datasource.get_data()50 self.datasource.get_data()
@@ -85,7 +88,8 @@ class DataSourceCloudSigmaTest(test_helpers.TestCase):
85 def test_lack_of_vendor_data(self):88 def test_lack_of_vendor_data(self):
86 stripped_context = copy.deepcopy(SERVER_CONTEXT)89 stripped_context = copy.deepcopy(SERVER_CONTEXT)
87 del stripped_context["vendor_data"]90 del stripped_context["vendor_data"]
88 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "")91 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma(
92 "", "", paths=self.paths)
89 self.datasource.cepko = CepkoMock(stripped_context)93 self.datasource.cepko = CepkoMock(stripped_context)
90 self.datasource.get_data()94 self.datasource.get_data()
9195
@@ -94,7 +98,8 @@ class DataSourceCloudSigmaTest(test_helpers.TestCase):
94 def test_lack_of_cloudinit_key_in_vendor_data(self):98 def test_lack_of_cloudinit_key_in_vendor_data(self):
95 stripped_context = copy.deepcopy(SERVER_CONTEXT)99 stripped_context = copy.deepcopy(SERVER_CONTEXT)
96 del stripped_context["vendor_data"]["cloudinit"]100 del stripped_context["vendor_data"]["cloudinit"]
97 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "")101 self.datasource = DataSourceCloudSigma.DataSourceCloudSigma(
102 "", "", paths=self.paths)
98 self.datasource.cepko = CepkoMock(stripped_context)103 self.datasource.cepko = CepkoMock(stripped_context)
99 self.datasource.get_data()104 self.datasource.get_data()
100105
diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py
index 180ebee..d6d2d6b 100644
--- a/tests/unittests/test_datasource/test_cloudstack.py
+++ b/tests/unittests/test_datasource/test_cloudstack.py
@@ -5,11 +5,7 @@ from cloudinit import util
5from cloudinit.sources.DataSourceCloudStack import (5from cloudinit.sources.DataSourceCloudStack import (
6 DataSourceCloudStack, get_latest_lease)6 DataSourceCloudStack, get_latest_lease)
77
8<<<<<<< tests/unittests/test_datasource/test_cloudstack.py
9from cloudinit.tests.helpers import CiTestCase, ExitStack, mock8from cloudinit.tests.helpers import CiTestCase, ExitStack, mock
10=======
11from cloudinit.tests.helpers import TestCase, mock, ExitStack
12>>>>>>> tests/unittests/test_datasource/test_cloudstack.py
139
14import os10import os
15import time11import time
@@ -37,6 +33,7 @@ class TestCloudStackPasswordFetching(CiTestCase):
37 self.patches.enter_context(mock.patch(33 self.patches.enter_context(mock.patch(
38 mod_name + '.dhcp.networkd_get_option_from_leases',34 mod_name + '.dhcp.networkd_get_option_from_leases',
39 get_networkd_server_address))35 get_networkd_server_address))
36 self.tmp = self.tmp_dir()
4037
41 def _set_password_server_response(self, response_string):38 def _set_password_server_response(self, response_string):
42 subp = mock.MagicMock(return_value=(response_string, ''))39 subp = mock.MagicMock(return_value=(response_string, ''))
@@ -47,26 +44,30 @@ class TestCloudStackPasswordFetching(CiTestCase):
4744
48 def test_empty_password_doesnt_create_config(self):45 def test_empty_password_doesnt_create_config(self):
49 self._set_password_server_response('')46 self._set_password_server_response('')
50 ds = DataSourceCloudStack({}, None, helpers.Paths({}))47 ds = DataSourceCloudStack(
48 {}, None, helpers.Paths({'run_dir': self.tmp}))
51 ds.get_data()49 ds.get_data()
52 self.assertEqual({}, ds.get_config_obj())50 self.assertEqual({}, ds.get_config_obj())
5351
54 def test_saved_password_doesnt_create_config(self):52 def test_saved_password_doesnt_create_config(self):
55 self._set_password_server_response('saved_password')53 self._set_password_server_response('saved_password')
56 ds = DataSourceCloudStack({}, None, helpers.Paths({}))54 ds = DataSourceCloudStack(
55 {}, None, helpers.Paths({'run_dir': self.tmp}))
57 ds.get_data()56 ds.get_data()
58 self.assertEqual({}, ds.get_config_obj())57 self.assertEqual({}, ds.get_config_obj())
5958
60 def test_password_sets_password(self):59 def test_password_sets_password(self):
61 password = 'SekritSquirrel'60 password = 'SekritSquirrel'
62 self._set_password_server_response(password)61 self._set_password_server_response(password)
63 ds = DataSourceCloudStack({}, None, helpers.Paths({}))62 ds = DataSourceCloudStack(
63 {}, None, helpers.Paths({'run_dir': self.tmp}))
64 ds.get_data()64 ds.get_data()
65 self.assertEqual(password, ds.get_config_obj()['password'])65 self.assertEqual(password, ds.get_config_obj()['password'])
6666
67 def test_bad_request_doesnt_stop_ds_from_working(self):67 def test_bad_request_doesnt_stop_ds_from_working(self):
68 self._set_password_server_response('bad_request')68 self._set_password_server_response('bad_request')
69 ds = DataSourceCloudStack({}, None, helpers.Paths({}))69 ds = DataSourceCloudStack(
70 {}, None, helpers.Paths({'run_dir': self.tmp}))
70 self.assertTrue(ds.get_data())71 self.assertTrue(ds.get_data())
7172
72 def assertRequestTypesSent(self, subp, expected_request_types):73 def assertRequestTypesSent(self, subp, expected_request_types):
@@ -81,14 +82,16 @@ class TestCloudStackPasswordFetching(CiTestCase):
81 def test_valid_response_means_password_marked_as_saved(self):82 def test_valid_response_means_password_marked_as_saved(self):
82 password = 'SekritSquirrel'83 password = 'SekritSquirrel'
83 subp = self._set_password_server_response(password)84 subp = self._set_password_server_response(password)
84 ds = DataSourceCloudStack({}, None, helpers.Paths({}))85 ds = DataSourceCloudStack(
86 {}, None, helpers.Paths({'run_dir': self.tmp}))
85 ds.get_data()87 ds.get_data()
86 self.assertRequestTypesSent(subp,88 self.assertRequestTypesSent(subp,
87 ['send_my_password', 'saved_password'])89 ['send_my_password', 'saved_password'])
8890
89 def _check_password_not_saved_for(self, response_string):91 def _check_password_not_saved_for(self, response_string):
90 subp = self._set_password_server_response(response_string)92 subp = self._set_password_server_response(response_string)
91 ds = DataSourceCloudStack({}, None, helpers.Paths({}))93 ds = DataSourceCloudStack(
94 {}, None, helpers.Paths({'run_dir': self.tmp}))
92 ds.get_data()95 ds.get_data()
93 self.assertRequestTypesSent(subp, ['send_my_password'])96 self.assertRequestTypesSent(subp, ['send_my_password'])
9497
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
index 237c189..9849788 100644
--- a/tests/unittests/test_datasource/test_configdrive.py
+++ b/tests/unittests/test_datasource/test_configdrive.py
@@ -725,8 +725,9 @@ class TestConvertNetworkData(TestCase):
725725
726726
727def cfg_ds_from_dir(seed_d):727def cfg_ds_from_dir(seed_d):
728 tmp = tempfile.mkdtemp()
728 cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN, None,729 cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN, None,
729 helpers.Paths({}))730 helpers.Paths({'run_dir': tmp}))
730 cfg_ds.seed_dir = seed_d731 cfg_ds.seed_dir = seed_d
731 cfg_ds.known_macs = KNOWN_MACS.copy()732 cfg_ds.known_macs = KNOWN_MACS.copy()
732 if not cfg_ds.get_data():733 if not cfg_ds.get_data():
diff --git a/tests/unittests/test_datasource/test_digitalocean.py b/tests/unittests/test_datasource/test_digitalocean.py
index f264f36..ec32173 100644
--- a/tests/unittests/test_datasource/test_digitalocean.py
+++ b/tests/unittests/test_datasource/test_digitalocean.py
@@ -13,7 +13,7 @@ from cloudinit import settings
13from cloudinit.sources import DataSourceDigitalOcean13from cloudinit.sources import DataSourceDigitalOcean
14from cloudinit.sources.helpers import digitalocean14from cloudinit.sources.helpers import digitalocean
1515
16from cloudinit.tests.helpers import mock, TestCase16from cloudinit.tests.helpers import mock, CiTestCase
1717
18DO_MULTIPLE_KEYS = ["ssh-rsa AAAAB3NzaC1yc2EAAAA... test1@do.co",18DO_MULTIPLE_KEYS = ["ssh-rsa AAAAB3NzaC1yc2EAAAA... test1@do.co",
19 "ssh-rsa AAAAB3NzaC1yc2EAAAA... test2@do.co"]19 "ssh-rsa AAAAB3NzaC1yc2EAAAA... test2@do.co"]
@@ -135,14 +135,17 @@ def _mock_dmi():
135 return (True, DO_META.get('id'))135 return (True, DO_META.get('id'))
136136
137137
138class TestDataSourceDigitalOcean(TestCase):138class TestDataSourceDigitalOcean(CiTestCase):
139 """139 """
140 Test reading the meta-data140 Test reading the meta-data
141 """141 """
142 def setUp(self):
143 super(TestDataSourceDigitalOcean, self).setUp()
144 self.tmp = self.tmp_dir()
142145
143 def get_ds(self, get_sysinfo=_mock_dmi):146 def get_ds(self, get_sysinfo=_mock_dmi):
144 ds = DataSourceDigitalOcean.DataSourceDigitalOcean(147 ds = DataSourceDigitalOcean.DataSourceDigitalOcean(
145 settings.CFG_BUILTIN, None, helpers.Paths({}))148 settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp}))
146 ds.use_ip4LL = False149 ds.use_ip4LL = False
147 if get_sysinfo is not None:150 if get_sysinfo is not None:
148 ds._get_sysinfo = get_sysinfo151 ds._get_sysinfo = get_sysinfo
@@ -194,7 +197,7 @@ class TestDataSourceDigitalOcean(TestCase):
194 self.assertIsInstance(ds.get_public_ssh_keys(), list)197 self.assertIsInstance(ds.get_public_ssh_keys(), list)
195198
196199
197class TestNetworkConvert(TestCase):200class TestNetworkConvert(CiTestCase):
198201
199 @mock.patch('cloudinit.net.get_interfaces_by_mac')202 @mock.patch('cloudinit.net.get_interfaces_by_mac')
200 def _get_networking(self, m_get_by_mac):203 def _get_networking(self, m_get_by_mac):
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index ba328ee..ba042ea 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -186,6 +186,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
186 super(TestEc2, self).setUp()186 super(TestEc2, self).setUp()
187 self.datasource = ec2.DataSourceEc2187 self.datasource = ec2.DataSourceEc2
188 self.metadata_addr = self.datasource.metadata_urls[0]188 self.metadata_addr = self.datasource.metadata_urls[0]
189 self.tmp = self.tmp_dir()
189190
190 def data_url(self, version):191 def data_url(self, version):
191 """Return a metadata url based on the version provided."""192 """Return a metadata url based on the version provided."""
@@ -199,7 +200,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
199 def _setup_ds(self, sys_cfg, platform_data, md, md_version=None):200 def _setup_ds(self, sys_cfg, platform_data, md, md_version=None):
200 self.uris = []201 self.uris = []
201 distro = {}202 distro = {}
202 paths = helpers.Paths({})203 paths = helpers.Paths({'run_dir': self.tmp})
203 if sys_cfg is None:204 if sys_cfg is None:
204 sys_cfg = {}205 sys_cfg = {}
205 ds = self.datasource(sys_cfg=sys_cfg, distro=distro, paths=paths)206 ds = self.datasource(sys_cfg=sys_cfg, distro=distro, paths=paths)
diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py
index d399ae7..82c788d 100644
--- a/tests/unittests/test_datasource/test_gce.py
+++ b/tests/unittests/test_datasource/test_gce.py
@@ -70,9 +70,10 @@ def _set_mock_metadata(gce_meta=None):
70class TestDataSourceGCE(test_helpers.HttprettyTestCase):70class TestDataSourceGCE(test_helpers.HttprettyTestCase):
7171
72 def setUp(self):72 def setUp(self):
73 tmp = self.tmp_dir()
73 self.ds = DataSourceGCE.DataSourceGCE(74 self.ds = DataSourceGCE.DataSourceGCE(
74 settings.CFG_BUILTIN, None,75 settings.CFG_BUILTIN, None,
75 helpers.Paths({}))76 helpers.Paths({'run_dir': tmp}))
76 ppatch = self.m_platform_reports_gce = mock.patch(77 ppatch = self.m_platform_reports_gce = mock.patch(
77 'cloudinit.sources.DataSourceGCE.platform_reports_gce')78 'cloudinit.sources.DataSourceGCE.platform_reports_gce')
78 self.m_platform_reports_gce = ppatch.start()79 self.m_platform_reports_gce = ppatch.start()
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index fea9156..70d50de 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -3,22 +3,20 @@
3from cloudinit import helpers3from cloudinit import helpers
4from cloudinit.sources import DataSourceNoCloud4from cloudinit.sources import DataSourceNoCloud
5from cloudinit import util5from cloudinit import util
6from cloudinit.tests.helpers import TestCase, populate_dir, mock, ExitStack6from cloudinit.tests.helpers import CiTestCase, populate_dir, mock, ExitStack
77
8import os8import os
9import shutil
10import tempfile
11import textwrap9import textwrap
12import yaml10import yaml
1311
1412
15class TestNoCloudDataSource(TestCase):13class TestNoCloudDataSource(CiTestCase):
1614
17 def setUp(self):15 def setUp(self):
18 super(TestNoCloudDataSource, self).setUp()16 super(TestNoCloudDataSource, self).setUp()
19 self.tmp = tempfile.mkdtemp()17 self.tmp = self.tmp_dir()
20 self.addCleanup(shutil.rmtree, self.tmp)18 self.paths = helpers.Paths(
21 self.paths = helpers.Paths({'cloud_dir': self.tmp})19 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
2220
23 self.cmdline = "root=TESTCMDLINE"21 self.cmdline = "root=TESTCMDLINE"
2422
@@ -215,7 +213,7 @@ class TestNoCloudDataSource(TestCase):
215 self.assertNotIn(gateway, str(dsrc.network_config))213 self.assertNotIn(gateway, str(dsrc.network_config))
216214
217215
218class TestParseCommandLineData(TestCase):216class TestParseCommandLineData(CiTestCase):
219217
220 def test_parse_cmdline_data_valid(self):218 def test_parse_cmdline_data_valid(self):
221 ds_id = "ds=nocloud"219 ds_id = "ds=nocloud"
diff --git a/tests/unittests/test_datasource/test_opennebula.py b/tests/unittests/test_datasource/test_opennebula.py
index e7d5569..2326dd5 100644
--- a/tests/unittests/test_datasource/test_opennebula.py
+++ b/tests/unittests/test_datasource/test_opennebula.py
@@ -3,12 +3,10 @@
3from cloudinit import helpers3from cloudinit import helpers
4from cloudinit.sources import DataSourceOpenNebula as ds4from cloudinit.sources import DataSourceOpenNebula as ds
5from cloudinit import util5from cloudinit import util
6from cloudinit.tests.helpers import mock, populate_dir, TestCase6from cloudinit.tests.helpers import mock, populate_dir, CiTestCase
77
8import os8import os
9import pwd9import pwd
10import shutil
11import tempfile
12import unittest10import unittest
1311
1412
@@ -36,14 +34,14 @@ PUBLIC_IP = '10.0.0.3'
36DS_PATH = "cloudinit.sources.DataSourceOpenNebula"34DS_PATH = "cloudinit.sources.DataSourceOpenNebula"
3735
3836
39class TestOpenNebulaDataSource(TestCase):37class TestOpenNebulaDataSource(CiTestCase):
40 parsed_user = None38 parsed_user = None
4139
42 def setUp(self):40 def setUp(self):
43 super(TestOpenNebulaDataSource, self).setUp()41 super(TestOpenNebulaDataSource, self).setUp()
44 self.tmp = tempfile.mkdtemp()42 self.tmp = self.tmp_dir()
45 self.addCleanup(shutil.rmtree, self.tmp)43 self.paths = helpers.Paths(
46 self.paths = helpers.Paths({'cloud_dir': self.tmp})44 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
4745
48 # defaults for few tests46 # defaults for few tests
49 self.ds = ds.DataSourceOpenNebula47 self.ds = ds.DataSourceOpenNebula
diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py
index ed367e0..42c3155 100644
--- a/tests/unittests/test_datasource/test_openstack.py
+++ b/tests/unittests/test_datasource/test_openstack.py
@@ -131,6 +131,10 @@ def _read_metadata_service():
131class TestOpenStackDataSource(test_helpers.HttprettyTestCase):131class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
132 VERSION = 'latest'132 VERSION = 'latest'
133133
134 def setUp(self):
135 super(TestOpenStackDataSource, self).setUp()
136 self.tmp = self.tmp_dir()
137
134 @hp.activate138 @hp.activate
135 def test_successful(self):139 def test_successful(self):
136 _register_uris(self.VERSION, EC2_FILES, EC2_META, OS_FILES)140 _register_uris(self.VERSION, EC2_FILES, EC2_META, OS_FILES)
@@ -232,7 +236,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
232 _register_uris(self.VERSION, EC2_FILES, EC2_META, OS_FILES)236 _register_uris(self.VERSION, EC2_FILES, EC2_META, OS_FILES)
233 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,237 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,
234 None,238 None,
235 helpers.Paths({}))239 helpers.Paths({'run_dir': self.tmp}))
236 self.assertIsNone(ds_os.version)240 self.assertIsNone(ds_os.version)
237 found = ds_os.get_data()241 found = ds_os.get_data()
238 self.assertTrue(found)242 self.assertTrue(found)
@@ -256,7 +260,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
256 _register_uris(self.VERSION, {}, {}, os_files)260 _register_uris(self.VERSION, {}, {}, os_files)
257 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,261 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,
258 None,262 None,
259 helpers.Paths({}))263 helpers.Paths({'run_dir': self.tmp}))
260 self.assertIsNone(ds_os.version)264 self.assertIsNone(ds_os.version)
261 found = ds_os.get_data()265 found = ds_os.get_data()
262 self.assertFalse(found)266 self.assertFalse(found)
@@ -271,7 +275,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
271 _register_uris(self.VERSION, {}, {}, os_files)275 _register_uris(self.VERSION, {}, {}, os_files)
272 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,276 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,
273 None,277 None,
274 helpers.Paths({}))278 helpers.Paths({'run_dir': self.tmp}))
275 ds_os.ds_cfg = {279 ds_os.ds_cfg = {
276 'max_wait': 0,280 'max_wait': 0,
277 'timeout': 0,281 'timeout': 0,
@@ -294,7 +298,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
294 _register_uris(self.VERSION, {}, {}, os_files)298 _register_uris(self.VERSION, {}, {}, os_files)
295 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,299 ds_os = ds.DataSourceOpenStack(settings.CFG_BUILTIN,
296 None,300 None,
297 helpers.Paths({}))301 helpers.Paths({'run_dir': self.tmp}))
298 ds_os.ds_cfg = {302 ds_os.ds_cfg = {
299 'max_wait': 0,303 'max_wait': 0,
300 'timeout': 0,304 'timeout': 0,
diff --git a/tests/unittests/test_datasource/test_scaleway.py b/tests/unittests/test_datasource/test_scaleway.py
index 436df9e..8dec06b 100644
--- a/tests/unittests/test_datasource/test_scaleway.py
+++ b/tests/unittests/test_datasource/test_scaleway.py
@@ -9,7 +9,7 @@ from cloudinit import helpers
9from cloudinit import settings9from cloudinit import settings
10from cloudinit.sources import DataSourceScaleway10from cloudinit.sources import DataSourceScaleway
1111
12from cloudinit.tests.helpers import mock, HttprettyTestCase, TestCase12from cloudinit.tests.helpers import mock, HttprettyTestCase, CiTestCase
1313
1414
15class DataResponses(object):15class DataResponses(object):
@@ -63,7 +63,11 @@ class MetadataResponses(object):
63 return 200, headers, json.dumps(cls.FAKE_METADATA)63 return 200, headers, json.dumps(cls.FAKE_METADATA)
6464
6565
66class TestOnScaleway(TestCase):66class TestOnScaleway(CiTestCase):
67
68 def setUp(self):
69 super(TestOnScaleway, self).setUp()
70 self.tmp = self.tmp_dir()
6771
68 def install_mocks(self, fake_dmi, fake_file_exists, fake_cmdline):72 def install_mocks(self, fake_dmi, fake_file_exists, fake_cmdline):
69 mock, faked = fake_dmi73 mock, faked = fake_dmi
@@ -91,7 +95,7 @@ class TestOnScaleway(TestCase):
9195
92 # When not on Scaleway, get_data() returns False.96 # When not on Scaleway, get_data() returns False.
93 datasource = DataSourceScaleway.DataSourceScaleway(97 datasource = DataSourceScaleway.DataSourceScaleway(
94 settings.CFG_BUILTIN, None, helpers.Paths({})98 settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp})
95 )99 )
96 self.assertFalse(datasource.get_data())100 self.assertFalse(datasource.get_data())
97101
@@ -159,8 +163,9 @@ def get_source_address_adapter(*args, **kwargs):
159class TestDataSourceScaleway(HttprettyTestCase):163class TestDataSourceScaleway(HttprettyTestCase):
160164
161 def setUp(self):165 def setUp(self):
166 tmp = self.tmp_dir()
162 self.datasource = DataSourceScaleway.DataSourceScaleway(167 self.datasource = DataSourceScaleway.DataSourceScaleway(
163 settings.CFG_BUILTIN, None, helpers.Paths({})168 settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': tmp})
164 )169 )
165 super(TestDataSourceScaleway, self).setUp()170 super(TestDataSourceScaleway, self).setUp()
166171
diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
index 933d5b6..88bae5f 100644
--- a/tests/unittests/test_datasource/test_smartos.py
+++ b/tests/unittests/test_datasource/test_smartos.py
@@ -359,7 +359,8 @@ class TestSmartOSDataSource(FilesystemMockingTestCase):
359359
360 self.tmp = tempfile.mkdtemp()360 self.tmp = tempfile.mkdtemp()
361 self.addCleanup(shutil.rmtree, self.tmp)361 self.addCleanup(shutil.rmtree, self.tmp)
362 self.paths = c_helpers.Paths({'cloud_dir': self.tmp})362 self.paths = c_helpers.Paths(
363 {'cloud_dir': self.tmp, 'run_dir': self.tmp})
363364
364 self.legacy_user_d = os.path.join(self.tmp, 'legacy_user_tmp')365 self.legacy_user_d = os.path.join(self.tmp, 'legacy_user_tmp')
365 os.mkdir(self.legacy_user_d)366 os.mkdir(self.legacy_user_d)
diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py
index 1284e75..7a920d4 100644
--- a/tests/unittests/test_ds_identify.py
+++ b/tests/unittests/test_ds_identify.py
@@ -7,7 +7,7 @@ from uuid import uuid4
7from cloudinit import safeyaml7from cloudinit import safeyaml
8from cloudinit import util8from cloudinit import util
9from cloudinit.tests.helpers import (9from cloudinit.tests.helpers import (
10 CiTestCase, dir2dict, json_dumps, populate_dir)10 CiTestCase, dir2dict, populate_dir)
1111
12UNAME_MYSYS = ("Linux bart 4.4.0-62-generic #83-Ubuntu "12UNAME_MYSYS = ("Linux bart 4.4.0-62-generic #83-Ubuntu "
13 "SMP Wed Jan 18 14:10:15 UTC 2017 x86_64 GNU/Linux")13 "SMP Wed Jan 18 14:10:15 UTC 2017 x86_64 GNU/Linux")
@@ -319,7 +319,7 @@ def _print_run_output(rc, out, err, cfg, files):
319 '-- rc = %s --' % rc,319 '-- rc = %s --' % rc,
320 '-- out --', str(out),320 '-- out --', str(out),
321 '-- err --', str(err),321 '-- err --', str(err),
322 '-- cfg --', json_dumps(cfg)]))322 '-- cfg --', util.json_dumps(cfg)]))
323 print('-- files --')323 print('-- files --')
324 for k, v in files.items():324 for k, v in files.items():
325 if "/_shwrap" in k:325 if "/_shwrap" in k:
diff --git a/tests/unittests/test_handler/test_handler_chef.py b/tests/unittests/test_handler/test_handler_chef.py
index 002fa19..0136a93 100644
--- a/tests/unittests/test_handler/test_handler_chef.py
+++ b/tests/unittests/test_handler/test_handler_chef.py
@@ -13,12 +13,8 @@ from cloudinit import helpers
13from cloudinit.sources import DataSourceNone13from cloudinit.sources import DataSourceNone
14from cloudinit import util14from cloudinit import util
1515
16<<<<<<< tests/unittests/test_handler/test_handler_chef.py
17from cloudinit.tests.helpers import (16from cloudinit.tests.helpers import (
18 CiTestCase, FilesystemMockingTestCase, mock, skipIf)17 CiTestCase, FilesystemMockingTestCase, mock, skipIf)
19=======
20from cloudinit.tests import helpers as t_help
21>>>>>>> tests/unittests/test_handler/test_handler_chef.py
2218
23LOG = logging.getLogger(__name__)19LOG = logging.getLogger(__name__)
2420
diff --git a/tests/unittests/test_handler/test_handler_lxd.py b/tests/unittests/test_handler/test_handler_lxd.py
index a8ce315..e0d9ab6 100644
--- a/tests/unittests/test_handler/test_handler_lxd.py
+++ b/tests/unittests/test_handler/test_handler_lxd.py
@@ -4,11 +4,6 @@ from cloudinit.config import cc_lxd
4from cloudinit.sources import DataSourceNoCloud4from cloudinit.sources import DataSourceNoCloud
5from cloudinit import (distros, helpers, cloud)5from cloudinit import (distros, helpers, cloud)
6from cloudinit.tests import helpers as t_help6from cloudinit.tests import helpers as t_help
7<<<<<<< tests/unittests/test_handler/test_handler_lxd.py
8=======
9
10import logging
11>>>>>>> tests/unittests/test_handler/test_handler_lxd.py
127
13try:8try:
14 from unittest import mock9 from unittest import mock
diff --git a/tests/unittests/test_runs/test_merge_run.py b/tests/unittests/test_runs/test_merge_run.py
index add9365..5d3f1ca 100644
--- a/tests/unittests/test_runs/test_merge_run.py
+++ b/tests/unittests/test_runs/test_merge_run.py
@@ -23,6 +23,7 @@ class TestMergeRun(helpers.FilesystemMockingTestCase):
23 cfg = {23 cfg = {
24 'datasource_list': ['None'],24 'datasource_list': ['None'],
25 'cloud_init_modules': ['write-files'],25 'cloud_init_modules': ['write-files'],
26 'system_info': {'paths': {'run_dir': new_root}}
26 }27 }
27 ud = self.readResource('user_data.1.txt')28 ud = self.readResource('user_data.1.txt')
28 cloud_cfg = util.yaml_dumps(cfg)29 cloud_cfg = util.yaml_dumps(cfg)
diff --git a/tests/unittests/test_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py
index b8fb479..762974e 100644
--- a/tests/unittests/test_runs/test_simple_run.py
+++ b/tests/unittests/test_runs/test_simple_run.py
@@ -2,10 +2,10 @@
22
3import os3import os
44
5from cloudinit.tests import helpers
65
7from cloudinit.settings import PER_INSTANCE6from cloudinit.settings import PER_INSTANCE
8from cloudinit import stages7from cloudinit import stages
8from cloudinit.tests import helpers
9from cloudinit import util9from cloudinit import util
1010
1111
@@ -23,6 +23,7 @@ class TestSimpleRun(helpers.FilesystemMockingTestCase):
23 'datasource_list': ['None'],23 'datasource_list': ['None'],
24 'runcmd': ['ls /etc'], # test ALL_DISTROS24 'runcmd': ['ls /etc'], # test ALL_DISTROS
25 'spacewalk': {}, # test non-ubuntu distros module definition25 'spacewalk': {}, # test non-ubuntu distros module definition
26 'system_info': {'paths': {'run_dir': self.new_root}},
26 'write_files': [27 'write_files': [
27 {28 {
28 'path': '/etc/blah.ini',29 'path': '/etc/blah.ini',
diff --git a/tests/unittests/test_vmware_config_file.py b/tests/unittests/test_vmware_config_file.py
index 306ee15..808d303 100644
--- a/tests/unittests/test_vmware_config_file.py
+++ b/tests/unittests/test_vmware_config_file.py
@@ -8,7 +8,6 @@
8import logging8import logging
9import sys9import sys
1010
11<<<<<<< tests/unittests/test_vmware_config_file.py
12from cloudinit.sources.DataSourceOVF import get_network_config_from_conf11from cloudinit.sources.DataSourceOVF import get_network_config_from_conf
13from cloudinit.sources.DataSourceOVF import read_vmware_imc12from cloudinit.sources.DataSourceOVF import read_vmware_imc
14from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProtoEnum13from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProtoEnum
@@ -16,11 +15,6 @@ from cloudinit.sources.helpers.vmware.imc.config import Config
16from cloudinit.sources.helpers.vmware.imc.config_file import ConfigFile15from cloudinit.sources.helpers.vmware.imc.config_file import ConfigFile
17from cloudinit.sources.helpers.vmware.imc.config_nic import gen_subnet16from cloudinit.sources.helpers.vmware.imc.config_nic import gen_subnet
18from cloudinit.sources.helpers.vmware.imc.config_nic import NicConfigurator17from cloudinit.sources.helpers.vmware.imc.config_nic import NicConfigurator
19=======
20from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProtoEnum
21from cloudinit.sources.helpers.vmware.imc.config import Config
22from cloudinit.sources.helpers.vmware.imc.config_file import ConfigFile
23>>>>>>> tests/unittests/test_vmware_config_file.py
24from cloudinit.tests.helpers import CiTestCase18from cloudinit.tests.helpers import CiTestCase
2519
26logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)20logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)

Subscribers

People subscribed via source and target branches