← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~adobrawy/cloud-init:rbx-datasource into cloud-init:master

 

Adam Dobrawy has proposed merging ~adobrawy/cloud-init:rbx-datasource into cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~adobrawy/cloud-init/+git/cloud-init/+merge/354679
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~adobrawy/cloud-init:rbx-datasource into cloud-init:master.
diff --git a/cloudinit/sources/DataSourceRbxCloud.py b/cloudinit/sources/DataSourceRbxCloud.py
new file mode 100644
index 0000000..736b860
--- /dev/null
+++ b/cloudinit/sources/DataSourceRbxCloud.py
@@ -0,0 +1,343 @@
+# vi: ts=4 expandtab
+# Copyright (C) 2016 Warsaw Data Center
+#
+# Author: Malwina Leis <m.leis@xxxxxxxxxxx>
+# Author: Grzegorz Brzeski <gregory@xxxxxxxxxx>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+'''Datasource for  rootbox / hyperone cloud platforms'''
+import errno
+import os
+import os.path
+
+import socket
+
+from cloudinit import log as logging
+from cloudinit import sources
+from cloudinit import util
+from cloudinit.netinfo import netdev_info
+
+LOG = logging.getLogger(__name__)
+
+
+def _read_file(filepath):
+    try:
+        content = util.load_file(filepath).strip()
+    except IOError:
+        util.logexc(LOG, 'Failed accessing file: ' + filepath)
+        return None
+    return content
+
+
+def _get_meta_data(filepath):
+    content = _read_file(filepath)
+    if not content:
+        return None
+
+    try:
+        content = util.load_json(content)
+    except Exception:
+        util.logexc(LOG, 'Failed parsing meta data file from json.')
+        return None
+
+    return content
+
+
+def read_user_data_callback(mount_dir, distro):
+    '''
+    Description:
+        This callback will be applied by util.mount_cb() on the mounted
+        file.
+
+    Input:
+        mount_dir - Mount directory
+
+    Returns:
+        User Data
+
+    '''
+
+    meta_data = _get_meta_data(os.path.join(mount_dir, 'cloud.json'))
+    user_data = _read_file(os.path.join(mount_dir, 'user.data'))
+    additional_metadata = meta_data['additionalMetadata']
+
+    data = {
+        'userdata': user_data,
+        'metadata': {
+            'instance-id': meta_data['vm']['_id'],
+            # This puts keys also for root
+            # 'public-keys': meta_data['additionalMetadata']['sshKeys'],
+            'local-hostname': meta_data['vm']['name']
+        },
+        'cfg': {
+            'ssh_pwauth': True,
+            'disable_root': True,
+            'system_info': {
+                'default_user': {
+                    'name': additional_metadata.get('username', 'guru'),
+                    'gecos': additional_metadata.get('username', 'guru'),
+                    'sudo': ['ALL=(ALL) NOPASSWD:ALL'],
+                    'lock_passwd': False,
+                    'ssh_authorized_keys': additional_metadata['sshKeys'],
+                    'shell': '/bin/bash'
+                }
+            },
+            'runcmd': []
+        }
+    }
+
+    hosts_path = '/etc/hosts'
+    hosts = _read_file(hosts_path)
+    if hosts:
+        data['cfg']['manage_etc_hosts'] = False
+        LOG.debug('/etc/hosts exists - set manage_etc_hosts to False')
+    else:
+        data['cfg']['manage_etc_hosts'] = True
+        LOG.debug('/etc/hosts does not exists - set manage_etc_hosts to True')
+
+    if meta_data['netadp']:
+        netdata = generate_eni(meta_data['netadp'], distro)
+        data['metadata']['network-interfaces'] = netdata['eni']
+        data['cfg']['runcmd'] = netdata['cmd']
+
+    username = meta_data['additionalMetadata'].get('username', 'guru')
+    password = meta_data['additionalMetadata']['password']['sha512']
+    data['cfg']['runcmd'].append("echo '{username}:{password}' | chpasswd -e"
+                                 .format(username=username,
+                                         password=password))
+
+    LOG.debug('returning DATA object:')
+    LOG.debug(data)
+
+    return data
+
+
+def generate_eni(netadp, distro):
+    LOG.debug("RBX: generate_eni")
+    LOG.debug(netadp)
+
+    netdevices = netdev_info()
+
+    ENI = []
+    CMD = []
+
+    ARPING = "arping -c 2 -S "
+    ARPING_RHEL = "arping -c 2 -s "
+
+    LOG.debug("Generating eni for distro: %s", distro)
+    if distro == 'rhel':
+        ARPING = ARPING_RHEL
+
+    ENI.append('auto lo')
+    ENI.append('iface lo inet loopback')
+    ENI.append('')
+
+    default_nic = [
+        n
+        for n in sorted(netadp, key=lambda k: k['_id'])
+        if n.get('default_gw') == "true"
+    ]
+    if (not default_nic) and netadp:
+        default_nic = [sorted(netadp, key=lambda k: k['_id'])[0]]
+        LOG.debug("Default interface mac: %s",
+                  default_nic[0].get('macaddress'))
+    else:
+        LOG.debug("No netadp to configure")
+        return None
+    LOG.debug("Default nic: %s", default_nic[0].get('macaddress'))
+
+    for name, data in netdevices.items():
+        ENI.append('\n')
+        name_split = name.split(':')
+        if len(name_split) > 1 and name_split[1] != "":
+            continue
+        name = name_split[0]
+
+        mac = data.get('hwaddr').lower()
+        if not mac:
+            continue
+
+        nic = [n for n in netadp if n.get('macaddress').lower() == mac]
+
+        if not nic:
+            continue
+
+        ip = nic[0].get("ip")
+        network = nic[0].get("network")
+
+        ENI.append('auto ' + name)
+
+        if not ip:
+            ENI.append('iface ' + name + ' inet manual')
+            ENI.append('\tpre-up ip link set ' + name + '  up')
+            ENI.append('\tpost-down ip link set ' + name + ' down')
+            ENI.append('\n')
+            continue
+
+        ENI.append('iface ' + name + ' inet static')
+        ENI.append('\taddress ' + ip[0].get("address"))
+        ENI.append('\tnetmask ' + network.get("netmask"))
+
+        if network.get("gateway"):
+            if default_nic[0].get('macaddress') == nic[0].get('macaddress'):
+                ENI.append('\tgateway ' + network.get("gateway"))
+                if network.get("dns").get("nameservers"):
+                    ENI.append('\tdns-nameservers ' + ' '.join(
+                        network.get("dns").get("nameservers")))
+
+            CMD.append(ARPING + ip[0].get("address") + ' ' +
+                       network.get("gateway") + ' &')
+            CMD.append(ARPING + ip[0].get("address") + ' ' +
+                       int2ip(ip2int(network.get("gateway")) + 2) + ' &')
+            CMD.append(ARPING + ip[0].get("address") + ' ' +
+                       int2ip(ip2int(network.get("gateway")) + 3) + ' &')
+
+        secondary_address = ip[1:]
+        if secondary_address:
+            LOG.debug('RBX: secondaryAddress')
+            address = ip.get("address")
+            netmask = str(netmask_to_cidr(network.get("netmask")))
+            for index, ip in enumerate(secondary_address):
+                ENI.append('\tup ip addr add ' + address + '/' + netmask +
+                           ' dev ' + name)
+                ENI.append('\tdown ip addr del ' + address + '/' + netmask +
+                           ' dev ' + name)
+                if network.get("gateway"):
+                    CMD.append(ARPING + address + ' ' +
+                               network.get("gateway") +
+                               ' &')
+                    CMD.append(ARPING + address + ' ' +
+                               int2ip(ip2int(network.get("gateway")) + 2) +
+                               ' &')
+                    CMD.append(ARPING + address + ' ' +
+                               int2ip(ip2int(network.get("gateway")) + 3) +
+                               ' &')
+
+        if network.get('routing'):
+            for route in network.get('routing'):
+                ENI.append('\tup ip route add ' + route.get('destination') +
+                           ' via ' + route.get('via') +
+                           ' dev ' + name)
+                ENI.append('\tdown ip route del ' + route.get('destination') +
+                           ' via ' + route.get('via') +
+                           ' dev ' + name)
+
+    return {
+        'eni': "\n".join(ENI),
+        'cmd': CMD
+    }
+
+
+def netmask_to_cidr(netmask):
+    '''
+    :param netmask: netmask ip addr (eg: 255.255.255.0)
+    :return: equivalent cidr number to given netmask ip (eg: 22)
+    '''
+    return sum([bin(int(x)).count('1') for x in netmask.split('.')])
+
+
+def ip2int(addr):
+    parts = addr.split('.')
+    return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
+           (int(parts[2]) << 8) + int(parts[3])
+
+
+def int2ip(addr):
+    return '.'.join([str(addr >> (i << 3) & 0xFF) for i in range(4)[::-1]])
+
+
+class DataSourceRbxCloud(sources.DataSource):
+    def __init__(self, sys_cfg, distro, paths):
+        sources.DataSource.__init__(self, sys_cfg, distro, paths)
+        self.seed = None
+        self.supported_seed_starts = ("/", "file://")
+
+    def __str__(self):
+        root = sources.DataSource.__str__(self)
+        return "%s [seed=%s]" % (root, self.seed)
+
+    def get_data(self):
+        '''
+        Description:
+            User Data is passed to the launching instance which
+            is used to perform instance configuration.
+        '''
+
+        dev_list = util.find_devs_with("LABEL=CLOUDMD")
+        for device in dev_list:
+            try:
+                rbx_data = util.mount_cb(device, read_user_data_callback,
+                                         self.distro.name)
+                if rbx_data:
+                    break
+            except OSError as err:
+                if err.errno != errno.ENOENT:
+                    raise
+            except util.MountFailedError:
+                util.logexc(LOG, "Failed to mount %s when looking for user "
+                                 "data", device)
+        if not rbx_data:
+            util.logexc(LOG, "Failed to load metadata and userdata")
+            return False
+
+        self.userdata_raw = rbx_data['userdata']
+        self.metadata = rbx_data['metadata']
+        self.cfg = rbx_data['cfg']
+
+        LOG.debug('RBX: metadata')
+        LOG.debug(self.metadata)
+        if self.metadata['network-interfaces']:
+            LOG.debug("Updating network interfaces from %s", self)
+            netdevices = netdev_info()
+
+            for nic, data in netdevices.items():
+
+                ifdown_cmd = ['ifdown', nic]
+                ip_down_cmd = ['ip', 'link', 'set', 'dev', nic, 'down']
+                ip_flush_cmd = ['ip', 'addr', 'flush', 'dev', nic]
+
+                try:
+                    util.subp(ifdown_cmd)
+                    LOG.debug("Brought '%s' down.", nic)
+
+                    util.subp(ip_down_cmd)
+                    LOG.debug("Brought '%s' down.", nic)
+
+                    util.subp(ip_flush_cmd)
+                    LOG.debug("Cleared config of  '%s'.", nic)
+                except Exception:
+                    LOG.debug("Clearing config of '%s' failed.", nic)
+
+            self.distro.apply_network(self.metadata['network-interfaces'])
+
+        return True
+
+    @property
+    def launch_index(self):
+        return None
+
+    def get_instance_id(self):
+        return self.metadata['instance-id']
+
+    def get_public_ssh_keys(self):
+        return self.metadata['public-keys']
+
+    def get_hostname(self, fqdn=False, _resolve_ip=False, metadata_only=False):
+        return self.metadata['local-hostname']
+
+    def get_userdata_raw(self):
+        return self.userdata_raw
+
+    def get_config_obj(self):
+        return self.cfg
+
+
+# Used to match classes to dependencies
+datasources = [
+    (DataSourceRbxCloud, (sources.DEP_FILESYSTEM,)),
+]
+
+
+# Return a list of data sources that match this set of dependencies
+def get_datasource_list(depends):
+    return sources.list_from_depends(depends, datasources)

Follow ups