Adjust changes for nclxd.

Migrate the nclxd to use LXD exclusivily.

Signed-off-by: Chuck Short <chuck.short@canonical.com>
This commit is contained in:
Chuck Short 2015-03-25 10:46:42 -04:00
parent 7c38728f2b
commit d035e70270
7 changed files with 211 additions and 194 deletions

View File

@ -13,8 +13,8 @@ set +o xtrace
LXD_DIR=$DEST/nova-compute-lxd
LXD_BIN_DIR=$GOPATH/src/github.com/lxc/lxd
LXC_REPO=${LXC_REPO:-${GIT_BASE}/zulcss/pylxd}
LXC_DIR=$DEST/pylxd
#LXC_REPO=${LXC_REPO:-${GIT_BASE}/zulcss/pylxd}
#LXC_DIR=$DEST/pylxd
function configure_lxd {
@ -27,8 +27,8 @@ function configure_lxd {
}
function install_lxd {
git_clone $LXC_REPO $LXC_DIR
setup_develop $LXC_DIR
# git_clone $LXC_REPO $LXC_DIR
# setup_develop $LXC_DIR
setup_develop $LXD_DIR
mkdir -p /etc/nova/rootwrap.d

View File

@ -1,3 +1,17 @@
# Copyright (c) 2015 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import httplib
import json
import socket
@ -62,14 +76,36 @@ class Client(object):
def container_init(self, config):
(status, data) = self._make_request('POST', '/1.0/containers',
json.dumps(config))
print data
return (status, data)
def container_start(self, name):
action = {'action': 'start', 'force': True}
(status, data) = self._make_request('PUT', '/1.0/containers/%s/state'
% name, json.dumps(action))
print data
return (status, data)
def container_restart(self, name):
action = {'action': 'restart', 'force': True}
(status, data) = self._make_request('PUT', '/1.0/containers/%s/state'
% name, json.dumps(action))
return (status, data)
def container_stop(self, name):
action = {'action': 'stop', 'force': True}
(status, data) = self._make_request('PUT', '/1.0/containers/%s/state'
% name, json.dumps(action))
return (status, data)
def container_suspend(self, name):
action = {'action': 'freeze', 'force': True}
(status, data) = self._make_request('PUT', '/1.0/containers/%s/state'
% name, json.dumps(action))
return (status, data)
def container_resume(self, name):
ction = {'action': 'unfreeze', 'force': True}
(status, data) = self._make_request('PUT', '/1.0/containers/%s/state'
% name, json.dumps(action))
return (status, data)
def container_delete(self, name):
@ -77,6 +113,11 @@ class Client(object):
% name)
return (status, data)
def container_update(self, name, config):
(status, data) = self._make_request('PUT', '/1.0/containers/%s'
% name, json.dumps(config))
return (status, data)
# profiles
def profile_list(self):
(status, data) = self._make_request('GET', '/1.0/profiles')
@ -91,7 +132,7 @@ class Client(object):
def profile_update(self, name, config):
(status, data) = self._make_request('PUT', '/1.0/profiles/%s' % name,
json.dumps(config))
reutrn (status, data)
return (status, data)
def profile_show(self, name):
(status, data) = self._make_request('GET', '/1.0/profiles/%s' % name)
@ -134,3 +175,13 @@ class Client(object):
def alias_delete(self, name):
(status, data) = self._make_request('DELETE', '/1.0/images/aliases/%s' % name)
return (status, data)
# operations
def operation_list(self):
(status, data) = self._make_request('GET', '/1.0/operations')
return [operation.split('/1.0/operations/')[-1]
for operation in data['metadata']['running']]
def operation_show(self, oid):
(status, data) = self._make_request('GET', '/1.0/operations/%s' % oid)
return (status, data)

View File

@ -1,90 +0,0 @@
import os
from oslo.config import cfg
from oslo_log import log as logging
from nova.i18n import _, _LW, _LE, _LI
from nova import utils
from nova import exception
from . import constants
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class ContainerConfig(object):
def __init__(self, client):
self.client = client
self.config = {}
self.resources = {}
self.devices = {}
def create_container_config(self, instance, network_info):
self.config = { 'name': instance.uuid,
'architecture': 'x86_64',
'ephemeral': True,
'hostname': instance.uuid,
'profiles': [],
}
self.config['source'] = {'type': 'image',
'alias': instance.image_ref}
self.config['devices'] = { 'eth0':
self._get_container_devices(network_info)}
self.config['config'] = {'raw.lxc': 'lxc.console.logfile = %s'
% self._get_console_path(instance),
'limits.memory': '%s' % self._get_memory_mb(instance),
'limits.cpus': '%s' % self._get_vcpus(instance)
}
LOG.debug(_('Creating container configuration'))
self._container_init(instance)
def _container_init(self, instance):
try:
(status, resp) = self.client.container_init(self.config)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to init container: %s') % resp.get('metadata'))
msg = _('Cannot init container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def _get_container_devices(self, network_info):
for vif in network_info:
vif_id = vif['id'][:11]
vif_type = vif['type']
bridge = vif['network']['bridge']
mac = vif['address']
if vif_type == 'ovs':
bridge = 'qbr%s' % vif_id
return {
'type': 'nic',
'parent': bridge,
'hwaddr': mac,
}
def _get_memory_mb(self, instance):
if instance.flavor is not None:
try:
memory_mb = '%sM' % int(instance.flavor.memory_mb)
except ValueError:
raise Exception('Failed to determine memory for container.')
return memory_mb
def _get_vcpus(self, instance):
if instance.flavor is not None:
try:
vcpus = instance.flavor.vcpus
except ValueError:
raise Exception('Failed to determine vcpus for container.')
return vcpus
def _get_console_path(self, instance):
return os.path.join(CONF.lxd.lxd_root_dir, instance.uuid, 'console.log')

View File

@ -1,18 +0,0 @@
from oslo.utils import units
from nova.compute import power_state
MAX_CONSOLE_BYTES = 100 * units.Ki
LXD_POWER_STATES = {
'RUNNING': power_state.RUNNING,
'STOPPED': power_state.SHUTDOWN,
'STARTING': power_state.BUILDING,
'STOPPING': power_state.SHUTDOWN,
'ABORTING': power_state.CRASHED,
'FREEZING': power_state.PAUSED,
'FROZEN': power_state.SUSPENDED,
'THAWED': power_state.PAUSED,
'PENDING': power_state.BUILDING,
'UNKNOWN': power_state.NOSTATE
}

View File

@ -12,10 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import pwd
from oslo.config import cfg
from oslo_log import log as logging
from oslo_utils import units
from nova.openstack.common import loopingcall
from nova.i18n import _, _LW, _LE
from nova import utils
@ -25,7 +29,6 @@ from nova.compute import power_state
from . import vif
from . import images
from . import config
CONF = cfg.CONF
CONF.import_opt('vif_plugging_timeout', 'nova.virt.driver')
@ -55,7 +58,6 @@ class Container(object):
self.client = client
self.virtapi = virtapi
self.image = images.ContainerImage(self.client)
self.config = config.ContainerConfig(self.client)
self.vif_driver = vif.LXDGenericDriver()
def init_host(self):
@ -70,8 +72,8 @@ class Container(object):
LOG.debug(_('Fetching image from Glance.'))
self.image.fetch_image(context, instance, image_meta)
LOG.debug(_('Writing LXD config'))
self.config.create_container_config(instance, network_info)
LOG.debug(_('Setting up container profiles'))
self.setup_container(instance, network_info)
LOG.debug(_('Setup Networking'))
self._start_network(instance, network_info)
@ -79,6 +81,103 @@ class Container(object):
LOG.debug(_('Start container'))
self._start_container(instance, network_info)
def setup_container(self, instance, network_info):
console_log = self._get_console_path(instance)
container_log = self._get_container_log(instance)
container = {'name': instance.uuid,
'source': {'type': 'image','alias': instance.image_ref}}
try:
(status, resp) = self.client.container_init(container)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to init container: %s') % resp)
msg = _('Cannot init container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
oid = resp.get('operation').split('/')[3]
if not oid:
msg = _('Unable to determine resource id')
raise exception.NovaException(msg)
timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_start,
oid)
timer.start(interval=0.5).wait()
network_type = self._get_container_devices(network_info)
container_config = {'config': {'raw.lxc': 'lxc.logfile = %s\nlxc.console.logfile=%s\n'
% (container_log,console_log)},
'devices': {'eth0': {'nictype': 'bridged',
'parent': network_type['parent'],
'hwaddr': network_type['hwaddr'],
'type': 'nic'}}}
try:
(status, resp) = self.client.container_update(instance.uuid, container_config)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to update container: %s') % resp)
msg = _('Cannot update container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_restart(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
try:
(status, resp) = self.client.container_restart(instance.uuid)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to restart container: %s') % resp)
msg = _('Cannot restart container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_power_on(self, instance, shutdown_timeout=0, shutdown_attempts=0):
try:
(status, resp) = self.client.container_stop(instance.uuid)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to power on container: %s') % resp)
msg = _('Cannot power on container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_power_off(self, context, instance, network_info, block_device_info):
try:
(status, resp) = self.client.container_stop(instance.uuid)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to power off container: %s') % resp)
msg = _('Cannot power on container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_suspend(self, instance):
try:
(status, resp) = self.client.container_suspend(instance.uuid)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to suspend container: %s') % resp)
msg = _('Cannot suspend container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_resume(self, context, instance, network_info, block_device_info=None):
try:
(status, resp) = self.client.container_resume(instance.uuid)
if resp.get('status') != 'OK':
raise exception.NovaException
except Exception as e:
LOG.debug(_('Failed to resume container: %s') % resp)
msg = _('Cannot suspend container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def container_destroy(self, context, instance, network_info, block_device_info,
destroy_disks, migrate_data):
try:
@ -90,6 +189,19 @@ class Container(object):
msg = _('Cannot delete container: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def get_console_log(self, instance):
console_dir = os.path.join(CONF.lxd.lxd_root_dir, instance.uuid)
console_log = self._get_console_path(instance)
uid = pwd.getpwuid(os.getuid()).pw_uid
utils.execute('chown', '%s:%s' % (uid, uid), console_log, run_as_root=True)
utils.execute('chmod', '755', console_dir, run_as_root=True)
with open(console_log, 'rb') as fp:
log_data, remaining = utils.last_bytes(fp, MAX_CONSOLE_BYTES)
if remaining > 0:
LOG.info(_('Truncated console log returned, '
'%d bytes ignored'),
remaining, instance=instance)
return log_data
def container_info(self, instance):
try:
@ -137,6 +249,11 @@ class Container(object):
for vif in network_info:
self.vif_driver.unplug(instance, vif)
def _wait_for_start(self, oid):
containers = self.client.operation_list()
if oid not in containers:
raise loopingcall.LoopingCallDone()
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]
@ -146,4 +263,26 @@ class Container(object):
'%(event)s for instance %(uuid)s'),
{'event': event_name, 'uuid': instance.uuid})
if CONF.vif_plugging_is_fatal:
raise exception.VirtualInterfaceCreateException()
raise exception.VirtualInterfaceCreateException()
def _get_console_path(self, instance):
return os.path.join(CONF.lxd.lxd_root_dir, instance.uuid, 'console.log')
def _get_container_log(self, instance):
return os.path.join(CONF.lxd.lxd_root_dir, instance.uuid, 'container.log')
def _get_container_devices(self, network_info):
for vif in network_info:
vif_id = vif['id'][:11]
vif_type = vif['type']
bridge = vif['network']['bridge']
mac = vif['address']
if vif_type == 'ovs':
bridge = 'qbr%s' % vif_id
return {
'type': 'nic',
'parent': bridge,
'hwaddr': mac,
}

View File

@ -93,7 +93,8 @@ class LXDDriver(driver.ComputeDriver):
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
pass
self.container.container_restart(context, instance, network_info, reboot_type,
block_device_info, bad_volumes_callback)
def rescue(self, context, instance, network_info, image_meta,
rescue_password):
@ -122,10 +123,10 @@ class LXDDriver(driver.ComputeDriver):
raise NotImplemented()
def power_off(self, instance, shutdown_timeout=0, shutdown_attempts=0):
pass
self.container.container_power_off(instance, shutdown_timeout, shutdown_attempts)
def power_on(self, context, instance, network_info, block_device_info):
pass
self.container.container_power_on(instance, network_info, block_device_info)
def soft_delete(self, instance):
pass
@ -140,10 +141,10 @@ class LXDDriver(driver.ComputeDriver):
pass
def suspend(self, instance):
raise NotImplemented()
return self.container.container_suspend(instance)
def resume(self, context, instance, network_info, block_device_info=None):
raise NotImplemented()
return self.container.container_resume(context, instance, network_info, block_device_info)
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None):

View File

@ -13,14 +13,10 @@
# under the License.
import os
import json
import time
import tempfile
import shutil
from oslo.config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from nova.i18n import _, _LE
from nova.openstack.common import fileutils
@ -39,7 +35,6 @@ class ContainerImage(object):
self.base_dir = os.path.join(CONF.instances_path,
CONF.image_cache_subdirectory_name)
self.workdir = tempfile.mkdtemp()
def fetch_image(self, context, instance, image_meta):
LOG.debug(_('Fetching image from glance'))
@ -56,8 +51,6 @@ class ContainerImage(object):
self._try_fetch_image(context, container_image, instance)
LOG.debug(_('Upload image to LXD'))
self._get_image_metadata(instance, image_meta)
self._update_image(instance, image_meta, container_image)
if instance.image_ref not in self.client.alias_list():
fingerprint = self._create_image(instance, container_image)
self._create_alias(instance, fingerprint)
@ -70,67 +63,7 @@ class ContainerImage(object):
except exception.ImageNotFound:
LOG.debug("Image %(image_id)s doesn't exist anymore on "
"image service, attempting to copy image ",
{'image_id': image_id})
def _get_image_metadata(self, instance, image_meta):
''' Generate LXD metadata understands '''
LOG.info(_('Generating metadata for LXD image'))
''' Extract the information from the glance image '''
variant = 'Default'
img_meta_prop = image_meta.get('properties', {}) if image_meta else {}
architecture = img_meta_prop.get('architecture', '')
if not architecture:
raise exception.NovaException(_('Unable to determine architecture.'))
os_distro = img_meta_prop.get('os_distro')
if not os_distro:
raise exception.NovaException(_('Unable to distribution.'))
os_version = img_meta_prop.get('os_version')
if not os_version:
raise exception.NovaException(_('Unable to determine version.'))
os_release = img_meta_prop.get('os_release')
if not os_release:
raise exception.NovaException(_('Unable to determine release '))
epoch = time.time()
self.metadata = {
'architecture': architecture,
'creation_date': int(epoch),
'properties': {
'os': os_distro,
'release': os_release,
'architecture': architecture,
'variant': variant,
'description': "%s %s %s Default (%s)" %
(os_distro,
os_release,
architecture,
os_version),
'name': instance['image_ref']
},
}
def _update_image(self,instance, image_meta, container_image):
rootfs_dir = os.path.join(self.workdir, 'rootfs')
fileutils.ensure_tree(rootfs_dir)
utils.execute('tar', '--anchored', '--numeric-owner', '-zxvf',
container_image, '-C', rootfs_dir,
run_as_root=True, check_exit_code=[0, 2])
metadata_yaml = json.dumps(self.metadata, sort_keys=True,
indent=4, separators=(',', ': '),
ensure_ascii=False).encode('utf-8') + b"\n"
metadata_file = os.path.join(self.workdir, 'metadata.yaml')
with open(metadata_file, 'w') as fp:
fp.write(metadata_yaml)
utils.execute('tar', '-C', self.workdir, '-zcvf',
container_image, 'metadata.yaml', 'rootfs',
run_as_root=True, check_exit_code=[0, 2])
{'image_id': instance.image_ref})
def _create_image(self, instance, container_image):
try:
@ -145,6 +78,7 @@ class ContainerImage(object):
msg = _('Cannot create image: {0}')
raise exception.NovaException(msg.format(e),
instance_id=instance.name)
def _create_alias(self, instance, fingerprint):
try:
LOG.debug(_('Creating LXD profile'))