nova-lxd/nclxd/nova/virt/lxd/container.py

244 lines
8.9 KiB
Python

import os
import grp
import pwd
import uuid
import lxc
import tarfile
from oslo.config import cfg
from oslo.utils import importutils, units
from nova.i18n import _, _LW, _LE, _LI
from nova.openstack.common import fileutils
from nova.openstack.common import log as logging
from nova import utils
from nova.virt import images
from nova import objects
from nova import exception
from . import config
from . import vif
CONF = cfg.CONF
CONF.import_opt('vif_plugging_timeout', 'nova.virt.driver')
CONF.import_opt('vif_plugging_is_fatal', 'nova.virt.driver')
LOG = logging.getLogger(__name__)
MAX_CONSOLE_BYTES = 100 * units.Ki
def get_container_dir(instance):
return os.path.join(CONF.lxd.lxd_root_dir, instance, 'rootfs')
class Container(object):
def __init__(self, client, virtapi, firewall):
self.client = client
self.virtapi = virtapi
self.firewall_driver = firewall
self.container = None
self.idmap = LXCUserIdMap()
self.vif_driver = vif.LXDGenericDriver()
self.base_dir = os.path.join(CONF.instances_path,
CONF.image_cache_subdirectory_name)
def init_container(self):
lxc_cgroup = uuid.uuid4()
utils.execute('cgm', 'create', 'all', lxc_cgroup,
run_as_root=True)
utils.execute('cgm', 'chown', 'all', lxc_cgroup,
pwd.getpwuid(os.getuid()).pw_uid,
pwd.getpwuid(os.getuid()).pw_gid,
run_as_root=True)
utils.execute('cgm', 'movepid', 'all', lxc_cgroup, os.getpid())
def get_console_log(self, instance):
console_log = os.path.join(CONF.lxd.lxd_root_dir,
instance['uuid'],
'console.log')
with open(console_log, 'rb') as fp:
log_data, remaining = utils.last_bytes(fp, MAX_CONSOLE_BYTES)
if remaining > 0:
LOG.info(_LI('Truncated console log returned, '
'%d bytes ignored'),
remaining, instance=instance)
return log_data
def start_container(self, context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info, flavor):
LOG.info(_LI('Starting new instance'), instance=instance)
instance_name = instance['uuid']
self.container = lxc.Container(instance['uuid'])
self.container.set_config_path(CONF.lxd.lxd_root_dir)
''' Create the instance directories '''
self._create_container(instance_name)
''' Fetch the image from glance '''
self._fetch_image(context, instance)
''' Start the contianer '''
self._start_container(context, instance, network_info, image_meta)
def _create_container(self, instance):
if not os.path.exists(get_container_dir(instance)):
fileutils.ensure_tree(get_container_dir(instance))
if not os.path.exists(self.base_dir):
fileutils.ensure_tree(self.base_dir)
def _fetch_image(self, context, instance):
(user, group) = self.idmap.get_user()
image = os.path.join(self.base_dir, '%s.tar.gz' % instance['image_ref'])
if not os.path.exists(image):
images.fetch_to_raw(context, instance['image_ref'], image,
instance['user_id'], instance['project_id'])
if not tarfile.is_tarfile(image):
raise exception.NovaException(_('Not an valid image'))
utils.execute('tar', '--directory', get_container_dir(instance['uuid']),
'--anchored', '--numeric-owner', '-xpzf', image,
run_as_root=True, check_exit_code=[0, 2])
utils.execute('chown', '-R', '%s:%s' % (user, group),
get_container_dir(instance['uuid']), run_as_root=True)
def _start_container(self, context, instance, network_info, image_meta):
with utils.temporary_mutation(context, read_deleted="yes"):
flavor = objects.Flavor.get_by_id(context,
instance['instance_type_id'])
timeout = CONF.vif_plugging_timeout
# check to see if neutron is ready before
# doing anything else
if (not self.client.running(instance['uuid']) and
utils.is_neutron() and timeout):
events = self._get_neutron_events(network_info)
else:
events = {}
try:
with self.virtapi.wait_for_instance_event(
instance, events, deadline=timeout,
error_callback=self._neutron_failed_callback):
self._write_config(instance, network_info, image_meta, flavor)
self._start_network(instance, network_info)
self._start_firewall(instance, network_info)
self.client.start(instance['uuid'])
except exception.VirtualInterfaceCreateException:
LOG.info(_LW('Failed'))
def _write_config(self, instance, network_info, image_meta, flavor):
template = config.LXDConfigTemplate(instance['uuid'], image_meta)
template.set_config()
self.container.load_config()
name = config.LXDConfigSetName(self.container, instance['uuid'])
name.set_config()
rootfs = config.LXDConfigSetRoot(self.container, instance['uuid'])
rootfs.set_config()
logpath = config.LXDConfigSetLog(self.container, instance['uuid'])
logpath.set_config()
console_log = config.LXDConfigConsole(self.container, instance['uuid'])
console_log.set_config()
idmap = config.LXDUserConfig(self.container, self.idmap)
idmap.set_config()
limit = config.LXDSetLimits(self.container, instance)
limit.set_config()
self.container.save_config()
def _start_network(self, instance, network_info):
for vif in network_info:
self.vif_driver.plug(instance, vif)
def teardown_network(self, instance, network_info):
for vif in network_info:
self.vif_driver.unplug(instancece, vif)
self._stop_firewall(instance, network_info)
def _start_firewall(self, instance, network_info):
self.firewall_driver.setup_basic_filtering(instance, network_info)
self.firewall_driver.prepare_instance_filter(instance, network_info)
self.firewall_driver.apply_instance_filter(instance, network_info)
def _stop_firewall(self, instnce, network_inf):
self.firewall_driver.unfilter_instance(instance, network_info)
def _get_neutron_events(self, network_info):
return [('network-vif-plugged', vif['id'])
for vif in network_info if vif.get('active', True) is False]
def _neutron_failed_callback(self, event_name, instance):
LOG.error(_LE('Neutron Reported failure on event '
'%(event)s for instance %(uuid)s'),
{'event': event_name, 'uuid': instance.uuid})
if CONF.vif_plugging_is_fatal:
raise exception.VirtualInterfaceCreateException()
class LXCIdMap(object):
def __init__(self, ustart, unum, gstart, gnum):
self.ustart = int(ustart)
self.unum = int(unum)
self.gstart = int(gstart)
self.gnum = int(gnum)
def usernsexec_margs(self, with_read=None):
if with_read:
if with_read == "user":
with_read = os.getuid()
unum = self.unum - 1
rflag = ['-m', 'u:%s:%s:1' % (self.ustart + self.unum, with_read)]
print(
"================ rflag: %s ==================" %
(str(rflag)))
else:
unum = self.unum
rflag = []
return ['-m', 'u:0:%s:%s' % (self.ustart, unum),
'-m', 'g:0:%s:%s' % (self.gstart, self.gnum)] + rflag
def lxc_conf_lines(self):
return (('lxc.id_map', 'u 0 %s %s' % (self.ustart, self.unum)),
('lxc.id_map', 'g 0 %s %s' % (self.gstart, self.gnum)))
def get_user(self):
return (self.ustart, self.gstart)
class LXCUserIdMap(LXCIdMap):
def __init__(self, user=None, group=None, subuid_f="/etc/subuid",
subgid_f="/etc/subgid"):
if user is None:
user = pwd.getpwuid(os.getuid())[0]
if group is None:
group = grp.getgrgid(os.getgid()).gr_name
def parse_sfile(fname, name):
line = None
with open(fname, "r") as fp:
for cline in fp:
if cline.startswith(name + ":"):
line = cline
break
if line is None:
raise ValueError("%s not found in %s" % (name, fname))
toks = line.split(":")
return (toks[1], toks[2])
ustart, unum = parse_sfile(subuid_f, user)
gstart, gnum = parse_sfile(subgid_f, group)
self.user = user
self.group = group
super(LXCUserIdMap, self).__init__(ustart, unum, gstart, gnum)