summaryrefslogtreecommitdiff
path: root/azurelinuxagent/daemon/resourcedisk/default.py
diff options
context:
space:
mode:
Diffstat (limited to 'azurelinuxagent/daemon/resourcedisk/default.py')
-rw-r--r--azurelinuxagent/daemon/resourcedisk/default.py219
1 files changed, 219 insertions, 0 deletions
diff --git a/azurelinuxagent/daemon/resourcedisk/default.py b/azurelinuxagent/daemon/resourcedisk/default.py
new file mode 100644
index 0000000..d2e400a
--- /dev/null
+++ b/azurelinuxagent/daemon/resourcedisk/default.py
@@ -0,0 +1,219 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import sys
+import threading
+import azurelinuxagent.common.logger as logger
+from azurelinuxagent.common.future import ustr
+import azurelinuxagent.common.conf as conf
+from azurelinuxagent.common.event import add_event, WALAEventOperation
+import azurelinuxagent.common.utils.fileutil as fileutil
+import azurelinuxagent.common.utils.shellutil as shellutil
+from azurelinuxagent.common.exception import ResourceDiskError
+from azurelinuxagent.common.osutil import get_osutil
+
+DATALOSS_WARNING_FILE_NAME="DATALOSS_WARNING_README.txt"
+DATA_LOSS_WARNING="""\
+WARNING: THIS IS A TEMPORARY DISK.
+
+Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
+
+Please do not use this disk for storing any personal or application data.
+
+For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
+"""
+
+class ResourceDiskHandler(object):
+ def __init__(self):
+ self.osutil = get_osutil()
+
+ def start_activate_resource_disk(self):
+ disk_thread = threading.Thread(target = self.run)
+ disk_thread.start()
+
+ def run(self):
+ mount_point = None
+ if conf.get_resourcedisk_format():
+ mount_point = self.activate_resource_disk()
+ if mount_point is not None and \
+ conf.get_resourcedisk_enable_swap():
+ self.enable_swap(mount_point)
+
+ def activate_resource_disk(self):
+ logger.info("Activate resource disk")
+ try:
+ mount_point = conf.get_resourcedisk_mountpoint()
+ fs = conf.get_resourcedisk_filesystem()
+ mount_point = self.mount_resource_disk(mount_point, fs)
+ warning_file = os.path.join(mount_point, DATALOSS_WARNING_FILE_NAME)
+ try:
+ fileutil.write_file(warning_file, DATA_LOSS_WARNING)
+ except IOError as e:
+ logger.warn("Failed to write data loss warnning:{0}", e)
+ return mount_point
+ except ResourceDiskError as e:
+ logger.error("Failed to mount resource disk {0}", e)
+ add_event(name="WALA", is_success=False, message=ustr(e),
+ op=WALAEventOperation.ActivateResourceDisk)
+
+ def enable_swap(self, mount_point):
+ logger.info("Enable swap")
+ try:
+ size_mb = conf.get_resourcedisk_swap_size_mb()
+ self.create_swap_space(mount_point, size_mb)
+ except ResourceDiskError as e:
+ logger.error("Failed to enable swap {0}", e)
+
+ def mount_resource_disk(self, mount_point, fs):
+ device = self.osutil.device_for_ide_port(1)
+ if device is None:
+ raise ResourceDiskError("unable to detect disk topology")
+
+ device = "/dev/" + device
+ mountlist = shellutil.run_get_output("mount")[1]
+ existing = self.osutil.get_mount_point(mountlist, device)
+
+ if(existing):
+ logger.info("Resource disk {0}1 is already mounted", device)
+ return existing
+
+ fileutil.mkdir(mount_point, mode=0o755)
+
+ logger.info("Detect GPT...")
+ partition = device + "1"
+ ret = shellutil.run_get_output("parted {0} print".format(device))
+ if ret[0]:
+ raise ResourceDiskError("({0}) {1}".format(device, ret[1]))
+
+ if "gpt" in ret[1]:
+ logger.info("GPT detected")
+ logger.info("Get GPT partitions")
+ parts = [x for x in ret[1].split("\n") if re.match("^\s*[0-9]+", x)]
+ logger.info("Found more than {0} GPT partitions.", len(parts))
+ if len(parts) > 1:
+ logger.info("Remove old GPT partitions")
+ for i in range(1, len(parts) + 1):
+ logger.info("Remove partition: {0}", i)
+ shellutil.run("parted {0} rm {1}".format(device, i))
+
+ logger.info("Create a new GPT partition using entire disk space")
+ shellutil.run("parted {0} mkpart primary 0% 100%".format(device))
+
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ else:
+ logger.info("GPT not detected")
+ logger.info("Check fstype")
+ ret = shellutil.run_get_output("sfdisk -q -c {0} 1".format(device))
+ if ret[1].rstrip() == "7" and fs != "ntfs":
+ logger.info("The partition is formatted with ntfs")
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("sfdisk -c {0} 1 83".format(device))
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+
+ logger.info("Mount resource disk")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point),
+ chk_err=False)
+ if ret:
+ logger.warn("Failed to mount resource disk. Retry mounting")
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point))
+ if ret:
+ raise ResourceDiskError("({0}) {1}".format(partition, ret))
+
+ logger.info("Resource disk ({0}) is mounted at {1} with fstype {2}",
+ device, mount_point, fs)
+ return mount_point
+
+ def create_swap_space(self, mount_point, size_mb):
+ size_kb = size_mb * 1024
+ size = size_kb * 1024
+ swapfile = os.path.join(mount_point, 'swapfile')
+ swaplist = shellutil.run_get_output("swapon -s")[1]
+
+ if swapfile in swaplist and os.path.getsize(swapfile) == size:
+ logger.info("Swap already enabled")
+ return
+
+ if os.path.isfile(swapfile) and os.path.getsize(swapfile) != size:
+ logger.info("Remove old swap file")
+ shellutil.run("swapoff -a", chk_err=False)
+ os.remove(swapfile)
+
+ if not os.path.isfile(swapfile):
+ logger.info("Create swap file")
+ self.mkfile(swapfile, size_kb * 1024)
+ shellutil.run("mkswap {0}".format(swapfile))
+ if shellutil.run("swapon {0}".format(swapfile)):
+ raise ResourceDiskError("{0}".format(swapfile))
+ logger.info("Enabled {0}KB of swap at {1}".format(size_kb, swapfile))
+
+ def mkfile(self, filename, nbytes):
+ """
+ Create a non-sparse file of that size. Deletes and replaces existing file.
+
+ To allow efficient execution, fallocate will be tried first. This includes
+ ``os.posix_fallocate`` on Python 3.3+ (unix) and the ``fallocate`` command
+ in the popular ``util-linux{,-ng}`` package.
+
+ A dd fallback will be tried too. When size < 64M, perform single-pass dd.
+ Otherwise do two-pass dd.
+ """
+
+ if not isinstance(nbytes, int):
+ nbytes = int(nbytes)
+
+ if nbytes < 0:
+ raise ValueError(nbytes)
+
+ if os.path.isfile(filename):
+ os.remove(filename)
+
+ # os.posix_fallocate
+ if sys.version_info >= (3, 3):
+ # Probable errors:
+ # - OSError: Seen on Cygwin, libc notimpl?
+ # - AttributeError: What if someone runs this under...
+ with open(filename, 'w') as f:
+ try:
+ os.posix_fallocate(f.fileno(), 0, nbytes)
+ return 0
+ except:
+ # Not confident with this thing, just keep trying...
+ pass
+
+ # fallocate command
+ fn_sh = shellutil.quote((filename,))
+ ret = shellutil.run(u"fallocate -l {0} {1}".format(nbytes, fn_sh))
+ if ret != 127: # 127 = command not found
+ return ret
+
+ # dd fallback
+ dd_maxbs = 64 * 1024 ** 2
+ dd_cmd = "dd if=/dev/zero bs={0} count={1} conv=notrunc of={2}"
+
+ blocks = int(nbytes / dd_maxbs)
+ if blocks > 0:
+ ret = shellutil.run(dd_cmd.format(dd_maxbs, fn_sh, blocks)) << 8
+
+ remains = int(nbytes % dd_maxbs)
+ if remains > 0:
+ ret += shellutil.run(dd_cmd.format(remains, fn_sh, 1))
+
+ return ret