|
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)
|