summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/config/cc_resizefs.py95
-rwxr-xr-xcloudinit/distros/__init__.py3
-rw-r--r--cloudinit/distros/freebsd.py277
-rw-r--r--cloudinit/settings.py2
-rw-r--r--cloudinit/sources/DataSourceAzure.py180
-rw-r--r--cloudinit/sources/helpers/azure.py11
-rw-r--r--cloudinit/stages.py2
-rw-r--r--cloudinit/util.py52
-rw-r--r--config/cloud.cfg-freebsd2
-rwxr-xr-xsetup.py11
-rwxr-xr-xsysvinit/freebsd/cloudconfig10
-rwxr-xr-xsysvinit/freebsd/cloudfinal10
-rwxr-xr-xsysvinit/freebsd/cloudinit10
-rwxr-xr-xsysvinit/freebsd/cloudinitlocal12
-rw-r--r--tests/unittests/test_datasource/test_azure.py65
-rw-r--r--tests/unittests/test_datasource/test_azure_helper.py4
-rw-r--r--tests/unittests/test_datasource/test_cloudstack.py5
-rw-r--r--tests/unittests/test_distros/test_netconfig.py47
-rw-r--r--tests/unittests/test_handler/test_handler_resizefs.py59
-rw-r--r--tests/unittests/test_net.py6
-rw-r--r--tests/unittests/test_util.py3
-rwxr-xr-xtools/build-on-freebsd4
22 files changed, 783 insertions, 87 deletions
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index 60e3ab53..ceee952b 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -33,7 +33,10 @@ disabled altogether by setting ``resize_rootfs`` to ``false``.
"""
import errno
+import getopt
import os
+import re
+import shlex
import stat
from cloudinit.settings import PER_ALWAYS
@@ -58,6 +61,62 @@ def _resize_ufs(mount_point, devpth):
return ('growfs', devpth)
+def _get_dumpfs_output(mount_point):
+ dumpfs_res, err = util.subp(['dumpfs', '-m', mount_point])
+ return dumpfs_res
+
+
+def _get_gpart_output(part):
+ gpart_res, err = util.subp(['gpart', 'show', part])
+ return gpart_res
+
+
+def _can_skip_resize_ufs(mount_point, devpth):
+ # extract the current fs sector size
+ """
+ # dumpfs -m /
+ # newfs command for / (/dev/label/rootfs)
+ newfs -O 2 -U -a 4 -b 32768 -d 32768 -e 4096 -f 4096 -g 16384
+ -h 64 -i 8192 -j -k 6408 -m 8 -o time -s 58719232 /dev/label/rootf
+ """
+ cur_fs_sz = None
+ frag_sz = None
+ dumpfs_res = _get_dumpfs_output(mount_point)
+ for line in dumpfs_res.splitlines():
+ if not line.startswith('#'):
+ newfs_cmd = shlex.split(line)
+ opt_value = 'O:Ua:s:b:d:e:f:g:h:i:jk:m:o:'
+ optlist, args = getopt.getopt(newfs_cmd[1:], opt_value)
+ for o, a in optlist:
+ if o == "-s":
+ cur_fs_sz = int(a)
+ if o == "-f":
+ frag_sz = int(a)
+ # check the current partition size
+ """
+ # gpart show /dev/da0
+=> 40 62914480 da0 GPT (30G)
+ 40 1024 1 freebsd-boot (512K)
+ 1064 58719232 2 freebsd-ufs (28G)
+ 58720296 3145728 3 freebsd-swap (1.5G)
+ 61866024 1048496 - free - (512M)
+ """
+ expect_sz = None
+ m = re.search('^(/dev/.+)p([0-9])$', devpth)
+ gpart_res = _get_gpart_output(m.group(1))
+ for line in gpart_res.splitlines():
+ if re.search(r"freebsd-ufs", line):
+ fields = line.split()
+ expect_sz = int(fields[1])
+ # Normalize the gpart sector size,
+ # because the size is not exactly the same as fs size.
+ normal_expect_sz = (expect_sz - expect_sz % (frag_sz / 512))
+ if normal_expect_sz == cur_fs_sz:
+ return True
+ else:
+ return False
+
+
# Do not use a dictionary as these commands should be able to be used
# for multiple filesystem types if possible, e.g. one command for
# ext2, ext3 and ext4.
@@ -68,9 +127,40 @@ RESIZE_FS_PREFIXES_CMDS = [
('ufs', _resize_ufs),
]
+RESIZE_FS_PRECHECK_CMDS = {
+ 'ufs': _can_skip_resize_ufs
+}
+
NOBLOCK = "noblock"
+def rootdev_from_cmdline(cmdline):
+ found = None
+ for tok in cmdline.split():
+ if tok.startswith("root="):
+ found = tok[5:]
+ break
+ if found is None:
+ return None
+
+ if found.startswith("/dev/"):
+ return found
+ if found.startswith("LABEL="):
+ return "/dev/disk/by-label/" + found[len("LABEL="):]
+ if found.startswith("UUID="):
+ return "/dev/disk/by-uuid/" + found[len("UUID="):]
+
+ return "/dev/" + found
+
+
+def can_skip_resize(fs_type, resize_what, devpth):
+ fstype_lc = fs_type.lower()
+ for i, func in RESIZE_FS_PRECHECK_CMDS.items():
+ if fstype_lc.startswith(i):
+ return func(resize_what, devpth)
+ return False
+
+
def handle(name, cfg, _cloud, log, args):
if len(args) != 0:
resize_root = args[0]
@@ -139,6 +229,11 @@ def handle(name, cfg, _cloud, log, args):
return
resizer = None
+ if can_skip_resize(fs_type, resize_what, devpth):
+ log.debug("Skip resize filesystem type %s for %s",
+ fs_type, resize_what)
+ return
+
fstype_lc = fs_type.lower()
for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS:
if fstype_lc.startswith(pfix):
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 28650b88..f56c0cf7 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -155,6 +155,9 @@ class Distro(object):
ns, header=header, render_hwaddress=True)
return self.apply_network(contents, bring_up=bring_up)
+ def generate_fallback_config(self):
+ return net.generate_fallback_config()
+
def apply_network_config(self, netconfig, bring_up=False):
# apply network config netconfig
# This method is preferred to apply_network which only takes
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index 183e4452..bad112fe 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -30,6 +30,7 @@ class Distro(distros.Distro):
login_conf_fn_bak = '/etc/login.conf.orig'
resolv_conf_fn = '/etc/resolv.conf'
ci_sudoers_fn = '/usr/local/etc/sudoers.d/90-cloud-init-users'
+ default_primary_nic = 'hn0'
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
@@ -38,6 +39,8 @@ class Distro(distros.Distro):
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
self.osfamily = 'freebsd'
+ self.ipv4_pat = re.compile(r"\s+inet\s+\d+[.]\d+[.]\d+[.]\d+")
+ cfg['ssh_svcname'] = 'sshd'
# Updates a key in /etc/rc.conf.
def updatercconf(self, key, value):
@@ -183,7 +186,6 @@ class Distro(distros.Distro):
"gecos": '-c',
"primary_group": '-g',
"groups": '-G',
- "passwd": '-h',
"shell": '-s',
"inactive": '-E',
}
@@ -193,19 +195,11 @@ class Distro(distros.Distro):
"no_log_init": '--no-log-init',
}
- redact_opts = ['passwd']
-
for key, val in kwargs.items():
if (key in adduser_opts and val and
isinstance(val, six.string_types)):
adduser_cmd.extend([adduser_opts[key], val])
- # Redact certain fields from the logs
- if key in redact_opts:
- log_adduser_cmd.extend([adduser_opts[key], 'REDACTED'])
- else:
- log_adduser_cmd.extend([adduser_opts[key], val])
-
elif key in adduser_flags and val:
adduser_cmd.append(adduser_flags[key])
log_adduser_cmd.append(adduser_flags[key])
@@ -226,19 +220,21 @@ class Distro(distros.Distro):
except Exception as e:
util.logexc(LOG, "Failed to create user %s", name)
raise e
+ # Set the password if it is provided
+ # For security consideration, only hashed passwd is assumed
+ passwd_val = kwargs.get('passwd', None)
+ if passwd_val is not None:
+ self.set_passwd(name, passwd_val, hashed=True)
def set_passwd(self, user, passwd, hashed=False):
- cmd = ['pw', 'usermod', user]
-
if hashed:
- cmd.append('-H')
+ hash_opt = "-H"
else:
- cmd.append('-h')
-
- cmd.append('0')
+ hash_opt = "-h"
try:
- util.subp(cmd, passwd, logstring="chpasswd for %s" % user)
+ util.subp(['pw', 'usermod', user, hash_opt, '0'],
+ data=passwd, logstring="chpasswd for %s" % user)
except Exception as e:
util.logexc(LOG, "Failed to set password for %s", user)
raise e
@@ -271,6 +267,255 @@ class Distro(distros.Distro):
keys = set(kwargs['ssh_authorized_keys']) or []
ssh_util.setup_user_keys(keys, name, options=None)
+ @staticmethod
+ def get_ifconfig_list():
+ cmd = ['ifconfig', '-l']
+ (nics, err) = util.subp(cmd, rcs=[0, 1])
+ if len(err):
+ LOG.warning("Error running %s: %s", cmd, err)
+ return None
+ return nics
+
+ @staticmethod
+ def get_ifconfig_ifname_out(ifname):
+ cmd = ['ifconfig', ifname]
+ (if_result, err) = util.subp(cmd, rcs=[0, 1])
+ if len(err):
+ LOG.warning("Error running %s: %s", cmd, err)
+ return None
+ return if_result
+
+ @staticmethod
+ def get_ifconfig_ether():
+ cmd = ['ifconfig', '-l', 'ether']
+ (nics, err) = util.subp(cmd, rcs=[0, 1])
+ if len(err):
+ LOG.warning("Error running %s: %s", cmd, err)
+ return None
+ return nics
+
+ @staticmethod
+ def get_interface_mac(ifname):
+ if_result = Distro.get_ifconfig_ifname_out(ifname)
+ for item in if_result.splitlines():
+ if item.find('ether ') != -1:
+ mac = str(item.split()[1])
+ if mac:
+ return mac
+
+ @staticmethod
+ def get_devicelist():
+ nics = Distro.get_ifconfig_list()
+ return nics.split()
+
+ @staticmethod
+ def get_ipv6():
+ ipv6 = []
+ nics = Distro.get_devicelist()
+ for nic in nics:
+ if_result = Distro.get_ifconfig_ifname_out(nic)
+ for item in if_result.splitlines():
+ if item.find("inet6 ") != -1 and item.find("scopeid") == -1:
+ ipv6.append(nic)
+ return ipv6
+
+ def get_ipv4(self):
+ ipv4 = []
+ nics = Distro.get_devicelist()
+ for nic in nics:
+ if_result = Distro.get_ifconfig_ifname_out(nic)
+ for item in if_result.splitlines():
+ print(item)
+ if self.ipv4_pat.match(item):
+ ipv4.append(nic)
+ return ipv4
+
+ def is_up(self, ifname):
+ if_result = Distro.get_ifconfig_ifname_out(ifname)
+ pat = "^" + ifname
+ for item in if_result.splitlines():
+ if re.match(pat, item):
+ flags = item.split('<')[1].split('>')[0]
+ if flags.find("UP") != -1:
+ return True
+
+ def _get_current_rename_info(self, check_downable=True):
+ """Collect information necessary for rename_interfaces."""
+ names = Distro.get_devicelist()
+ bymac = {}
+ for n in names:
+ bymac[Distro.get_interface_mac(n)] = {
+ 'name': n, 'up': self.is_up(n), 'downable': None}
+
+ if check_downable:
+ nics_with_addresses = set()
+ ipv6 = self.get_ipv6()
+ ipv4 = self.get_ipv4()
+ for bytes_out in (ipv6, ipv4):
+ for i in ipv6:
+ nics_with_addresses.update(i)
+ for i in ipv4:
+ nics_with_addresses.update(i)
+
+ for d in bymac.values():
+ d['downable'] = (d['up'] is False or
+ d['name'] not in nics_with_addresses)
+
+ return bymac
+
+ def _rename_interfaces(self, renames):
+ if not len(renames):
+ LOG.debug("no interfaces to rename")
+ return
+
+ current_info = self._get_current_rename_info()
+
+ cur_bymac = {}
+ for mac, data in current_info.items():
+ cur = data.copy()
+ cur['mac'] = mac
+ cur_bymac[mac] = cur
+
+ def update_byname(bymac):
+ return dict((data['name'], data)
+ for data in bymac.values())
+
+ def rename(cur, new):
+ util.subp(["ifconfig", cur, "name", new], capture=True)
+
+ def down(name):
+ util.subp(["ifconfig", name, "down"], capture=True)
+
+ def up(name):
+ util.subp(["ifconfig", name, "up"], capture=True)
+
+ ops = []
+ errors = []
+ ups = []
+ cur_byname = update_byname(cur_bymac)
+ tmpname_fmt = "cirename%d"
+ tmpi = -1
+
+ for mac, new_name in renames:
+ cur = cur_bymac.get(mac, {})
+ cur_name = cur.get('name')
+ cur_ops = []
+ if cur_name == new_name:
+ # nothing to do
+ continue
+
+ if not cur_name:
+ errors.append("[nic not present] Cannot rename mac=%s to %s"
+ ", not available." % (mac, new_name))
+ continue
+
+ if cur['up']:
+ msg = "[busy] Error renaming mac=%s from %s to %s"
+ if not cur['downable']:
+ errors.append(msg % (mac, cur_name, new_name))
+ continue
+ cur['up'] = False
+ cur_ops.append(("down", mac, new_name, (cur_name,)))
+ ups.append(("up", mac, new_name, (new_name,)))
+
+ if new_name in cur_byname:
+ target = cur_byname[new_name]
+ if target['up']:
+ msg = "[busy-target] Error renaming mac=%s from %s to %s."
+ if not target['downable']:
+ errors.append(msg % (mac, cur_name, new_name))
+ continue
+ else:
+ cur_ops.append(("down", mac, new_name, (new_name,)))
+
+ tmp_name = None
+ while tmp_name is None or tmp_name in cur_byname:
+ tmpi += 1
+ tmp_name = tmpname_fmt % tmpi
+
+ cur_ops.append(("rename", mac, new_name, (new_name, tmp_name)))
+ target['name'] = tmp_name
+ cur_byname = update_byname(cur_bymac)
+ if target['up']:
+ ups.append(("up", mac, new_name, (tmp_name,)))
+
+ cur_ops.append(("rename", mac, new_name, (cur['name'], new_name)))
+ cur['name'] = new_name
+ cur_byname = update_byname(cur_bymac)
+ ops += cur_ops
+
+ opmap = {'rename': rename, 'down': down, 'up': up}
+ if len(ops) + len(ups) == 0:
+ if len(errors):
+ LOG.debug("unable to do any work for renaming of %s", renames)
+ else:
+ LOG.debug("no work necessary for renaming of %s", renames)
+ else:
+ LOG.debug("achieving renaming of %s with ops %s",
+ renames, ops + ups)
+
+ for op, mac, new_name, params in ops + ups:
+ try:
+ opmap.get(op)(*params)
+ except Exception as e:
+ errors.append(
+ "[unknown] Error performing %s%s for %s, %s: %s" %
+ (op, params, mac, new_name, e))
+ if len(errors):
+ raise Exception('\n'.join(errors))
+
+ def apply_network_config_names(self, netcfg):
+ renames = []
+ for ent in netcfg.get('config', {}):
+ if ent.get('type') != 'physical':
+ continue
+ mac = ent.get('mac_address')
+ name = ent.get('name')
+ if not mac:
+ continue
+ renames.append([mac, name])
+ return self._rename_interfaces(renames)
+
+ @classmethod
+ def generate_fallback_config(self):
+ nics = Distro.get_ifconfig_ether()
+ if nics is None:
+ LOG.debug("Fail to get network interfaces")
+ return None
+ potential_interfaces = nics.split()
+ connected = []
+ for nic in potential_interfaces:
+ pat = "^" + nic
+ if_result = Distro.get_ifconfig_ifname_out(nic)
+ for item in if_result.split("\n"):
+ if re.match(pat, item):
+ flags = item.split('<')[1].split('>')[0]
+ if flags.find("RUNNING") != -1:
+ connected.append(nic)
+ if connected:
+ potential_interfaces = connected
+ names = list(sorted(potential_interfaces))
+ default_pri_nic = Distro.default_primary_nic
+ if default_pri_nic in names:
+ names.remove(default_pri_nic)
+ names.insert(0, default_pri_nic)
+ target_name = None
+ target_mac = None
+ for name in names:
+ mac = Distro.get_interface_mac(name)
+ if mac:
+ target_name = name
+ target_mac = mac
+ break
+ if target_mac and target_name:
+ nconf = {'config': [], 'version': 1}
+ nconf['config'].append(
+ {'type': 'physical', 'name': target_name,
+ 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
+ return nconf
+ else:
+ return None
+
def _write_network(self, settings):
entries = net_util.translate_network(settings)
nameservers = []
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index dbafead5..411960d8 100644
--- a/cloudinit/settings.py
+++ b/cloudinit/settings.py
@@ -39,7 +39,7 @@ CFG_BUILTIN = {
],
'def_log_file': '/var/log/cloud-init.log',
'log_cfgs': [],
- 'syslog_fix_perms': ['syslog:adm', 'root:adm'],
+ 'syslog_fix_perms': ['syslog:adm', 'root:adm', 'root:wheel'],
'system_info': {
'paths': {
'cloud_dir': '/var/lib/cloud',
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 04358b73..5254e18a 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -10,6 +10,7 @@ import crypt
from functools import partial
import os
import os.path
+import re
import time
from xml.dom import minidom
import xml.etree.ElementTree as ET
@@ -32,19 +33,160 @@ BOUNCE_COMMAND = [
# azure systems will always have a resource disk, and 66-azure-ephemeral.rules
# ensures that it gets linked to this path.
RESOURCE_DISK_PATH = '/dev/disk/cloud/azure_resource'
+DEFAULT_PRIMARY_NIC = 'eth0'
+LEASE_FILE = '/var/lib/dhcp/dhclient.eth0.leases'
+DEFAULT_FS = 'ext4'
+
+
+def find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):
+ # extract the 'X' from dev.storvsc.X. if deviceid matches
+ """
+ dev.storvsc.1.%pnpinfo:
+ classid=32412632-86cb-44a2-9b5c-50d1417354f5
+ deviceid=00000000-0001-8899-0000-000000000000
+ """
+ for line in sysctl_out.splitlines():
+ if re.search(r"pnpinfo", line):
+ fields = line.split()
+ if len(fields) >= 3:
+ columns = fields[2].split('=')
+ if (len(columns) >= 2 and
+ columns[0] == "deviceid" and
+ columns[1].startswith(deviceid)):
+ comps = fields[0].split('.')
+ return comps[2]
+ return None
+
+
+def find_busdev_from_disk(camcontrol_out, disk_drv):
+ # find the scbusX from 'camcontrol devlist -b' output
+ # if disk_drv matches the specified disk driver, i.e. blkvsc1
+ """
+ scbus0 on ata0 bus 0
+ scbus1 on ata1 bus 0
+ scbus2 on blkvsc0 bus 0
+ scbus3 on blkvsc1 bus 0
+ scbus4 on storvsc2 bus 0
+ scbus5 on storvsc3 bus 0
+ scbus-1 on xpt0 bus 0
+ """
+ for line in camcontrol_out.splitlines():
+ if re.search(disk_drv, line):
+ items = line.split()
+ return items[0]
+ return None
+
+
+def find_dev_from_busdev(camcontrol_out, busdev):
+ # find the daX from 'camcontrol devlist' output
+ # if busdev matches the specified value, i.e. 'scbus2'
+ """
+ <Msft Virtual CD/ROM 1.0> at scbus1 target 0 lun 0 (cd0,pass0)
+ <Msft Virtual Disk 1.0> at scbus2 target 0 lun 0 (da0,pass1)
+ <Msft Virtual Disk 1.0> at scbus3 target 1 lun 0 (da1,pass2)
+ """
+ for line in camcontrol_out.splitlines():
+ if re.search(busdev, line):
+ items = line.split('(')
+ if len(items) == 2:
+ dev_pass = items[1].split(',')
+ return dev_pass[0]
+ return None
+
+
+def get_dev_storvsc_sysctl():
+ try:
+ sysctl_out, err = util.subp(['sysctl', 'dev.storvsc'])
+ except util.ProcessExecutionError:
+ LOG.debug("Fail to execute sysctl dev.storvsc")
+ return None
+ return sysctl_out
+
+
+def get_camcontrol_dev_bus():
+ try:
+ camcontrol_b_out, err = util.subp(['camcontrol', 'devlist', '-b'])
+ except util.ProcessExecutionError:
+ LOG.debug("Fail to execute camcontrol devlist -b")
+ return None
+ return camcontrol_b_out
+
+
+def get_camcontrol_dev():
+ try:
+ camcontrol_out, err = util.subp(['camcontrol', 'devlist'])
+ except util.ProcessExecutionError:
+ LOG.debug("Fail to execute camcontrol devlist")
+ return None
+ return camcontrol_out
+
+
+def get_resource_disk_on_freebsd(port_id):
+ g0 = "00000000"
+ if port_id > 1:
+ g0 = "00000001"
+ port_id = port_id - 2
+ g1 = "000" + str(port_id)
+ g0g1 = "{0}-{1}".format(g0, g1)
+ """
+ search 'X' from
+ 'dev.storvsc.X.%pnpinfo:
+ classid=32412632-86cb-44a2-9b5c-50d1417354f5
+ deviceid=00000000-0001-8899-0000-000000000000'
+ """
+ sysctl_out = get_dev_storvsc_sysctl()
+
+ storvscid = find_storvscid_from_sysctl_pnpinfo(sysctl_out, g0g1)
+ if not storvscid:
+ LOG.debug("Fail to find storvsc id from sysctl")
+ return None
+
+ camcontrol_b_out = get_camcontrol_dev_bus()
+ camcontrol_out = get_camcontrol_dev()
+ # try to find /dev/XX from 'blkvsc' device
+ blkvsc = "blkvsc{0}".format(storvscid)
+ scbusx = find_busdev_from_disk(camcontrol_b_out, blkvsc)
+ if scbusx:
+ devname = find_dev_from_busdev(camcontrol_out, scbusx)
+ if devname is None:
+ LOG.debug("Fail to find /dev/daX")
+ return None
+ return devname
+ # try to find /dev/XX from 'storvsc' device
+ storvsc = "storvsc{0}".format(storvscid)
+ scbusx = find_busdev_from_disk(camcontrol_b_out, storvsc)
+ if scbusx:
+ devname = find_dev_from_busdev(camcontrol_out, scbusx)
+ if devname is None:
+ LOG.debug("Fail to find /dev/daX")
+ return None
+ return devname
+ return None
+
+# update the FreeBSD specific information
+if util.is_FreeBSD():
+ DEFAULT_PRIMARY_NIC = 'hn0'
+ LEASE_FILE = '/var/db/dhclient.leases.hn0'
+ DEFAULT_FS = 'freebsd-ufs'
+ res_disk = get_resource_disk_on_freebsd(1)
+ if res_disk is not None:
+ LOG.debug("resource disk is not None")
+ RESOURCE_DISK_PATH = "/dev/" + res_disk
+ else:
+ LOG.debug("resource disk is None")
BUILTIN_DS_CONFIG = {
'agent_command': AGENT_START_BUILTIN,
'data_dir': "/var/lib/waagent",
'set_hostname': True,
'hostname_bounce': {
- 'interface': 'eth0',
+ 'interface': DEFAULT_PRIMARY_NIC,
'policy': True,
'command': BOUNCE_COMMAND,
'hostname_command': 'hostname',
},
'disk_aliases': {'ephemeral0': RESOURCE_DISK_PATH},
- 'dhclient_lease_file': '/var/lib/dhcp/dhclient.eth0.leases',
+ 'dhclient_lease_file': LEASE_FILE,
}
BUILTIN_CLOUD_CONFIG = {
@@ -53,7 +195,7 @@ BUILTIN_CLOUD_CONFIG = {
'layout': [100],
'overwrite': True},
},
- 'fs_setup': [{'filesystem': 'ext4',
+ 'fs_setup': [{'filesystem': DEFAULT_FS,
'device': 'ephemeral0.1',
'replace_fs': 'ntfs'}],
}
@@ -190,7 +332,11 @@ class DataSourceAzureNet(sources.DataSource):
for cdev in candidates:
try:
if cdev.startswith("/dev/"):
- ret = util.mount_cb(cdev, load_azure_ds_dir)
+ if util.is_FreeBSD():
+ ret = util.mount_cb(cdev, load_azure_ds_dir,
+ mtype="udf", sync=False)
+ else:
+ ret = util.mount_cb(cdev, load_azure_ds_dir)
else:
ret = load_azure_ds_dir(cdev)
@@ -218,11 +364,12 @@ class DataSourceAzureNet(sources.DataSource):
LOG.debug("using files cached in %s", ddir)
# azure / hyper-v provides random data here
- seed = util.load_file("/sys/firmware/acpi/tables/OEM0",
- quiet=True, decode=False)
- if seed:
- self.metadata['random_seed'] = seed
-
+ if not util.is_FreeBSD():
+ seed = util.load_file("/sys/firmware/acpi/tables/OEM0",
+ quiet=True, decode=False)
+ if seed:
+ self.metadata['random_seed'] = seed
+ # TODO. find the seed on FreeBSD platform
# now update ds_cfg to reflect contents pass in config
user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
@@ -633,8 +780,19 @@ def encrypt_pass(password, salt_id="$6$"):
def list_possible_azure_ds_devs():
# return a sorted list of devices that might have a azure datasource
devlist = []
- for fstype in ("iso9660", "udf"):
- devlist.extend(util.find_devs_with("TYPE=%s" % fstype))
+ if util.is_FreeBSD():
+ cdrom_dev = "/dev/cd0"
+ try:
+ util.subp(["mount", "-o", "ro", "-t", "udf", cdrom_dev,
+ "/mnt/cdrom/secure"])
+ except util.ProcessExecutionError:
+ LOG.debug("Fail to mount cd")
+ return devlist
+ util.subp(["umount", "/mnt/cdrom/secure"])
+ devlist.append(cdrom_dev)
+ else:
+ for fstype in ("iso9660", "udf"):
+ devlist.extend(util.find_devs_with("TYPE=%s" % fstype))
devlist.sort(reverse=True)
return devlist
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index 6e01aa47..e22409d1 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -29,6 +29,14 @@ def cd(newdir):
os.chdir(prevdir)
+def _get_dhcp_endpoint_option_name():
+ if util.is_FreeBSD():
+ azure_endpoint = "option-245"
+ else:
+ azure_endpoint = "unknown-245"
+ return azure_endpoint
+
+
class AzureEndpointHttpClient(object):
headers = {
@@ -235,8 +243,9 @@ class WALinuxAgentShim(object):
leases = []
content = util.load_file(fallback_lease_file)
LOG.debug("content is %s", content)
+ option_name = _get_dhcp_endpoint_option_name()
for line in content.splitlines():
- if 'unknown-245' in line:
+ if option_name in line:
# Example line from Ubuntu
# option unknown-245 a8:3f:81:10;
leases.append(line.strip(' ').split(' ', 2)[-1].strip(';\n"'))
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index f7191b09..ad557827 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -624,7 +624,7 @@ class Init(object):
return (None, loc)
if ncfg:
return (ncfg, loc)
- return (net.generate_fallback_config(), "fallback")
+ return (self.distro.generate_fallback_config(), "fallback")
def apply_network_config(self, bring_up):
netcfg, src = self._find_networking_config()
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 22af99dd..27a98330 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -565,6 +565,10 @@ def is_ipv4(instr):
return len(toks) == 4
+def is_FreeBSD():
+ return system_info()['platform'].startswith('FreeBSD')
+
+
def get_cfg_option_bool(yobj, key, default=False):
if key not in yobj:
return default
@@ -2055,11 +2059,56 @@ def parse_mtab(path):
return None
+def find_freebsd_part(label_part):
+ if label_part.startswith("/dev/label/"):
+ target_label = label_part[5:]
+ (label_part, err) = subp(['glabel', 'status', '-s'])
+ for labels in label_part.split("\n"):
+ items = labels.split()
+ if len(items) > 0 and items[0].startswith(target_label):
+ label_part = items[2]
+ break
+ label_part = str(label_part)
+ return label_part
+
+
+def get_path_dev_freebsd(path, mnt_list):
+ path_found = None
+ for line in mnt_list.split("\n"):
+ items = line.split()
+ if (len(items) > 2 and os.path.exists(items[1] + path)):
+ path_found = line
+ break
+ return path_found
+
+
+def get_mount_info_freebsd(path, log=LOG):
+ (result, err) = subp(['mount', '-p', path], rcs=[0, 1])
+ if len(err):
+ # find a path if the input is not a mounting point
+ (mnt_list, err) = subp(['mount', '-p'])
+ path_found = get_path_dev_freebsd(path, mnt_list)
+ if (path_found is None):
+ return None
+ result = path_found
+ ret = result.split()
+ label_part = find_freebsd_part(ret[0])
+ return "/dev/" + label_part, ret[2], ret[1]
+
+
def parse_mount(path):
(mountoutput, _err) = subp("mount")
mount_locs = mountoutput.splitlines()
for line in mount_locs:
m = re.search(r'^(/dev/[\S]+) on (/.*) \((.+), .+, (.+)\)$', line)
+ if not m:
+ continue
+ # check whether the dev refers to a label on FreeBSD
+ # for example, if dev is '/dev/label/rootfs', we should
+ # continue finding the real device like '/dev/da0'.
+ devm = re.search('^(/dev/.+)p([0-9])$', m.group(1))
+ if (not devm and is_FreeBSD()):
+ return get_mount_info_freebsd(path)
devpth = m.group(1)
mount_point = m.group(2)
fs_type = m.group(3)
@@ -2336,7 +2385,8 @@ def read_dmi_data(key):
uname_arch = os.uname()[4]
if not (uname_arch == "x86_64" or
(uname_arch.startswith("i") and uname_arch[2:] == "86") or
- uname_arch == 'aarch64'):
+ uname_arch == 'aarch64' or
+ uname_arch == 'amd64'):
LOG.debug("dmidata is not supported on %s", uname_arch)
return None
diff --git a/config/cloud.cfg-freebsd b/config/cloud.cfg-freebsd
index be664f5d..d666c397 100644
--- a/config/cloud.cfg-freebsd
+++ b/config/cloud.cfg-freebsd
@@ -5,7 +5,7 @@ syslog_fix_perms: root:wheel
# This should not be required, but leave it in place until the real cause of
# not beeing able to find -any- datasources is resolved.
-datasource_list: ['ConfigDrive', 'OpenStack', 'Ec2']
+datasource_list: ['ConfigDrive', 'Azure', 'OpenStack', 'Ec2']
# A set of users which may be applied and/or used by various modules
# when a 'default' entry is found it will reference the 'default_user'
diff --git a/setup.py b/setup.py
index 32a44d94..4616599b 100755
--- a/setup.py
+++ b/setup.py
@@ -89,7 +89,6 @@ LIB = "/lib"
if os.uname()[0] == 'FreeBSD':
USR = "/usr/local"
USR_LIB_EXEC = "/usr/local/lib"
- ETC = "/usr/local/etc"
elif os.path.isfile('/etc/redhat-release'):
USR_LIB_EXEC = "/usr/libexec"
@@ -164,8 +163,6 @@ else:
(ETC + '/cloud', glob('config/*.cfg')),
(ETC + '/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')),
(ETC + '/cloud/templates', glob('templates/*')),
- (ETC + '/NetworkManager/dispatcher.d/', ['tools/hook-network-manager']),
- (ETC + '/dhcp/dhclient-exit-hooks.d/', ['tools/hook-dhclient']),
(USR_LIB_EXEC + '/cloud-init', ['tools/ds-identify',
'tools/uncloud-init',
'tools/write-ssh-key-fingerprints']),
@@ -174,8 +171,14 @@ else:
[f for f in glob('doc/examples/*') if is_f(f)]),
(USR + '/share/doc/cloud-init/examples/seed',
[f for f in glob('doc/examples/seed/*') if is_f(f)]),
- (LIB + '/udev/rules.d', [f for f in glob('udev/*.rules')]),
]
+ if os.uname()[0] != 'FreeBSD':
+ data_files.extend([
+ (ETC + '/NetworkManager/dispatcher.d/',
+ ['tools/hook-network-manager']),
+ (ETC + '/dhcp/dhclient-exit-hooks.d/', ['tools/hook-dhclient']),
+ (LIB + '/udev/rules.d', [f for f in glob('udev/*.rules')])
+ ])
# Use a subclass for install that handles
# adding on the right init system configuration files
cmdclass = {
diff --git a/sysvinit/freebsd/cloudconfig b/sysvinit/freebsd/cloudconfig
index 01bc061e..e4064fa3 100755
--- a/sysvinit/freebsd/cloudconfig
+++ b/sysvinit/freebsd/cloudconfig
@@ -7,24 +7,14 @@
. /etc/rc.subr
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
name="cloudconfig"
command="/usr/local/bin/cloud-init"
start_cmd="cloudconfig_start"
stop_cmd=":"
rcvar="cloudinit_enable"
-start_precmd="cloudinit_override"
start_cmd="cloudconfig_start"
-cloudinit_override()
-{
- # If there exist sysconfig/defaults variable override files use it...
- if [ -f /etc/defaults/cloud-init ]; then
- . /etc/defaults/cloud-init
- fi
-}
-
cloudconfig_start()
{
echo "${command} starting"
diff --git a/sysvinit/freebsd/cloudfinal b/sysvinit/freebsd/cloudfinal
index 1b487aa0..b6894c39 100755
--- a/sysvinit/freebsd/cloudfinal
+++ b/sysvinit/freebsd/cloudfinal
@@ -7,24 +7,14 @@
. /etc/rc.subr
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
name="cloudfinal"
command="/usr/local/bin/cloud-init"
start_cmd="cloudfinal_start"
stop_cmd=":"
rcvar="cloudinit_enable"
-start_precmd="cloudinit_override"
start_cmd="cloudfinal_start"
-cloudinit_override()
-{
- # If there exist sysconfig/defaults variable override files use it...
- if [ -f /etc/defaults/cloud-init ]; then
- . /etc/defaults/cloud-init
- fi
-}
-
cloudfinal_start()
{
echo -n "${command} starting"
diff --git a/sysvinit/freebsd/cloudinit b/sysvinit/freebsd/cloudinit
index 862eeab4..33263009 100755
--- a/sysvinit/freebsd/cloudinit
+++ b/sysvinit/freebsd/cloudinit
@@ -7,24 +7,14 @@
. /etc/rc.subr
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
name="cloudinit"
command="/usr/local/bin/cloud-init"
start_cmd="cloudinit_start"
stop_cmd=":"
rcvar="cloudinit_enable"
-start_precmd="cloudinit_override"
start_cmd="cloudinit_start"
-cloudinit_override()
-{
- # If there exist sysconfig/defaults variable override files use it...
- if [ -f /etc/defaults/cloud-init ]; then
- . /etc/defaults/cloud-init
- fi
-}
-
cloudinit_start()
{
echo -n "${command} starting"
diff --git a/sysvinit/freebsd/cloudinitlocal b/sysvinit/freebsd/cloudinitlocal
index fb342a0f..11a5eb1c 100755
--- a/sysvinit/freebsd/cloudinitlocal
+++ b/sysvinit/freebsd/cloudinitlocal
@@ -1,30 +1,20 @@
#!/bin/sh
# PROVIDE: cloudinitlocal
-# REQUIRE: mountcritlocal
+# REQUIRE: ldconfig mountcritlocal
# BEFORE: NETWORKING FILESYSTEMS cloudinit cloudconfig cloudfinal
. /etc/rc.subr
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
name="cloudinitlocal"
command="/usr/local/bin/cloud-init"
start_cmd="cloudlocal_start"
stop_cmd=":"
rcvar="cloudinit_enable"
-start_precmd="cloudinit_override"
start_cmd="cloudlocal_start"
-cloudinit_override()
-{
- # If there exist sysconfig/defaults variable override files use it...
- if [ -f /etc/defaults/cloud-init ]; then
- . /etc/defaults/cloud-init
- fi
-}
-
cloudlocal_start()
{
echo -n "${command} starting"
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 8d22bb59..e6b0dcb4 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -3,6 +3,8 @@
from cloudinit import helpers
from cloudinit.util import b64e, decode_binary, load_file
from cloudinit.sources import DataSourceAzure
+from cloudinit.util import find_freebsd_part
+from cloudinit.util import get_path_dev_freebsd
from ..helpers import TestCase, populate_dir, mock, ExitStack, PY26, SkipTest
@@ -95,6 +97,41 @@ class TestAzureDataSource(TestCase):
for module, name, new in patches:
self.patches.enter_context(mock.patch.object(module, name, new))
+ def _get_mockds(self):
+ mod = DataSourceAzure
+ sysctl_out = "dev.storvsc.3.%pnpinfo: "\
+ "classid=ba6163d9-04a1-4d29-b605-72e2ffb1dc7f "\
+ "deviceid=f8b3781b-1e82-4818-a1c3-63d806ec15bb\n"
+ sysctl_out += "dev.storvsc.2.%pnpinfo: "\
+ "classid=ba6163d9-04a1-4d29-b605-72e2ffb1dc7f "\
+ "deviceid=f8b3781a-1e82-4818-a1c3-63d806ec15bb\n"
+ sysctl_out += "dev.storvsc.1.%pnpinfo: "\
+ "classid=32412632-86cb-44a2-9b5c-50d1417354f5 "\
+ "deviceid=00000000-0001-8899-0000-000000000000\n"
+ camctl_devbus = """
+scbus0 on ata0 bus 0
+scbus1 on ata1 bus 0
+scbus2 on blkvsc0 bus 0
+scbus3 on blkvsc1 bus 0
+scbus4 on storvsc2 bus 0
+scbus5 on storvsc3 bus 0
+scbus-1 on xpt0 bus 0
+ """
+ camctl_dev = """
+<Msft Virtual CD/ROM 1.0> at scbus1 target 0 lun 0 (cd0,pass0)
+<Msft Virtual Disk 1.0> at scbus2 target 0 lun 0 (da0,pass1)
+<Msft Virtual Disk 1.0> at scbus3 target 1 lun 0 (da1,pass2)
+ """
+ self.apply_patches([
+ (mod, 'get_dev_storvsc_sysctl', mock.MagicMock(
+ return_value=sysctl_out)),
+ (mod, 'get_camcontrol_dev_bus', mock.MagicMock(
+ return_value=camctl_devbus)),
+ (mod, 'get_camcontrol_dev', mock.MagicMock(
+ return_value=camctl_dev))
+ ])
+ return mod
+
def _get_ds(self, data, agent_command=None):
def dsdevs():
@@ -177,6 +214,34 @@ class TestAzureDataSource(TestCase):
return
raise AssertionError("XML is the same")
+ def test_get_resource_disk(self):
+ ds = self._get_mockds()
+ dev = ds.get_resource_disk_on_freebsd(1)
+ self.assertEqual("da1", dev)
+
+ @mock.patch('cloudinit.util.subp')
+ def test_find_freebsd_part_on_Azure(self, mock_subp):
+ glabel_out = '''
+gptid/fa52d426-c337-11e6-8911-00155d4c5e47 N/A da0p1
+ label/rootfs N/A da0p2
+ label/swap N/A da0p3
+'''
+ mock_subp.return_value = (glabel_out, "")
+ res = find_freebsd_part("/dev/label/rootfs")
+ self.assertEqual("da0p2", res)
+
+ def test_get_path_dev_freebsd_on_Azure(self):
+ mnt_list = '''
+/dev/label/rootfs / ufs rw 1 1
+devfs /dev devfs rw,multilabel 0 0
+fdescfs /dev/fd fdescfs rw 0 0
+/dev/da1s1 /mnt/resource ufs rw 2 2
+'''
+ with mock.patch.object(os.path, 'exists',
+ return_value=True):
+ res = get_path_dev_freebsd('/etc', mnt_list)
+ self.assertNotEqual(res, None)
+
def test_basic_seed_dir(self):
odata = {'HostName': "myhost", 'UserName': "myuser"}
data = {'ovfcontent': construct_valid_ovf_env(data=odata),
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
index aafdebd7..b2d2971b 100644
--- a/tests/unittests/test_datasource/test_azure_helper.py
+++ b/tests/unittests/test_datasource/test_azure_helper.py
@@ -3,7 +3,6 @@
import os
from cloudinit.sources.helpers import azure as azure_helper
-
from ..helpers import ExitStack, mock, TestCase
@@ -72,10 +71,11 @@ class TestFindEndpoint(TestCase):
@staticmethod
def _build_lease_content(encoded_address):
+ endpoint = azure_helper._get_dhcp_endpoint_option_name()
return '\n'.join([
'lease {',
' interface "eth0";',
- ' option unknown-245 {0};'.format(encoded_address),
+ ' option {0} {1};'.format(endpoint, encoded_address),
'}'])
def test_from_dhcp_client(self):
diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py
index e93d28de..1d3d2f19 100644
--- a/tests/unittests/test_datasource/test_cloudstack.py
+++ b/tests/unittests/test_datasource/test_cloudstack.py
@@ -15,6 +15,11 @@ class TestCloudStackPasswordFetching(TestCase):
mod_name = 'cloudinit.sources.DataSourceCloudStack'
self.patches.enter_context(mock.patch('{0}.ec2'.format(mod_name)))
self.patches.enter_context(mock.patch('{0}.uhelp'.format(mod_name)))
+ default_gw = "192.201.20.0"
+ mod_name = 'cloudinit.sources.DataSourceCloudStack.get_default_gateway'
+ get_default_gw = mock.MagicMock(return_value=default_gw)
+ self.patches.enter_context(
+ mock.patch(mod_name, get_default_gw))
def _set_password_server_response(self, response_string):
subp = mock.MagicMock(return_value=(response_string, ''))
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 88370669..1e10a33d 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -178,6 +178,20 @@ class WriteBuffer(object):
class TestNetCfgDistro(TestCase):
+ frbsd_ifout = """\
+hn0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
+ options=51b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,TSO4,LRO>
+ ether 00:15:5d:4c:73:00
+ inet6 fe80::215:5dff:fe4c:7300%hn0 prefixlen 64 scopeid 0x2
+ inet 10.156.76.127 netmask 0xfffffc00 broadcast 10.156.79.255
+ nd6 options=23<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL>
+ media: Ethernet autoselect (10Gbase-T <full-duplex>)
+ status: active
+"""
+
+ def setUp(self):
+ super(TestNetCfgDistro, self).setUp()
+
def _get_distro(self, dname, renderers=None):
cls = distros.fetch(dname)
cfg = settings.CFG_BUILTIN
@@ -251,6 +265,7 @@ class TestNetCfgDistro(TestCase):
def test_apply_network_config_v1_to_netplan_ub(self):
renderers = ['netplan']
+ devlist = ['eth0', 'lo']
ub_distro = self._get_distro('ubuntu', renderers=renderers)
with ExitStack() as mocks:
write_bufs = {}
@@ -272,6 +287,9 @@ class TestNetCfgDistro(TestCase):
mock.patch.object(util, 'subp', return_value=(0, 0)))
mocks.enter_context(
mock.patch.object(os.path, 'isfile', return_value=False))
+ mocks.enter_context(
+ mock.patch("cloudinit.net.netplan.get_devicelist",
+ return_value=devlist))
ub_distro.apply_network_config(V1_NET_CFG, False)
@@ -285,6 +303,7 @@ class TestNetCfgDistro(TestCase):
def test_apply_network_config_v2_passthrough_ub(self):
renderers = ['netplan']
+ devlist = ['eth0', 'lo']
ub_distro = self._get_distro('ubuntu', renderers=renderers)
with ExitStack() as mocks:
write_bufs = {}
@@ -306,7 +325,10 @@ class TestNetCfgDistro(TestCase):
mock.patch.object(util, 'subp', return_value=(0, 0)))
mocks.enter_context(
mock.patch.object(os.path, 'isfile', return_value=False))
-
+ # FreeBSD does not have '/sys/class/net' file,
+ # so we need mock here.
+ mocks.enter_context(
+ mock.patch.object(os, 'listdir', return_value=devlist))
ub_distro.apply_network_config(V2_NET_CFG, False)
self.assertEqual(len(write_bufs), 1)
@@ -328,6 +350,29 @@ class TestNetCfgDistro(TestCase):
for (k, v) in b1.items():
self.assertEqual(v, b2[k])
+ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_list')
+ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out')
+ def test_get_ip_nic_freebsd(self, ifname_out, iflist):
+ frbsd_distro = self._get_distro('freebsd')
+ iflist.return_value = "lo0 hn0"
+ ifname_out.return_value = self.frbsd_ifout
+ res = frbsd_distro.get_ipv4()
+ self.assertEqual(res, ['lo0', 'hn0'])
+ res = frbsd_distro.get_ipv6()
+ self.assertEqual(res, [])
+
+ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ether')
+ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out')
+ @mock.patch('cloudinit.distros.freebsd.Distro.get_interface_mac')
+ def test_generate_fallback_config_freebsd(self, mac, ifname_out, if_ether):
+ frbsd_distro = self._get_distro('freebsd')
+
+ if_ether.return_value = 'hn0'
+ ifname_out.return_value = self.frbsd_ifout
+ mac.return_value = '00:15:5d:4c:73:00'
+ res = frbsd_distro.generate_fallback_config()
+ self.assertIsNotNone(res)
+
def test_simple_write_rh(self):
rh_distro = self._get_distro('rhel')
diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py
new file mode 100644
index 00000000..52591b8b
--- /dev/null
+++ b/tests/unittests/test_handler/test_handler_resizefs.py
@@ -0,0 +1,59 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.config import cc_resizefs
+
+import textwrap
+import unittest
+
+try:
+ from unittest import mock
+except ImportError:
+ import mock
+
+
+class TestResizefs(unittest.TestCase):
+ def setUp(self):
+ super(TestResizefs, self).setUp()
+ self.name = "resizefs"
+
+ @mock.patch('cloudinit.config.cc_resizefs._get_dumpfs_output')
+ @mock.patch('cloudinit.config.cc_resizefs._get_gpart_output')
+ def test_skip_ufs_resize(self, gpart_out, dumpfs_out):
+ fs_type = "ufs"
+ resize_what = "/"
+ devpth = "/dev/da0p2"
+ dumpfs_out.return_value = (
+ "# newfs command for / (/dev/label/rootfs)\n"
+ "newfs -O 2 -U -a 4 -b 32768 -d 32768 -e 4096 "
+ "-f 4096 -g 16384 -h 64 -i 8192 -j -k 6408 -m 8 "
+ "-o time -s 58719232 /dev/label/rootfs\n")
+ gpart_out.return_value = textwrap.dedent("""\
+ => 40 62914480 da0 GPT (30G)
+ 40 1024 1 freebsd-boot (512K)
+ 1064 58719232 2 freebsd-ufs (28G)
+ 58720296 3145728 3 freebsd-swap (1.5G)
+ 61866024 1048496 - free - (512M)
+ """)
+ res = cc_resizefs.can_skip_resize(fs_type, resize_what, devpth)
+ self.assertTrue(res)
+
+ @mock.patch('cloudinit.config.cc_resizefs._get_dumpfs_output')
+ @mock.patch('cloudinit.config.cc_resizefs._get_gpart_output')
+ def test_skip_ufs_resize_roundup(self, gpart_out, dumpfs_out):
+ fs_type = "ufs"
+ resize_what = "/"
+ devpth = "/dev/da0p2"
+ dumpfs_out.return_value = (
+ "# newfs command for / (/dev/label/rootfs)\n"
+ "newfs -O 2 -U -a 4 -b 32768 -d 32768 -e 4096 "
+ "-f 4096 -g 16384 -h 64 -i 8192 -j -k 368 -m 8 "
+ "-o time -s 297080 /dev/label/rootfs\n")
+ gpart_out.return_value = textwrap.dedent("""\
+ => 34 297086 da0 GPT (145M)
+ 34 297086 1 freebsd-ufs (145M)
+ """)
+ res = cc_resizefs.can_skip_resize(fs_type, resize_what, devpth)
+ self.assertTrue(res)
+
+
+# vi: ts=4 expandtab
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 89e75369..5d2dd031 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1120,14 +1120,14 @@ class TestNetplanPostcommands(CiTestCase):
render_target = 'netplan.yaml'
renderer = netplan.Renderer(
{'netplan_path': render_target, 'postcmds': True})
- renderer.render_network_state(render_dir, ns)
-
expected = [
mock.call(['netplan', 'generate'], capture=True),
mock.call(['udevadm', 'test-builtin', 'net_setup_link',
'/sys/class/net/lo'], capture=True),
]
- mock_subp.assert_has_calls(expected)
+ with mock.patch.object(os.path, 'islink', return_value=True):
+ renderer.render_network_state(render_dir, ns)
+ mock_subp.assert_has_calls(expected)
class TestEniNetworkStateToEni(CiTestCase):
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index 5d21b4b7..189caca8 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -596,7 +596,8 @@ class TestSubp(helpers.TestCase):
def test_subp_capture_stderr(self):
data = b'hello world'
(out, err) = util.subp(self.stdin2err, capture=True,
- decode=False, data=data)
+ decode=False, data=data,
+ update_env={'LC_ALL': 'C'})
self.assertEqual(err, data)
self.assertEqual(out, b'')
diff --git a/tools/build-on-freebsd b/tools/build-on-freebsd
index 8436498e..ccc10b40 100755
--- a/tools/build-on-freebsd
+++ b/tools/build-on-freebsd
@@ -10,9 +10,7 @@ depschecked=/tmp/c-i.dependencieschecked
pkgs="
dmidecode
e2fsprogs
- gpart
py27-Jinja2
- py27-argparse
py27-boto
py27-cheetah
py27-configobj
@@ -38,7 +36,7 @@ python setup.py build
python setup.py install -O1 --skip-build --prefix /usr/local/ --init-system sysvinit_freebsd
# Install the correct config file:
-cp config/cloud.cfg-freebsd /usr/local/etc/cloud/cloud.cfg
+cp config/cloud.cfg-freebsd /etc/cloud/cloud.cfg
# Enable cloud-init in /etc/rc.conf:
sed -i.bak -e "/cloudinit_enable=.*/d" /etc/rc.conf