Blame cloudinit/sources/DataSourceRbxCloud.py

Packit Service a04d08
# Copyright (C) 2018 Warsaw Data Center
Packit Service a04d08
#
Packit Service a04d08
# Author: Malwina Leis <m.leis@rootbox.com>
Packit Service a04d08
# Author: Grzegorz Brzeski <gregory@rootbox.io>
Packit Service a04d08
# Author: Adam Dobrawy <a.dobrawy@hyperone.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
"""
Packit Service a04d08
This file contains code used to gather the user data passed to an
Packit Service a04d08
instance on rootbox / hyperone cloud platforms
Packit Service a04d08
"""
Packit Service a04d08
import errno
Packit Service a04d08
import os
Packit Service a04d08
import os.path
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit import sources
Packit Service 9bfd13
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from cloudinit.event import EventType
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
ETC_HOSTS = '/etc/hosts'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_manage_etc_hosts():
Packit Service a04d08
    hosts = util.load_file(ETC_HOSTS, quiet=True)
Packit Service a04d08
    if hosts:
Packit Service a04d08
        LOG.debug('/etc/hosts exists - setting manage_etc_hosts to False')
Packit Service a04d08
        return False
Packit Service a04d08
    LOG.debug('/etc/hosts does not exists - setting manage_etc_hosts to True')
Packit Service a04d08
    return True
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def ip2int(addr):
Packit Service a04d08
    parts = addr.split('.')
Packit Service a04d08
    return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
Packit Service a04d08
           (int(parts[2]) << 8) + int(parts[3])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def int2ip(addr):
Packit Service a04d08
    return '.'.join([str(addr >> (i << 3) & 0xFF) for i in range(4)[::-1]])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _sub_arp(cmd):
Packit Service a04d08
    """
Packit Service 9bfd13
    Uses the preferred cloud-init subprocess def of subp.subp
Packit Service a04d08
    and runs arping.  Breaking this to a separate function
Packit Service a04d08
    for later use in mocking and unittests
Packit Service a04d08
    """
Packit Service 9bfd13
    return subp.subp(['arping'] + cmd)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def gratuitous_arp(items, distro):
Packit Service a04d08
    source_param = '-S'
Packit Service a04d08
    if distro.name in ['fedora', 'centos', 'rhel']:
Packit Service a04d08
        source_param = '-s'
Packit Service a04d08
    for item in items:
Packit Service 9bfd13
        try:
Packit Service 9bfd13
            _sub_arp([
Packit Service 9bfd13
                '-c', '2',
Packit Service 9bfd13
                source_param, item['source'],
Packit Service 9bfd13
                item['destination']
Packit Service 9bfd13
            ])
Packit Service 9bfd13
        except subp.ProcessExecutionError as error:
Packit Service 9bfd13
            # warning, because the system is able to function properly
Packit Service 9bfd13
            # despite no success - some ARP table may be waiting for
Packit Service 9bfd13
            # expiration, but the system may continue
Packit Service 9bfd13
            LOG.warning('Failed to arping from "%s" to "%s": %s',
Packit Service 9bfd13
                        item['source'], item['destination'], error)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_md():
Packit Service a04d08
    rbx_data = None
Packit Service 9bfd13
    devices = set(
Packit Service 9bfd13
        util.find_devs_with('LABEL=CLOUDMD') +
Packit Service 9bfd13
        util.find_devs_with('LABEL=cloudmd')
Packit Service 9bfd13
    )
Packit Service a04d08
    for device in devices:
Packit Service a04d08
        try:
Packit Service a04d08
            rbx_data = util.mount_cb(
Packit Service a04d08
                device=device,
Packit Service a04d08
                callback=read_user_data_callback,
Packit Service 9bfd13
                mtype=['vfat', 'fat', 'msdosfs']
Packit Service a04d08
            )
Packit Service a04d08
            if rbx_data:
Packit Service a04d08
                break
Packit Service a04d08
        except OSError as err:
Packit Service a04d08
            if err.errno != errno.ENOENT:
Packit Service a04d08
                raise
Packit Service a04d08
        except util.MountFailedError:
Packit Service a04d08
            util.logexc(LOG, "Failed to mount %s when looking for user "
Packit Service a04d08
                             "data", device)
Packit Service a04d08
    if not rbx_data:
Packit Service a04d08
        util.logexc(LOG, "Failed to load metadata and userdata")
Packit Service a04d08
        return False
Packit Service a04d08
    return rbx_data
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def generate_network_config(netadps):
Packit Service a04d08
    """Generate network configuration
Packit Service a04d08
Packit Service a04d08
    @param netadps: A list of network adapter settings
Packit Service a04d08
Packit Service a04d08
    @returns: A dict containing network config
Packit Service a04d08
    """
Packit Service a04d08
    return {
Packit Service a04d08
        'version': 1,
Packit Service a04d08
        'config': [
Packit Service a04d08
            {
Packit Service a04d08
                'type': 'physical',
Packit Service a04d08
                'name': 'eth{}'.format(str(i)),
Packit Service a04d08
                'mac_address': netadp['macaddress'].lower(),
Packit Service a04d08
                'subnets': [
Packit Service a04d08
                    {
Packit Service a04d08
                        'type': 'static',
Packit Service a04d08
                        'address': ip['address'],
Packit Service a04d08
                        'netmask': netadp['network']['netmask'],
Packit Service a04d08
                        'control': 'auto',
Packit Service a04d08
                        'gateway': netadp['network']['gateway'],
Packit Service a04d08
                        'dns_nameservers': netadp['network']['dns'][
Packit Service a04d08
                            'nameservers']
Packit Service a04d08
                    } for ip in netadp['ip']
Packit Service a04d08
                ],
Packit Service a04d08
            } for i, netadp in enumerate(netadps)
Packit Service a04d08
        ]
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def read_user_data_callback(mount_dir):
Packit Service a04d08
    """This callback will be applied by util.mount_cb() on the mounted
Packit Service a04d08
    drive.
Packit Service a04d08
Packit Service a04d08
    @param mount_dir: String representing path of directory where mounted drive
Packit Service a04d08
    is available
Packit Service a04d08
Packit Service a04d08
    @returns: A dict containing userdata, metadata and cfg based on metadata.
Packit Service a04d08
    """
Packit Service a04d08
    meta_data = util.load_json(
Packit Service a04d08
        text=util.load_file(
Packit Service a04d08
            fname=os.path.join(mount_dir, 'cloud.json'),
Packit Service a04d08
            decode=False
Packit Service a04d08
        )
Packit Service a04d08
    )
Packit Service a04d08
    user_data = util.load_file(
Packit Service a04d08
        fname=os.path.join(mount_dir, 'user.data'),
Packit Service a04d08
        quiet=True
Packit Service a04d08
    )
Packit Service a04d08
    if 'vm' not in meta_data or 'netadp' not in meta_data:
Packit Service a04d08
        util.logexc(LOG, "Failed to load metadata. Invalid format.")
Packit Service a04d08
        return None
Packit Service a04d08
    username = meta_data.get('additionalMetadata', {}).get('username')
Packit Service a04d08
    ssh_keys = meta_data.get('additionalMetadata', {}).get('sshKeys', [])
Packit Service a04d08
Packit Service a04d08
    hash = None
Packit Service a04d08
    if meta_data.get('additionalMetadata', {}).get('password'):
Packit Service a04d08
        hash = meta_data['additionalMetadata']['password']['sha512']
Packit Service a04d08
Packit Service a04d08
    network = generate_network_config(meta_data['netadp'])
Packit Service a04d08
Packit Service a04d08
    data = {
Packit Service a04d08
        'userdata': user_data,
Packit Service a04d08
        'metadata': {
Packit Service a04d08
            'instance-id': meta_data['vm']['_id'],
Packit Service a04d08
            'local-hostname': meta_data['vm']['name'],
Packit Service a04d08
            'public-keys': []
Packit Service a04d08
        },
Packit Service a04d08
        'gratuitous_arp': [
Packit Service a04d08
            {
Packit Service a04d08
                "source": ip["address"],
Packit Service a04d08
                "destination": target
Packit Service a04d08
            }
Packit Service a04d08
            for netadp in meta_data['netadp']
Packit Service a04d08
            for ip in netadp['ip']
Packit Service a04d08
            for target in [
Packit Service a04d08
                netadp['network']["gateway"],
Packit Service a04d08
                int2ip(ip2int(netadp['network']["gateway"]) + 2),
Packit Service a04d08
                int2ip(ip2int(netadp['network']["gateway"]) + 3)
Packit Service a04d08
            ]
Packit Service a04d08
        ],
Packit Service a04d08
        'cfg': {
Packit Service a04d08
            'ssh_pwauth': True,
Packit Service a04d08
            'disable_root': True,
Packit Service a04d08
            'system_info': {
Packit Service a04d08
                'default_user': {
Packit Service a04d08
                    'name': username,
Packit Service a04d08
                    'gecos': username,
Packit Service a04d08
                    'sudo': ['ALL=(ALL) NOPASSWD:ALL'],
Packit Service a04d08
                    'passwd': hash,
Packit Service a04d08
                    'lock_passwd': False,
Packit Service a04d08
                    'ssh_authorized_keys': ssh_keys,
Packit Service a04d08
                }
Packit Service a04d08
            },
Packit Service a04d08
            'network_config': network,
Packit Service a04d08
            'manage_etc_hosts': get_manage_etc_hosts(),
Packit Service a04d08
        },
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
    LOG.debug('returning DATA object:')
Packit Service a04d08
    LOG.debug(data)
Packit Service a04d08
Packit Service a04d08
    return data
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class DataSourceRbxCloud(sources.DataSource):
Packit Service a04d08
    dsname = "RbxCloud"
Packit Service a04d08
    update_events = {'network': [
Packit Service a04d08
        EventType.BOOT_NEW_INSTANCE,
Packit Service a04d08
        EventType.BOOT
Packit Service a04d08
    ]}
Packit Service a04d08
Packit Service a04d08
    def __init__(self, sys_cfg, distro, paths):
Packit Service a04d08
        sources.DataSource.__init__(self, sys_cfg, distro, paths)
Packit Service a04d08
        self.seed = None
Packit Service a04d08
Packit Service a04d08
    def __str__(self):
Packit Service a04d08
        root = sources.DataSource.__str__(self)
Packit Service a04d08
        return "%s [seed=%s]" % (root, self.seed)
Packit Service a04d08
Packit Service a04d08
    def _get_data(self):
Packit Service a04d08
        """
Packit Service a04d08
        Metadata is passed to the launching instance which
Packit Service a04d08
        is used to perform instance configuration.
Packit Service a04d08
        """
Packit Service a04d08
        rbx_data = get_md()
Packit Service a04d08
        self.userdata_raw = rbx_data['userdata']
Packit Service a04d08
        self.metadata = rbx_data['metadata']
Packit Service a04d08
        self.gratuitous_arp = rbx_data['gratuitous_arp']
Packit Service a04d08
        self.cfg = rbx_data['cfg']
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def network_config(self):
Packit Service a04d08
        return self.cfg['network_config']
Packit Service a04d08
Packit Service a04d08
    def get_public_ssh_keys(self):
Packit Service a04d08
        return self.metadata['public-keys']
Packit Service a04d08
Packit Service a04d08
    def get_userdata_raw(self):
Packit Service a04d08
        return self.userdata_raw
Packit Service a04d08
Packit Service a04d08
    def get_config_obj(self):
Packit Service a04d08
        return self.cfg
Packit Service a04d08
Packit Service a04d08
    def activate(self, cfg, is_new_instance):
Packit Service a04d08
        gratuitous_arp(self.gratuitous_arp, self.distro)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Used to match classes to dependencies
Packit Service a04d08
datasources = [
Packit Service a04d08
    (DataSourceRbxCloud, (sources.DEP_FILESYSTEM,)),
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# Return a list of data sources that match this set of dependencies
Packit Service a04d08
def get_datasource_list(depends):
Packit Service a04d08
    return sources.list_from_depends(depends, datasources)