Merge ~paul-meyer/cloud-init:fix-ntfs-mount into cloud-init:master

Proposed by Paul Meyer
Status: Merged
Approved by: Scott Moser
Approved revision: c4e82b27c2149c000633554c231274bcd3c6e1eb
Merge reported by: Scott Moser
Merged at revision: aa4eeb80839382117e1813e396dc53aa634fd7ba
Proposed branch: ~paul-meyer/cloud-init:fix-ntfs-mount
Merge into: cloud-init:master
Diff against target: 379 lines (+138/-35)
3 files modified
cloudinit/sources/DataSourceAzure.py (+47/-16)
cloudinit/util.py (+3/-2)
tests/unittests/test_datasource/test_azure.py (+88/-17)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Ryan Harper Pending
Review via email: mp+344538@code.launchpad.net

Commit message

Azure: Ignore NTFS mount errors when checking ephemeral drive

The Azure data source provides a method to check whether a NTFS partition
on the ephemeral disk is safe for reformatting to ext4. The method checks
to see if there are customer data files on the disk. However, mounting
the partition fails on systems that do not have the capability of
mounting NTFS. Note that in this case, it is also very unlikely that the
NTFS partition would have been used by the system (since it can't mount
it). The only case would be where an update to the system removed the
capability to mount NTFS, the likelihood of which is also very small.
This change allows the reformatting of the ephemeral disk to ext4 on
systems where mounting NTFS is not supported.

Description of the change

The change converts a specific mount error to success in the method cloudinit.sources.DataSourceAzure.can_dev_be_reformatted.

Rationale:
The Azure data source provides a method to check whether a NTFS partition no the ephemeral disk is safe for reformatting to ext4. The method checks to see if there are customer data files on the disk. However, mounting the partition fails on systems that do not have the capability of mounting NTFS. Note that in this case, it is also very unlikely that the NTFS partition would have been used by the system (since is can't mount it). The only case would be where an update to the system removed the capability to mount NTFS, the likelihood of which is also very small.
This change allows the reformatting of the ephemeral disk to ext4 on systems where mounting NTFS is not supported.

Testing:
1. On Azure, boot a VM from image RedHat:RHEL:7-RAW-CI:latest. Verify that /dev/sdb1 is formatted ext4.
2. Update cloud-init (sudo yum update cloud-init)
3. Choose redeploy from the portal (or deallocate and restart VM).
Expected: /dev/sdb1 is formatted ext4
Actual: /dev/sdb1 is formatted ntfs

When this patch is applied between step 2 and 3, the expected result is achieved.

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:7c44b975ee546df18910b620f920d673046c4080
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1063/
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/1063/rebuild

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

Just adding the comments we had during IRC discussion:

I'd prefer if we detected that we can't mount ntfs by checking for

a) is there an ntfs kernel module
b) is there a mount.ntfs tool in $PATH

vs. ignoring the error on certain distros;

The general worry is that the NTFS partition could have user-data on it; though I understand that for some distros which don't support NTFS that data wouldn't have come from instances running those distros.

It was also mentioned that images which don't support ntfs, could embed cloud-config, like:

"support_ntfs: false"

That could also be metadata that the AzureDatasource uses to determine if the mount failure is fatal or not.

0f62c80... by Paul Meyer

Add _can_mount_ntfs

f7adfcb... by Paul Meyer

Only allow NTFS errors to be ignored if cloudinit agrees NTFS is not mountable

Revision history for this message
Paul Meyer (paul-meyer) wrote :

> I'd prefer if we detected that we can't mount ntfs by checking for
>
> a) is there an ntfs kernel module
> b) is there a mount.ntfs tool in $PATH
These checks are implemented now.

> It was also mentioned that images which don't support ntfs, could embed cloud-
> config, like:
>
> "support_ntfs: false"
>
> That could also be metadata that the AzureDataSource uses to determine if the
> mount failure is fatal or not.
We don't need this right now, so I left it out.

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

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

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

review: Needs Fixing (continuous-integration)
dfd94a6... by Paul Meyer

fixing up tests

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

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

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

I think we can narrow down the check to just: 1) is the module loaded 2) do we have a mount.ntfs

I'll ask smoser to comment w.r.t the case where we have mount.ntfs (and module is present but not loaded, the modinfo case returns true) if that's something we care about; unless mount.ntfs will attempt to modprobe the ntfs module then cloud-init isn't currently modprobing that before calling mount.

Revision history for this message
Paul Meyer (paul-meyer) wrote :

> unless mount.ntfs will attempt to modprobe the ntfs module then
> cloud-init isn't currently modprobing that before calling mount.

My experience is that if a mount helper needs a module it will modprobe it. However, I can't point to any doc that says that is the contract. I'll wait for you or smoser to respond.

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

I kind of suspect that mount.ntfs would do a modprobe. That seems quite reasonable to me.

that said... There is a ton of happenstance here. I realize that most likely if you have an ntfs filesystem on azure and you can't mount it, then you probably didnt have any data there.

However, there is a failure case. However unlikely..
a.) boot into ubuntu with ntfs support, put a ton of important data on a disk that warns clearly that it is not to be used for important data.
b.) install a custom kernel
c.) reboot
d.) cloud-init can't mount ntfs, so it assumes it should create a new filesystem there.
e.) user screams, and angry tweets about how irresponsible cloud-init developers are, especially @smoser0.

yes... its probably very unlikely.

Better would be if we had information exposed to us from the platform (Azure) that said definitively "we wiped that disk... its empty now".

Without access to that fact, which is known to the platform, we're just doing a bunch of heuristics.

I'm almost more apt to just say that on azure, cloud-init will *always* feel free to destroy an ntfs partition. At least then, we've fixed the problem for the future... no one will ever have an ntfs filesystem there that they put their data on. We can make a config option saying "never-destroy-ntfs" for those people that for some reason did that intentionally, or we can just tell them that there might be better choices for a filesystem.

21ef0d7... by Paul Meyer

Review comments - change logic to use config switch

Revision history for this message
Paul Meyer (paul-meyer) wrote :

After discussion on IRC, made it so that

a.) if we can mount it, then check for data... don't reformat it if it has files
b.) if we cannot mount it... then check a config variable, if that variable says not to delete, then don't.

If we are able to mount and see that there is data on the disk (in NTFS fs), we'll emit a warning to guide the user to set the config setting.

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

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

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

some feedback in line.

c4e82b2... by Paul Meyer

Review comments

Revision history for this message
Paul Meyer (paul-meyer) wrote :

Updated. I wasn't sure how to set the LANG env variable for the mount command, but I hope this is acceptable.

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

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

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

An upstream commit landed for this bug.

To view that commit see the following URL:
https://git.launchpad.net/cloud-init/commit/?id=aa4eeb80

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
2index 1b03d46..7007d9e 100644
3--- a/cloudinit/sources/DataSourceAzure.py
4+++ b/cloudinit/sources/DataSourceAzure.py
5@@ -208,6 +208,7 @@ BUILTIN_CLOUD_CONFIG = {
6 }
7
8 DS_CFG_PATH = ['datasource', DS_NAME]
9+DS_CFG_KEY_PRESERVE_NTFS = 'never_destroy_ntfs'
10 DEF_EPHEMERAL_LABEL = 'Temporary Storage'
11
12 # The redacted password fails to meet password complexity requirements
13@@ -394,14 +395,9 @@ class DataSourceAzure(sources.DataSource):
14 if found == ddir:
15 LOG.debug("using files cached in %s", ddir)
16
17- # azure / hyper-v provides random data here
18- # TODO. find the seed on FreeBSD platform
19- # now update ds_cfg to reflect contents pass in config
20- if not util.is_FreeBSD():
21- seed = util.load_file("/sys/firmware/acpi/tables/OEM0",
22- quiet=True, decode=False)
23- if seed:
24- self.metadata['random_seed'] = seed
25+ seed = _get_random_seed()
26+ if seed:
27+ self.metadata['random_seed'] = seed
28
29 user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
30 self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
31@@ -539,7 +535,9 @@ class DataSourceAzure(sources.DataSource):
32 return fabric_data
33
34 def activate(self, cfg, is_new_instance):
35- address_ephemeral_resize(is_new_instance=is_new_instance)
36+ address_ephemeral_resize(is_new_instance=is_new_instance,
37+ preserve_ntfs=self.ds_cfg.get(
38+ DS_CFG_KEY_PRESERVE_NTFS, False))
39 return
40
41 @property
42@@ -583,17 +581,29 @@ def _has_ntfs_filesystem(devpath):
43 return os.path.realpath(devpath) in ntfs_devices
44
45
46-def can_dev_be_reformatted(devpath):
47- """Determine if block device devpath is newly formatted ephemeral.
48+def can_dev_be_reformatted(devpath, preserve_ntfs):
49+ """Determine if the ephemeral drive at devpath should be reformatted.
50
51- A newly formatted disk will:
52+ A fresh ephemeral disk is formatted by Azure and will:
53 a.) have a partition table (dos or gpt)
54 b.) have 1 partition that is ntfs formatted, or
55 have 2 partitions with the second partition ntfs formatted.
56 (larger instances with >2TB ephemeral disk have gpt, and will
57 have a microsoft reserved partition as part 1. LP: #1686514)
58 c.) the ntfs partition will have no files other than possibly
59- 'dataloss_warning_readme.txt'"""
60+ 'dataloss_warning_readme.txt'
61+
62+ User can indicate that NTFS should never be destroyed by setting
63+ DS_CFG_KEY_PRESERVE_NTFS in dscfg.
64+ If data is found on NTFS, user is warned to set DS_CFG_KEY_PRESERVE_NTFS
65+ to make sure cloud-init does not accidentally wipe their data.
66+ If cloud-init cannot mount the disk to check for data, destruction
67+ will be allowed, unless the dscfg key is set."""
68+ if preserve_ntfs:
69+ msg = ('config says to never destroy NTFS (%s.%s), skipping checks' %
70+ (".".join(DS_CFG_PATH), DS_CFG_KEY_PRESERVE_NTFS))
71+ return False, msg
72+
73 if not os.path.exists(devpath):
74 return False, 'device %s does not exist' % devpath
75
76@@ -626,18 +636,27 @@ def can_dev_be_reformatted(devpath):
77 bmsg = ('partition %s (%s) on device %s was ntfs formatted' %
78 (cand_part, cand_path, devpath))
79 try:
80- file_count = util.mount_cb(cand_path, count_files)
81+ file_count = util.mount_cb(cand_path, count_files, mtype="ntfs",
82+ update_env_for_mount={'LANG': 'C'})
83 except util.MountFailedError as e:
84+ if "mount: unknown filesystem type 'ntfs'" in str(e):
85+ return True, (bmsg + ' but this system cannot mount NTFS,'
86+ ' assuming there are no important files.'
87+ ' Formatting allowed.')
88 return False, bmsg + ' but mount of %s failed: %s' % (cand_part, e)
89
90 if file_count != 0:
91+ LOG.warning("it looks like you're using NTFS on the ephemeral disk, "
92+ 'to ensure that filesystem does not get wiped, set '
93+ '%s.%s in config', '.'.join(DS_CFG_PATH),
94+ DS_CFG_KEY_PRESERVE_NTFS)
95 return False, bmsg + ' but had %d files on it.' % file_count
96
97 return True, bmsg + ' and had no important files. Safe for reformatting.'
98
99
100 def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120,
101- is_new_instance=False):
102+ is_new_instance=False, preserve_ntfs=False):
103 # wait for ephemeral disk to come up
104 naplen = .2
105 missing = util.wait_for_files([devpath], maxwait=maxwait, naplen=naplen,
106@@ -653,7 +672,7 @@ def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120,
107 if is_new_instance:
108 result, msg = (True, "First instance boot.")
109 else:
110- result, msg = can_dev_be_reformatted(devpath)
111+ result, msg = can_dev_be_reformatted(devpath, preserve_ntfs)
112
113 LOG.debug("reformattable=%s: %s", result, msg)
114 if not result:
115@@ -967,6 +986,18 @@ def _check_freebsd_cdrom(cdrom_dev):
116 return False
117
118
119+def _get_random_seed():
120+ """Return content random seed file if available, otherwise,
121+ return None."""
122+ # azure / hyper-v provides random data here
123+ # TODO. find the seed on FreeBSD platform
124+ # now update ds_cfg to reflect contents pass in config
125+ if util.is_FreeBSD():
126+ return None
127+ return util.load_file("/sys/firmware/acpi/tables/OEM0",
128+ quiet=True, decode=False)
129+
130+
131 def list_possible_azure_ds_devs():
132 devlist = []
133 if util.is_FreeBSD():
134diff --git a/cloudinit/util.py b/cloudinit/util.py
135index edfedc7..653ed6e 100644
136--- a/cloudinit/util.py
137+++ b/cloudinit/util.py
138@@ -1581,7 +1581,8 @@ def mounts():
139 return mounted
140
141
142-def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True):
143+def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True,
144+ update_env_for_mount=None):
145 """
146 Mount the device, call method 'callback' passing the directory
147 in which it was mounted, then unmount. Return whatever 'callback'
148@@ -1643,7 +1644,7 @@ def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True):
149 mountcmd.extend(['-t', mtype])
150 mountcmd.append(device)
151 mountcmd.append(tmpd)
152- subp(mountcmd)
153+ subp(mountcmd, update_env=update_env_for_mount)
154 umount = tmpd # This forces it to be unmounted (when set)
155 mountpoint = tmpd
156 break
157diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
158index 26e8d7d..e82716e 100644
159--- a/tests/unittests/test_datasource/test_azure.py
160+++ b/tests/unittests/test_datasource/test_azure.py
161@@ -1,10 +1,10 @@
162 # This file is part of cloud-init. See LICENSE file for license information.
163
164 from cloudinit import helpers
165-from cloudinit.util import b64e, decode_binary, load_file, write_file
166 from cloudinit.sources import DataSourceAzure as dsaz
167-from cloudinit.util import find_freebsd_part
168-from cloudinit.util import get_path_dev_freebsd
169+from cloudinit.util import (b64e, decode_binary, load_file, write_file,
170+ find_freebsd_part, get_path_dev_freebsd,
171+ MountFailedError)
172 from cloudinit.version import version_string as vs
173 from cloudinit.tests.helpers import (CiTestCase, TestCase, populate_dir, mock,
174 ExitStack, PY26, SkipTest)
175@@ -95,6 +95,8 @@ class TestAzureDataSource(CiTestCase):
176 self.patches = ExitStack()
177 self.addCleanup(self.patches.close)
178
179+ self.patches.enter_context(mock.patch.object(dsaz, '_get_random_seed'))
180+
181 super(TestAzureDataSource, self).setUp()
182
183 def apply_patches(self, patches):
184@@ -335,6 +337,18 @@ fdescfs /dev/fd fdescfs rw 0 0
185 self.assertTrue(ret)
186 self.assertEqual(data['agent_invoked'], '_COMMAND')
187
188+ def test_sys_cfg_set_never_destroy_ntfs(self):
189+ sys_cfg = {'datasource': {'Azure': {
190+ 'never_destroy_ntfs': 'user-supplied-value'}}}
191+ data = {'ovfcontent': construct_valid_ovf_env(data={}),
192+ 'sys_cfg': sys_cfg}
193+
194+ dsrc = self._get_ds(data)
195+ ret = self._get_and_setup(dsrc)
196+ self.assertTrue(ret)
197+ self.assertEqual(dsrc.ds_cfg.get(dsaz.DS_CFG_KEY_PRESERVE_NTFS),
198+ 'user-supplied-value')
199+
200 def test_username_used(self):
201 odata = {'HostName': "myhost", 'UserName': "myuser"}
202 data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
203@@ -676,6 +690,8 @@ class TestAzureBounce(CiTestCase):
204 mock.MagicMock(return_value={})))
205 self.patches.enter_context(
206 mock.patch.object(dsaz.util, 'which', lambda x: True))
207+ self.patches.enter_context(
208+ mock.patch.object(dsaz, '_get_random_seed'))
209
210 def _dmi_mocks(key):
211 if key == 'system-uuid':
212@@ -957,7 +973,9 @@ class TestCanDevBeReformatted(CiTestCase):
213 # return sorted by partition number
214 return sorted(ret, key=lambda d: d[0])
215
216- def mount_cb(device, callback):
217+ def mount_cb(device, callback, mtype, update_env_for_mount):
218+ self.assertEqual('ntfs', mtype)
219+ self.assertEqual('C', update_env_for_mount.get('LANG'))
220 p = self.tmp_dir()
221 for f in bypath.get(device).get('files', []):
222 write_file(os.path.join(p, f), content=f)
223@@ -988,14 +1006,16 @@ class TestCanDevBeReformatted(CiTestCase):
224 '/dev/sda2': {'num': 2},
225 '/dev/sda3': {'num': 3},
226 }}})
227- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
228+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
229+ preserve_ntfs=False)
230 self.assertFalse(value)
231 self.assertIn("3 or more", msg.lower())
232
233 def test_no_partitions_is_false(self):
234 """A disk with no partitions can not be formatted."""
235 self.patchup({'/dev/sda': {}})
236- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
237+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
238+ preserve_ntfs=False)
239 self.assertFalse(value)
240 self.assertIn("not partitioned", msg.lower())
241
242@@ -1007,7 +1027,8 @@ class TestCanDevBeReformatted(CiTestCase):
243 '/dev/sda1': {'num': 1},
244 '/dev/sda2': {'num': 2, 'fs': 'ext4', 'files': []},
245 }}})
246- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
247+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
248+ preserve_ntfs=False)
249 self.assertFalse(value)
250 self.assertIn("not ntfs", msg.lower())
251
252@@ -1020,7 +1041,8 @@ class TestCanDevBeReformatted(CiTestCase):
253 '/dev/sda2': {'num': 2, 'fs': 'ntfs',
254 'files': ['secret.txt']},
255 }}})
256- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
257+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
258+ preserve_ntfs=False)
259 self.assertFalse(value)
260 self.assertIn("files on it", msg.lower())
261
262@@ -1032,7 +1054,8 @@ class TestCanDevBeReformatted(CiTestCase):
263 '/dev/sda1': {'num': 1},
264 '/dev/sda2': {'num': 2, 'fs': 'ntfs', 'files': []},
265 }}})
266- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
267+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
268+ preserve_ntfs=False)
269 self.assertTrue(value)
270 self.assertIn("safe for", msg.lower())
271
272@@ -1043,7 +1066,8 @@ class TestCanDevBeReformatted(CiTestCase):
273 'partitions': {
274 '/dev/sda1': {'num': 1, 'fs': 'zfs'},
275 }}})
276- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
277+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
278+ preserve_ntfs=False)
279 self.assertFalse(value)
280 self.assertIn("not ntfs", msg.lower())
281
282@@ -1055,9 +1079,14 @@ class TestCanDevBeReformatted(CiTestCase):
283 '/dev/sda1': {'num': 1, 'fs': 'ntfs',
284 'files': ['file1.txt', 'file2.exe']},
285 }}})
286- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
287- self.assertFalse(value)
288- self.assertIn("files on it", msg.lower())
289+ with mock.patch.object(dsaz.LOG, 'warning') as warning:
290+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
291+ preserve_ntfs=False)
292+ wmsg = warning.call_args[0][0]
293+ self.assertIn("looks like you're using NTFS on the ephemeral disk",
294+ wmsg)
295+ self.assertFalse(value)
296+ self.assertIn("files on it", msg.lower())
297
298 def test_one_partition_ntfs_empty_is_true(self):
299 """1 mountable ntfs partition and no files can be formatted."""
300@@ -1066,7 +1095,8 @@ class TestCanDevBeReformatted(CiTestCase):
301 'partitions': {
302 '/dev/sda1': {'num': 1, 'fs': 'ntfs', 'files': []}
303 }}})
304- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
305+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
306+ preserve_ntfs=False)
307 self.assertTrue(value)
308 self.assertIn("safe for", msg.lower())
309
310@@ -1078,7 +1108,8 @@ class TestCanDevBeReformatted(CiTestCase):
311 '/dev/sda1': {'num': 1, 'fs': 'ntfs',
312 'files': ['dataloss_warning_readme.txt']}
313 }}})
314- value, msg = dsaz.can_dev_be_reformatted("/dev/sda")
315+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
316+ preserve_ntfs=False)
317 self.assertTrue(value)
318 self.assertIn("safe for", msg.lower())
319
320@@ -1093,7 +1124,8 @@ class TestCanDevBeReformatted(CiTestCase):
321 'num': 1, 'fs': 'ntfs', 'files': [self.warning_file],
322 'realpath': '/dev/sdb1'}
323 }}})
324- value, msg = dsaz.can_dev_be_reformatted(epath)
325+ value, msg = dsaz.can_dev_be_reformatted(epath,
326+ preserve_ntfs=False)
327 self.assertTrue(value)
328 self.assertIn("safe for", msg.lower())
329
330@@ -1112,10 +1144,49 @@ class TestCanDevBeReformatted(CiTestCase):
331 epath + '-part3': {'num': 3, 'fs': 'ext',
332 'realpath': '/dev/sdb3'}
333 }}})
334- value, msg = dsaz.can_dev_be_reformatted(epath)
335+ value, msg = dsaz.can_dev_be_reformatted(epath,
336+ preserve_ntfs=False)
337 self.assertFalse(value)
338 self.assertIn("3 or more", msg.lower())
339
340+ def test_ntfs_mount_errors_true(self):
341+ """can_dev_be_reformatted does not fail if NTFS is unknown fstype."""
342+ self.patchup({
343+ '/dev/sda': {
344+ 'partitions': {
345+ '/dev/sda1': {'num': 1, 'fs': 'ntfs', 'files': []}
346+ }}})
347+
348+ err = ("Unexpected error while running command.\n",
349+ "Command: ['mount', '-o', 'ro,sync', '-t', 'auto', ",
350+ "'/dev/sda1', '/fake-tmp/dir']\n"
351+ "Exit code: 32\n"
352+ "Reason: -\n"
353+ "Stdout: -\n"
354+ "Stderr: mount: unknown filesystem type 'ntfs'")
355+ self.m_mount_cb.side_effect = MountFailedError(
356+ 'Failed mounting %s to %s due to: %s' %
357+ ('/dev/sda', '/fake-tmp/dir', err))
358+
359+ value, msg = dsaz.can_dev_be_reformatted('/dev/sda',
360+ preserve_ntfs=False)
361+ self.assertTrue(value)
362+ self.assertIn('cannot mount NTFS, assuming', msg)
363+
364+ def test_never_destroy_ntfs_config_false(self):
365+ """Normally formattable situation with never_destroy_ntfs set."""
366+ self.patchup({
367+ '/dev/sda': {
368+ 'partitions': {
369+ '/dev/sda1': {'num': 1, 'fs': 'ntfs',
370+ 'files': ['dataloss_warning_readme.txt']}
371+ }}})
372+ value, msg = dsaz.can_dev_be_reformatted("/dev/sda",
373+ preserve_ntfs=True)
374+ self.assertFalse(value)
375+ self.assertIn("config says to never destroy NTFS "
376+ "(datasource.Azure.never_destroy_ntfs)", msg)
377+
378
379 class TestAzureNetExists(CiTestCase):
380

Subscribers

People subscribed via source and target branches