Import nova driver, rootwrap config, and tests from nova

This commit is contained in:
Russell Bryant 2014-03-12 06:32:21 -04:00
parent 9e827fcaa1
commit 99be0391b8
14 changed files with 1897 additions and 0 deletions

View File

@ -0,0 +1,6 @@
# nova-rootwrap command filters for setting up network in the docker driver
# This file should be owned by (and only-writeable by) the root user
[Filters]
# nova/virt/docker/driver.py: 'ln', '-sf', '/var/run/netns/.*'
ln: CommandFilter, /bin/ln, root

View File

View File

View File

@ -0,0 +1,168 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 time
import uuid
from nova.openstack.common import timeutils
import nova.virt.docker.client
class MockClient(object):
def __init__(self, endpoint=None):
self._containers = {}
self.name = None
def _fake_id(self):
return uuid.uuid4().hex + uuid.uuid4().hex
def _is_daemon_running(self):
return True
@nova.virt.docker.client.filter_data
def list_containers(self, _all=True):
containers = []
for container_id in self._containers.iterkeys():
containers.append({
'Status': 'Exit 0',
'Created': int(time.time()),
'Image': 'ubuntu:12.04',
'Ports': '',
'Command': 'bash ',
'Id': container_id
})
return containers
def create_container(self, args, name):
self.name = name
data = {
'Hostname': '',
'User': '',
'Memory': 0,
'MemorySwap': 0,
'AttachStdin': False,
'AttachStdout': False,
'AttachStderr': False,
'PortSpecs': None,
'Tty': True,
'OpenStdin': True,
'StdinOnce': False,
'Env': None,
'Cmd': [],
'Dns': None,
'Image': None,
'Volumes': {},
'VolumesFrom': ''
}
data.update(args)
container_id = self._fake_id()
self._containers[container_id] = {
'id': container_id,
'running': False,
'config': args
}
return container_id
def start_container(self, container_id):
if container_id not in self._containers:
return False
self._containers[container_id]['running'] = True
return True
@nova.virt.docker.client.filter_data
def inspect_image(self, image_name):
return {'container_config': {'Cmd': None}}
@nova.virt.docker.client.filter_data
def inspect_container(self, container_id):
if container_id not in self._containers:
return
container = self._containers[container_id]
info = {
'Args': [],
'Config': container['config'],
'Created': str(timeutils.utcnow()),
'ID': container_id,
'Image': self._fake_id(),
'NetworkSettings': {
'Bridge': '',
'Gateway': '',
'IPAddress': '',
'IPPrefixLen': 0,
'PortMapping': None
},
'Path': 'bash',
'ResolvConfPath': '/etc/resolv.conf',
'State': {
'ExitCode': 0,
'Ghost': False,
'Pid': 0,
'Running': container['running'],
'StartedAt': str(timeutils.utcnow())
},
'SysInitPath': '/tmp/docker',
'Volumes': {},
}
return info
def stop_container(self, container_id, timeout=None):
if container_id not in self._containers:
return False
self._containers[container_id]['running'] = False
return True
def kill_container(self, container_id):
if container_id not in self._containers:
return False
self._containers[container_id]['running'] = False
return True
def destroy_container(self, container_id):
if container_id not in self._containers:
return False
# Docker doesn't allow to destroy a running container.
if self._containers[container_id]['running']:
return False
del self._containers[container_id]
return True
def pull_repository(self, name):
return True
def push_repository(self, name, headers=None):
return True
def commit_container(self, container_id, name):
if container_id not in self._containers:
return False
return True
def get_container_logs(self, container_id):
if container_id not in self._containers:
return False
return '\n'.join([
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
'Vivamus ornare mi sit amet orci feugiat, nec luctus magna ',
'vehicula. Quisque diam nisl, dictum vitae pretium id, ',
'consequat eget sapien. Ut vehicula tortor non ipsum ',
'consectetur, at tincidunt elit posuere. In ut ligula leo. ',
'Donec eleifend accumsan mi, in accumsan metus. Nullam nec ',
'nulla eu risus vehicula porttitor. Sed purus ligula, ',
'placerat nec metus a, imperdiet viverra turpis. Praesent ',
'dapibus ornare massa. Nam ut hendrerit nunc. Interdum et ',
'malesuada fames ac ante ipsum primis in faucibus. ',
'Fusce nec pellentesque nisl.'])

View File

@ -0,0 +1,524 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 uuid
import mox
from nova.openstack.common import jsonutils
from nova import test
import nova.virt.docker.client
class FakeResponse(object):
def __init__(self, status, data='', headers=None):
self.status = status
self._data = data
self._headers = headers or {}
def read(self, _size=None):
return self._data
def getheader(self, key):
return self._headers.get(key)
class DockerHTTPClientTestCase(test.NoDBTestCase):
def test_list_containers(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.7/containers/ps?all=1',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='[]',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
containers = client.list_containers()
self.assertEqual([], containers)
self.mox.VerifyAll()
def test_create_container(self):
mock_conn = self.mox.CreateMockAnything()
expected_uuid = uuid.uuid4()
expected_body = jsonutils.dumps({
'Hostname': '',
'User': '',
'Memory': 0,
'MemorySwap': 0,
'AttachStdin': False,
'AttachStdout': False,
'AttachStderr': False,
'PortSpecs': [],
'Tty': True,
'OpenStdin': True,
'StdinOnce': False,
'Env': None,
'Cmd': [],
'Dns': None,
'Image': None,
'Volumes': {},
'VolumesFrom': '',
})
mock_conn.request('POST', '/v1.7/containers/create?name={0}'.format(
expected_uuid),
body=expected_body,
headers={'Content-Type': 'application/json'})
response = FakeResponse(201, data='{"id": "XXX"}',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
container_id = client.create_container({}, expected_uuid)
self.assertEqual('XXX', container_id)
self.mox.VerifyAll()
def test_create_container_with_args(self):
mock_conn = self.mox.CreateMockAnything()
expected_uuid = uuid.uuid4()
expected_body = jsonutils.dumps({
'Hostname': 'marco',
'User': '',
'Memory': 512,
'MemorySwap': 0,
'AttachStdin': False,
'AttachStdout': False,
'AttachStderr': False,
'PortSpecs': [],
'Tty': True,
'OpenStdin': True,
'StdinOnce': False,
'Env': None,
'Cmd': [],
'Dns': None,
'Image': 'example',
'Volumes': {},
'VolumesFrom': '',
})
mock_conn.request('POST', '/v1.7/containers/create?name={0}'.format(
expected_uuid),
body=expected_body,
headers={'Content-Type': 'application/json'})
response = FakeResponse(201, data='{"id": "XXX"}',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
args = {
'Hostname': 'marco',
'Memory': 512,
'Image': 'example',
}
container_id = client.create_container(args, expected_uuid)
self.assertEqual('XXX', container_id)
self.mox.VerifyAll()
def test_create_container_no_id_in_response(self):
mock_conn = self.mox.CreateMockAnything()
expected_uuid = uuid.uuid4()
mock_conn.request('POST', '/v1.7/containers/create?name={0}'.format(
expected_uuid),
body=mox.IgnoreArg(),
headers={'Content-Type': 'application/json'})
response = FakeResponse(201, data='{"ping": "pong"}',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
container_id = client.create_container({}, expected_uuid)
self.assertIsNone(container_id)
self.mox.VerifyAll()
def test_create_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
expected_uuid = uuid.uuid4()
mock_conn.request('POST', '/v1.7/containers/create?name={0}'.format(
expected_uuid),
body=mox.IgnoreArg(),
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
container_id = client.create_container({}, expected_uuid)
self.assertIsNone(container_id)
self.mox.VerifyAll()
def test_start_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/start',
body='{}',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.start_container('XXX'))
self.mox.VerifyAll()
def test_start_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/start',
body='{}',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.start_container('XXX'))
self.mox.VerifyAll()
def test_inspect_image(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.7/images/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='{"name": "XXX"}',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
image = client.inspect_image('XXX')
self.assertEqual({'name': 'XXX'}, image)
self.mox.VerifyAll()
def test_inspect_image_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.7/images/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
image = client.inspect_image('XXX')
self.assertIsNone(image)
self.mox.VerifyAll()
def test_inspect_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.7/containers/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='{"id": "XXX"}',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
container = client.inspect_container('XXX')
self.assertEqual({'id': 'XXX'}, container)
self.mox.VerifyAll()
def test_inspect_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('GET', '/v1.7/containers/XXX/json',
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
container = client.inspect_container('XXX')
self.assertIsNone(container)
self.mox.VerifyAll()
def test_stop_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/stop?t=5',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.stop_container('XXX'))
self.mox.VerifyAll()
def test_kill_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/kill',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.kill_container('XXX'))
self.mox.VerifyAll()
def test_stop_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/stop?t=5',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.stop_container('XXX'))
self.mox.VerifyAll()
def test_kill_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/containers/XXX/kill',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.kill_container('XXX'))
self.mox.VerifyAll()
def test_destroy_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('DELETE', '/v1.7/containers/XXX',
headers={'Content-Type': 'application/json'})
response = FakeResponse(204,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.destroy_container('XXX'))
self.mox.VerifyAll()
def test_destroy_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('DELETE', '/v1.7/containers/XXX',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.destroy_container('XXX'))
self.mox.VerifyAll()
def test_pull_repository(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/images/create?fromImage=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.pull_repository('ping'))
self.mox.VerifyAll()
def test_pull_repository_tag(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.7/images/create?fromImage=ping&tag=pong'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(200,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.pull_repository('ping:pong'))
self.mox.VerifyAll()
def test_pull_repository_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/images/create?fromImage=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.pull_repository('ping'))
self.mox.VerifyAll()
def test_push_repository(self):
mock_conn = self.mox.CreateMockAnything()
body = ('{"username":"foo","password":"bar",'
'"auth":"","email":"foo@bar.bar"}')
mock_conn.request('POST', '/v1.7/images/ping/push',
headers={'Content-Type': 'application/json'},
body=body)
response = FakeResponse(200,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.push_repository('ping'))
self.mox.VerifyAll()
def test_push_repository_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
body = ('{"username":"foo","password":"bar",'
'"auth":"","email":"foo@bar.bar"}')
mock_conn.request('POST', '/v1.7/images/ping/push',
headers={'Content-Type': 'application/json'},
body=body)
response = FakeResponse(400,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.push_repository('ping'))
self.mox.VerifyAll()
def test_commit_container(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/commit?container=XXX&repo=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(201,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(True, client.commit_container('XXX', 'ping'))
self.mox.VerifyAll()
def test_commit_container_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
mock_conn.request('POST', '/v1.7/commit?container=XXX&repo=ping',
headers={'Content-Type': 'application/json'})
response = FakeResponse(400,
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
self.assertEqual(False, client.commit_container('XXX', 'ping'))
self.mox.VerifyAll()
def test_get_container_logs(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.7/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(200, data='ping pong',
headers={'Content-Type': 'application/json'})
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
logs = client.get_container_logs('XXX')
self.assertEqual('ping pong', logs)
self.mox.VerifyAll()
def test_get_container_logs_bad_return_code(self):
mock_conn = self.mox.CreateMockAnything()
url = '/v1.7/containers/XXX/attach?logs=1&stream=0&stdout=1&stderr=1'
mock_conn.request('POST', url,
headers={'Content-Type': 'application/json'})
response = FakeResponse(404)
mock_conn.getresponse().AndReturn(response)
self.mox.ReplayAll()
client = nova.virt.docker.client.DockerHTTPClient(mock_conn)
logs = client.get_container_logs('XXX')
self.assertIsNone(logs)
self.mox.VerifyAll()

View File

@ -0,0 +1,219 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 contextlib
import socket
import mock
from nova import context
from nova import exception
from nova.openstack.common import jsonutils
from nova.openstack.common import units
from nova import test
from nova.tests import utils
import nova.tests.virt.docker.mock_client
from nova.tests.virt.test_virt_drivers import _VirtDriverTestCase
from nova.virt.docker import hostinfo
from nova.virt.docker import network
class DockerDriverTestCase(_VirtDriverTestCase, test.TestCase):
driver_module = 'nova.virt.docker.DockerDriver'
def setUp(self):
super(DockerDriverTestCase, self).setUp()
self.mock_client = nova.tests.virt.docker.mock_client.MockClient()
self.stubs.Set(nova.virt.docker.driver.DockerDriver, 'docker',
self.mock_client)
def fake_setup_network(self, instance, network_info):
return
self.stubs.Set(nova.virt.docker.driver.DockerDriver,
'_setup_network',
fake_setup_network)
def fake_get_registry_port(self):
return 5042
self.stubs.Set(nova.virt.docker.driver.DockerDriver,
'_get_registry_port',
fake_get_registry_port)
# Note: using mock.object.path on class throws
# errors in test_virt_drivers
def fake_teardown_network(container_id):
return
self.stubs.Set(network, 'teardown_network', fake_teardown_network)
self.context = context.RequestContext('fake_user', 'fake_project')
def test_driver_capabilities(self):
self.assertFalse(self.connection.capabilities['has_imagecache'])
self.assertFalse(self.connection.capabilities['supports_recreate'])
#NOTE(bcwaldon): This exists only because _get_running_instance on the
# base class will not let us set a custom disk/container_format.
def _get_running_instance(self, obj=False):
instance_ref = utils.get_test_instance(obj=obj)
network_info = utils.get_test_network_info()
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = \
'1.1.1.1'
image_info = utils.get_test_image_info(None, instance_ref)
image_info['disk_format'] = 'raw'
image_info['container_format'] = 'docker'
self.connection.spawn(self.ctxt, jsonutils.to_primitive(instance_ref),
image_info, [], 'herp', network_info=network_info)
return instance_ref, network_info
def test_get_host_stats(self):
self.mox.StubOutWithMock(socket, 'gethostname')
socket.gethostname().AndReturn('foo')
socket.gethostname().AndReturn('bar')
self.mox.ReplayAll()
self.assertEqual('foo',
self.connection.get_host_stats()['host_hostname'])
self.assertEqual('foo',
self.connection.get_host_stats()['host_hostname'])
def test_get_available_resource(self):
memory = {
'total': 4 * units.Mi,
'free': 3 * units.Mi,
'used': 1 * units.Mi
}
disk = {
'total': 50 * units.Gi,
'available': 25 * units.Gi,
'used': 25 * units.Gi
}
# create the mocks
with contextlib.nested(
mock.patch.object(hostinfo, 'get_memory_usage',
return_value=memory),
mock.patch.object(hostinfo, 'get_disk_usage',
return_value=disk)
) as (
get_memory_usage,
get_disk_usage
):
# run the code
stats = self.connection.get_available_resource(nodename='test')
# make our assertions
get_memory_usage.assert_called_once_with()
get_disk_usage.assert_called_once_with()
expected_stats = {
'vcpus': 1,
'vcpus_used': 0,
'memory_mb': 4,
'memory_mb_used': 1,
'local_gb': 50L,
'local_gb_used': 25L,
'disk_available_least': 25L,
'hypervisor_type': 'docker',
'hypervisor_version': 1000,
'hypervisor_hostname': 'test',
'cpu_info': '?',
'supported_instances': ('[["i686", "docker", "lxc"],'
' ["x86_64", "docker", "lxc"]]')
}
self.assertEqual(expected_stats, stats)
def test_plug_vifs(self):
# Check to make sure the method raises NotImplementedError.
self.assertRaises(NotImplementedError,
self.connection.plug_vifs,
instance=utils.get_test_instance(),
network_info=None)
def test_unplug_vifs(self):
# Check to make sure the method raises NotImplementedError.
self.assertRaises(NotImplementedError,
self.connection.unplug_vifs,
instance=utils.get_test_instance(),
network_info=None)
def test_create_container(self, image_info=None):
instance_href = utils.get_test_instance()
if image_info is None:
image_info = utils.get_test_image_info(None, instance_href)
image_info['disk_format'] = 'raw'
image_info['container_format'] = 'docker'
self.connection.spawn(self.context, instance_href, image_info,
'fake_files', 'fake_password')
self._assert_cpu_shares(instance_href)
self.assertEqual(self.mock_client.name, "nova-{0}".format(
instance_href['uuid']))
def test_create_container_vcpus_2(self, image_info=None):
flavor = utils.get_test_flavor(options={
'name': 'vcpu_2',
'flavorid': 'vcpu_2',
'vcpus': 2
})
instance_href = utils.get_test_instance(flavor=flavor)
if image_info is None:
image_info = utils.get_test_image_info(None, instance_href)
image_info['disk_format'] = 'raw'
image_info['container_format'] = 'docker'
self.connection.spawn(self.context, instance_href, image_info,
'fake_files', 'fake_password')
self._assert_cpu_shares(instance_href, vcpus=2)
self.assertEqual(self.mock_client.name, "nova-{0}".format(
instance_href['uuid']))
def _assert_cpu_shares(self, instance_href, vcpus=4):
container_id = self.connection._find_container_by_name(
instance_href['name']).get('id')
container_info = self.connection.docker.inspect_container(container_id)
self.assertEqual(vcpus * 1024, container_info['Config']['CpuShares'])
@mock.patch('nova.virt.docker.driver.DockerDriver._setup_network',
side_effect=Exception)
def test_create_container_net_setup_fails(self, mock_setup_network):
self.assertRaises(exception.InstanceDeployFailure,
self.test_create_container)
self.assertEqual(0, len(self.mock_client.list_containers()))
def test_create_container_wrong_image(self):
instance_href = utils.get_test_instance()
image_info = utils.get_test_image_info(None, instance_href)
image_info['disk_format'] = 'raw'
image_info['container_format'] = 'invalid_format'
self.assertRaises(exception.InstanceDeployFailure,
self.test_create_container,
image_info)
@mock.patch.object(network, 'teardown_network')
@mock.patch.object(nova.virt.docker.driver.DockerDriver,
'_find_container_by_name', return_value={'id': 'fake_id'})
def test_destroy_container(self, byname_mock, teardown_mock):
instance = utils.get_test_instance()
self.connection.destroy(self.context, instance, 'fake_networkinfo')
byname_mock.assert_called_once_with(instance['name'])
teardown_mock.assert_called_with('fake_id')
def test_get_memory_limit_from_sys_meta_in_object(self):
instance = utils.get_test_instance(obj=True)
limit = self.connection._get_memory_limit_bytes(instance)
self.assertEqual(2048 * units.Mi, limit)
def test_get_memory_limit_from_sys_meta_in_db_instance(self):
instance = utils.get_test_instance(obj=False)
limit = self.connection._get_memory_limit_bytes(instance)
self.assertEqual(2048 * units.Mi, limit)

View File

@ -0,0 +1,77 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 posix
import mock
from nova import test
from nova.virt.docker import hostinfo
class HostInfoTestCase(test.NoDBTestCase):
def setUp(self):
super(HostInfoTestCase, self).setUp()
hostinfo.get_meminfo = self.get_meminfo
hostinfo.statvfs = self.statvfs
def get_meminfo(self):
data = ['MemTotal: 1018784 kB\n',
'MemFree: 220060 kB\n',
'Buffers: 21640 kB\n',
'Cached: 63364 kB\n']
return data
def statvfs(self):
seq = (4096, 4096, 10047582, 7332259, 6820195,
2564096, 2271310, 2271310, 1024, 255)
return posix.statvfs_result(sequence=seq)
def test_get_disk_usage(self):
disk_usage = hostinfo.get_disk_usage()
self.assertEqual(disk_usage['total'], 41154895872)
self.assertEqual(disk_usage['available'], 27935518720)
self.assertEqual(disk_usage['used'], 11121963008)
def test_parse_meminfo(self):
meminfo = hostinfo.parse_meminfo()
self.assertEqual(meminfo['memtotal'], 1043234816)
self.assertEqual(meminfo['memfree'], 225341440)
self.assertEqual(meminfo['cached'], 64884736)
self.assertEqual(meminfo['buffers'], 22159360)
def test_get_memory_usage(self):
usage = hostinfo.get_memory_usage()
self.assertEqual(usage['total'], 1043234816)
self.assertEqual(usage['used'], 730849280)
self.assertEqual(usage['free'], 312385536)
@mock.patch('nova.virt.docker.hostinfo.get_mounts')
def test_find_cgroup_devices_path_centos(self, mock):
mock.return_value = [
'none /sys/fs/cgroup cgroup rw,relatime,perf_event,'
'blkio,net_cls,freezer,devices,memory,cpuacct,cpu,'
'cpuset 0 0']
path = hostinfo.get_cgroup_devices_path()
self.assertEqual('/sys/fs/cgroup', path)
@mock.patch('nova.virt.docker.hostinfo.get_mounts')
def test_find_cgroup_devices_path_ubuntu(self, mock):
mock.return_value = ['cgroup /cgroup tmpfs rw,relatime,mode=755 0 0',
'cgroup /cgroup/devices cgroup rw,relatime,devices,' +
'clone_children 0 0']
path = hostinfo.get_cgroup_devices_path()
self.assertEqual('/cgroup/devices', path)

View File

@ -0,0 +1,87 @@
# Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# 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 uuid
from nova import exception
from nova import test
from nova import utils
from nova.tests import utils as test_utils
from nova.openstack.common import processutils
from nova.virt.docker import network
import mock
class NetworkTestCase(test.NoDBTestCase):
@mock.patch.object(utils, 'execute')
def test_teardown_delete_network(self, utils_mock):
id = "second-id"
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
network.teardown_network(id)
utils_mock.assert_called_with('ip', 'netns', 'delete', id,
run_as_root=True)
@mock.patch.object(utils, 'execute')
def test_teardown_network_not_in_list(self, utils_mock):
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
network.teardown_network("not-in-list")
utils_mock.assert_called_with('ip', '-o', 'netns', 'list')
@mock.patch.object(network, 'LOG')
@mock.patch.object(utils, 'execute',
side_effect=processutils.ProcessExecutionError)
def test_teardown_network_fails(self, utils_mock, log_mock):
# Call fails but method should not fail.
# Error will be caught and logged.
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
id = "third-id"
network.teardown_network(id)
log_mock.warning.assert_called_with(mock.ANY, id)
def test_find_gateway(self):
instance = {'uuid': uuid.uuid4()}
network_info = test_utils.get_test_network_info()
first_net = network_info[0]['network']
first_net['subnets'][0]['gateway']['address'] = '10.0.0.1'
self.assertEqual('10.0.0.1', network.find_gateway(instance, first_net))
def test_cannot_find_gateway(self):
instance = {'uuid': uuid.uuid4()}
network_info = test_utils.get_test_network_info()
first_net = network_info[0]['network']
first_net['subnets'] = []
self.assertRaises(exception.InstanceDeployFailure,
network.find_gateway, instance, first_net)
def test_find_fixed_ip(self):
instance = {'uuid': uuid.uuid4()}
network_info = test_utils.get_test_network_info()
first_net = network_info[0]['network']
first_net['subnets'][0]['cidr'] = '10.0.0.0/24'
first_net['subnets'][0]['ips'][0]['type'] = 'fixed'
first_net['subnets'][0]['ips'][0]['address'] = '10.0.1.13'
self.assertEqual('10.0.1.13/24', network.find_fixed_ip(instance,
first_net))
def test_cannot_find_fixed_ip(self):
instance = {'uuid': uuid.uuid4()}
network_info = test_utils.get_test_network_info()
first_net = network_info[0]['network']
first_net['subnets'] = []
self.assertRaises(exception.InstanceDeployFailure,
network.find_fixed_ip, instance, first_net)

View File

View File

@ -0,0 +1,22 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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.
"""
:mod:`docker` -- Nova support for Docker Hypervisor to run Linux containers
===========================================================================
"""
from nova.virt.docker import driver
DockerDriver = driver.DockerDriver

View File

@ -0,0 +1,242 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 functools
import socket
from eventlet.green import httplib
import six
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def filter_data(f):
"""Decorator that post-processes data returned by Docker to avoid any
surprises with different versions of Docker
"""
@functools.wraps(f)
def wrapper(*args, **kwds):
out = f(*args, **kwds)
def _filter(obj):
if isinstance(obj, list):
new_list = []
for o in obj:
new_list.append(_filter(o))
obj = new_list
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(k, six.string_types):
obj[k.lower()] = _filter(v)
return obj
return _filter(out)
return wrapper
class Response(object):
def __init__(self, http_response, url=None):
self.url = url
self._response = http_response
self.code = int(http_response.status)
self.data = http_response.read()
self._json = None
def read(self, size=None):
return self._response.read(size)
def to_json(self, default=None):
if not self._json:
self._json = self._decode_json(self.data, default)
return self._json
def _validate_content_type(self):
# Docker does not return always the correct Content-Type.
# Lets try to parse the response anyway since json is requested.
if self._response.getheader('Content-Type') != 'application/json':
LOG.debug(_("Content-Type of response is not application/json"
" (Docker bug?). Requested URL %s") % self.url)
@filter_data
def _decode_json(self, data, default=None):
if not data:
return default
self._validate_content_type()
# Do not catch ValueError or SyntaxError since that
# just hides the root cause of errors.
return jsonutils.loads(data)
class UnixHTTPConnection(httplib.HTTPConnection):
def __init__(self):
httplib.HTTPConnection.__init__(self, 'localhost')
self.unix_socket = '/var/run/docker.sock'
def connect(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(self.unix_socket)
self.sock = sock
class DockerHTTPClient(object):
def __init__(self, connection=None):
self._connection = connection
@property
def connection(self):
if self._connection:
return self._connection
else:
return UnixHTTPConnection()
def make_request(self, *args, **kwargs):
headers = {}
if 'headers' in kwargs and kwargs['headers']:
headers = kwargs['headers']
if 'Content-Type' not in headers:
headers['Content-Type'] = 'application/json'
kwargs['headers'] = headers
conn = self.connection
conn.request(*args, **kwargs)
return Response(conn.getresponse(), url=args[1])
def list_containers(self, _all=True):
resp = self.make_request(
'GET',
'/v1.7/containers/ps?all={0}'.format(int(_all)))
return resp.to_json(default=[])
def create_container(self, args, name):
data = {
'Hostname': '',
'User': '',
'Memory': 0,
'MemorySwap': 0,
'AttachStdin': False,
'AttachStdout': False,
'AttachStderr': False,
'PortSpecs': [],
'Tty': True,
'OpenStdin': True,
'StdinOnce': False,
'Env': None,
'Cmd': [],
'Dns': None,
'Image': None,
'Volumes': {},
'VolumesFrom': '',
}
data.update(args)
resp = self.make_request(
'POST',
'/v1.7/containers/create?name={0}'.format(name),
body=jsonutils.dumps(data))
if resp.code != 201:
return
obj = resp.to_json()
for k, v in obj.iteritems():
if k.lower() == 'id':
return v
def start_container(self, container_id):
resp = self.make_request(
'POST',
'/v1.7/containers/{0}/start'.format(container_id),
body='{}')
return (resp.code == 200)
def inspect_image(self, image_name):
resp = self.make_request(
'GET',
'/v1.7/images/{0}/json'.format(image_name))
if resp.code != 200:
return
return resp.to_json()
def inspect_container(self, container_id):
resp = self.make_request(
'GET',
'/v1.7/containers/{0}/json'.format(container_id))
if resp.code != 200:
return
return resp.to_json()
def stop_container(self, container_id):
timeout = 5
resp = self.make_request(
'POST',
'/v1.7/containers/{0}/stop?t={1}'.format(container_id, timeout))
return (resp.code == 204)
def kill_container(self, container_id):
resp = self.make_request(
'POST',
'/v1.7/containers/{0}/kill'.format(container_id))
return (resp.code == 204)
def destroy_container(self, container_id):
resp = self.make_request(
'DELETE',
'/v1.7/containers/{0}'.format(container_id))
return (resp.code == 204)
def pull_repository(self, name):
parts = name.rsplit(':', 1)
url = '/v1.7/images/create?fromImage={0}'.format(parts[0])
if len(parts) > 1:
url += '&tag={0}'.format(parts[1])
resp = self.make_request('POST', url)
while True:
buf = resp.read(1024)
if not buf:
# Image pull completed
break
return (resp.code == 200)
def push_repository(self, name, headers=None):
url = '/v1.7/images/{0}/push'.format(name)
# NOTE(samalba): docker requires the credentials fields even if
# they're not needed here.
body = ('{"username":"foo","password":"bar",'
'"auth":"","email":"foo@bar.bar"}')
resp = self.make_request('POST', url, headers=headers, body=body)
while True:
buf = resp.read(1024)
if not buf:
# Image push completed
break
return (resp.code == 200)
def commit_container(self, container_id, name):
parts = name.rsplit(':', 1)
url = '/v1.7/commit?container={0}&repo={1}'.format(container_id,
parts[0])
if len(parts) > 1:
url += '&tag={0}'.format(parts[1])
resp = self.make_request('POST', url)
return (resp.code == 201)
def get_container_logs(self, container_id):
resp = self.make_request(
'POST',
('/v1.7/containers/{0}/attach'
'?logs=1&stream=0&stdout=1&stderr=1').format(container_id))
if resp.code != 200:
return
return resp.data

View File

@ -0,0 +1,417 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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.
"""
A Docker Hypervisor which allows running Linux Containers instead of VMs.
"""
import os
import random
import socket
import time
from oslo.config import cfg
from nova.compute import flavors
from nova.compute import power_state
from nova.compute import task_states
from nova import exception
from nova.image import glance
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log
from nova.openstack.common import units
from nova import utils
import nova.virt.docker.client
from nova.virt.docker import hostinfo
from nova.virt.docker import network
from nova.virt import driver
docker_opts = [
cfg.IntOpt('registry_default_port',
default=5042,
help=_('Default TCP port to find the '
'docker-registry container'),
deprecated_group='DEFAULT',
deprecated_name='docker_registry_default_port'),
]
CONF = cfg.CONF
CONF.register_opts(docker_opts, 'docker')
CONF.import_opt('my_ip', 'nova.netconf')
LOG = log.getLogger(__name__)
class DockerDriver(driver.ComputeDriver):
"""Docker hypervisor driver."""
def __init__(self, virtapi):
super(DockerDriver, self).__init__(virtapi)
self._docker = None
@property
def docker(self):
if self._docker is None:
self._docker = nova.virt.docker.client.DockerHTTPClient()
return self._docker
def init_host(self, host):
LOG.warning(_('The docker driver does not meet the Nova project\'s '
'requirements for quality verification and is planned '
'for removal. This may change, but users should plan '
'accordingly. Additional details here: '
'https://wiki.openstack.org/wiki/HypervisorSupportMatrix'
'/DeprecationPlan'))
if self._is_daemon_running() is False:
raise exception.NovaException(_('Docker daemon is not running or '
'is not reachable (check the rights on /var/run/docker.sock)'))
def _is_daemon_running(self):
try:
self.docker.list_containers()
return True
except socket.error:
# NOTE(samalba): If the daemon is not running, we'll get a socket
# error. The list_containers call is safe to call often, there
# is an internal hard limit in docker if the amount of containers
# is huge.
return False
def list_instances(self, inspect=False):
res = []
for container in self.docker.list_containers():
info = self.docker.inspect_container(container['id'])
if inspect:
res.append(info)
else:
res.append(info['Config'].get('Hostname'))
return res
def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks."""
msg = _("VIF plugging is not supported by the Docker driver.")
raise NotImplementedError(msg)
def unplug_vifs(self, instance, network_info):
"""Unplug VIFs from networks."""
msg = _("VIF unplugging is not supported by the Docker driver.")
raise NotImplementedError(msg)
def _find_container_by_name(self, name):
for info in self.list_instances(inspect=True):
if info['Config'].get('Hostname') == name:
return info
return {}
def get_info(self, instance):
container = self._find_container_by_name(instance['name'])
if not container:
raise exception.InstanceNotFound(instance_id=instance['name'])
running = container['State'].get('Running')
info = {
'max_mem': 0,
'mem': 0,
'num_cpu': 1,
'cpu_time': 0
}
info['state'] = power_state.RUNNING if running \
else power_state.SHUTDOWN
return info
def get_host_stats(self, refresh=False):
hostname = socket.gethostname()
memory = hostinfo.get_memory_usage()
disk = hostinfo.get_disk_usage()
stats = self.get_available_resource(hostname)
stats['hypervisor_hostname'] = stats['hypervisor_hostname']
stats['host_hostname'] = stats['hypervisor_hostname']
stats['host_name_label'] = stats['hypervisor_hostname']
return stats
def get_available_resource(self, nodename):
if not hasattr(self, '_nodename'):
self._nodename = nodename
if nodename != self._nodename:
LOG.error(_('Hostname has changed from %(old)s to %(new)s. '
'A restart is required to take effect.'
) % {'old': self._nodename,
'new': nodename})
memory = hostinfo.get_memory_usage()
disk = hostinfo.get_disk_usage()
stats = {
'vcpus': 1,
'vcpus_used': 0,
'memory_mb': memory['total'] / units.Mi,
'memory_mb_used': memory['used'] / units.Mi,
'local_gb': disk['total'] / units.Gi,
'local_gb_used': disk['used'] / units.Gi,
'disk_available_least': disk['available'] / units.Gi,
'hypervisor_type': 'docker',
'hypervisor_version': utils.convert_version_to_int('1.0'),
'hypervisor_hostname': self._nodename,
'cpu_info': '?',
'supported_instances': jsonutils.dumps([
('i686', 'docker', 'lxc'),
('x86_64', 'docker', 'lxc')
])
}
return stats
def _find_container_pid(self, container_id):
cgroup_path = hostinfo.get_cgroup_devices_path()
lxc_path = os.path.join(cgroup_path, 'lxc')
tasks_path = os.path.join(lxc_path, container_id, 'tasks')
n = 0
while True:
# NOTE(samalba): We wait for the process to be spawned inside the
# container in order to get the the "container pid". This is
# usually really fast. To avoid race conditions on a slow
# machine, we allow 10 seconds as a hard limit.
if n > 20:
return
try:
with open(tasks_path) as f:
pids = f.readlines()
if pids:
return int(pids[0].strip())
except IOError:
pass
time.sleep(0.5)
n += 1
def _setup_network(self, instance, network_info):
if not network_info:
return
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
return
network_info = network_info[0]['network']
netns_path = '/var/run/netns'
if not os.path.exists(netns_path):
utils.execute(
'mkdir', '-p', netns_path, run_as_root=True)
nspid = self._find_container_pid(container_id)
if not nspid:
msg = _('Cannot find any PID under container "{0}"')
raise RuntimeError(msg.format(container_id))
netns_path = os.path.join(netns_path, container_id)
utils.execute(
'ln', '-sf', '/proc/{0}/ns/net'.format(nspid),
'/var/run/netns/{0}'.format(container_id),
run_as_root=True)
rand = random.randint(0, 100000)
if_local_name = 'pvnetl{0}'.format(rand)
if_remote_name = 'pvnetr{0}'.format(rand)
bridge = network_info['bridge']
gateway = network.find_gateway(instance, network_info)
ip = network.find_fixed_ip(instance, network_info)
undo_mgr = utils.UndoManager()
try:
utils.execute(
'ip', 'link', 'add', 'name', if_local_name, 'type',
'veth', 'peer', 'name', if_remote_name,
run_as_root=True)
undo_mgr.undo_with(lambda: utils.execute(
'ip', 'link', 'delete', if_local_name, run_as_root=True))
# NOTE(samalba): Deleting the interface will delete all associated
# resources (remove from the bridge, its pair, etc...)
utils.execute(
'brctl', 'addif', bridge, if_local_name,
run_as_root=True)
utils.execute(
'ip', 'link', 'set', if_local_name, 'up',
run_as_root=True)
utils.execute(
'ip', 'link', 'set', if_remote_name, 'netns', nspid,
run_as_root=True)
utils.execute(
'ip', 'netns', 'exec', container_id, 'ifconfig',
if_remote_name, ip,
run_as_root=True)
utils.execute(
'ip', 'netns', 'exec', container_id,
'ip', 'route', 'replace', 'default', 'via', gateway, 'dev',
if_remote_name, run_as_root=True)
except Exception:
msg = _('Failed to setup the network, rolling back')
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
def _get_memory_limit_bytes(self, instance):
system_meta = utils.instance_sys_meta(instance)
return int(system_meta.get('instance_type_memory_mb', 0)) * units.Mi
def _get_image_name(self, context, instance, image):
fmt = image['container_format']
if fmt != 'docker':
msg = _('Image container format not supported ({0})')
raise exception.InstanceDeployFailure(msg.format(fmt),
instance_id=instance['name'])
registry_port = self._get_registry_port()
return '{0}:{1}/{2}'.format(CONF.my_ip,
registry_port,
image['name'])
def _get_default_cmd(self, image_name):
default_cmd = ['sh']
info = self.docker.inspect_image(image_name)
if not info:
return default_cmd
if not info['container_config']['Cmd']:
return default_cmd
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
image_name = self._get_image_name(context, instance, image_meta)
args = {
'Hostname': instance['name'],
'Image': image_name,
'Memory': self._get_memory_limit_bytes(instance),
'CpuShares': self._get_cpu_shares(instance)
}
default_cmd = self._get_default_cmd(image_name)
if default_cmd:
args['Cmd'] = default_cmd
container_id = self._create_container(instance, args)
if not container_id:
msg = _('Image name "{0}" does not exist, fetching it...')
LOG.info(msg.format(image_name))
res = self.docker.pull_repository(image_name)
if res is False:
raise exception.InstanceDeployFailure(
_('Cannot pull missing image'),
instance_id=instance['name'])
container_id = self._create_container(instance, args)
if not container_id:
raise exception.InstanceDeployFailure(
_('Cannot create container'),
instance_id=instance['name'])
self.docker.start_container(container_id)
try:
self._setup_network(instance, network_info)
except Exception as e:
msg = _('Cannot setup network: {0}')
self.docker.kill_container(container_id)
self.docker.destroy_container(container_id)
raise exception.InstanceDeployFailure(msg.format(e),
instance_id=instance['name'])
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True):
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
return
self.docker.stop_container(container_id)
self.docker.destroy_container(container_id)
network.teardown_network(container_id)
def cleanup(self, context, instance, network_info, block_device_info=None,
destroy_disks=True):
"""Cleanup after instance being destroyed by Hypervisor."""
pass
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
return
if not self.docker.stop_container(container_id):
LOG.warning(_('Cannot stop the container, '
'please check docker logs'))
if not self.docker.start_container(container_id):
LOG.warning(_('Cannot restart the container, '
'please check docker logs'))
def power_on(self, context, instance, network_info, block_device_info):
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
return
self.docker.start_container(container_id)
def power_off(self, instance):
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
return
self.docker.stop_container(container_id)
def get_console_output(self, context, instance):
container_id = self._find_container_by_name(instance.name).get('id')
if not container_id:
return
return self.docker.get_container_logs(container_id)
def _get_registry_port(self):
default_port = CONF.docker.registry_default_port
registry = None
for container in self.docker.list_containers(_all=False):
container = self.docker.inspect_container(container['id'])
if 'docker-registry' in container['Path']:
registry = container
break
if not registry:
return default_port
# NOTE(samalba): The registry service always binds on port 5000 in the
# container
try:
return container['NetworkSettings']['PortMapping']['Tcp']['5000']
except (KeyError, TypeError):
# NOTE(samalba): Falling back to a default port allows more
# flexibility (run docker-registry outside a container)
return default_port
def snapshot(self, context, instance, image_href, update_task_state):
container_id = self._find_container_by_name(instance['name']).get('id')
if not container_id:
raise exception.InstanceNotRunning(instance_id=instance['uuid'])
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
(image_service, image_id) = glance.get_remote_image_service(
context, image_href)
image = image_service.show(context, image_id)
registry_port = self._get_registry_port()
name = image['name']
default_tag = (':' not in name)
name = '{0}:{1}/{2}'.format(CONF.my_ip,
registry_port,
name)
commit_name = name if not default_tag else name + ':latest'
self.docker.commit_container(container_id, commit_name)
update_task_state(task_state=task_states.IMAGE_UPLOADING,
expected_state=task_states.IMAGE_PENDING_UPLOAD)
headers = {'X-Meta-Glance-Image-Id': image_href}
self.docker.push_repository(name, headers=headers)
def _get_cpu_shares(self, instance):
"""Get allocated CPUs from configured flavor.
Docker/lxc supports relative CPU allocation.
cgroups specifies following:
/sys/fs/cgroup/lxc/cpu.shares = 1024
/sys/fs/cgroup/cpu.shares = 1024
For that reason we use 1024 as multiplier.
This multiplier allows to divide the CPU
resources fair with containers started by
the user (e.g. docker registry) which has
the default CpuShares value of zero.
"""
flavor = flavors.extract_flavor(instance)
return int(flavor['vcpus']) * 1024
def _create_container(self, instance, args):
name = "nova-" + instance['uuid']
return self.docker.create_container(args, name)

View File

@ -0,0 +1,83 @@
# Copyright (c) 2013 dotCloud, Inc.
# All Rights Reserved.
#
# 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 os
def statvfs():
docker_path = '/var/lib/docker'
if not os.path.exists(docker_path):
docker_path = '/'
return os.statvfs(docker_path)
def get_meminfo():
with open('/proc/meminfo') as f:
return f.readlines()
def get_disk_usage():
# This is the location where Docker stores its containers. It's currently
# hardcoded in Docker so it's not configurable yet.
st = statvfs()
return {
'total': st.f_blocks * st.f_frsize,
'available': st.f_bavail * st.f_frsize,
'used': (st.f_blocks - st.f_bfree) * st.f_frsize
}
def parse_meminfo():
meminfo = {}
for ln in get_meminfo():
parts = ln.split(':')
if len(parts) < 2:
continue
key = parts[0].lower()
value = parts[1].strip()
parts = value.split(' ')
value = parts[0]
if not value.isdigit():
continue
value = int(parts[0])
if len(parts) > 1 and parts[1] == 'kB':
value *= 1024
meminfo[key] = value
return meminfo
def get_memory_usage():
meminfo = parse_meminfo()
total = meminfo.get('memtotal', 0)
free = meminfo.get('memfree', 0)
free += meminfo.get('cached', 0)
free += meminfo.get('buffers', 0)
return {
'total': total,
'free': free,
'used': total - free
}
def get_mounts():
with open('/proc/mounts') as f:
return f.readlines()
def get_cgroup_devices_path():
for ln in get_mounts():
fields = ln.split(' ')
if fields[2] == 'cgroup' and 'devices' in fields[3].split(','):
return fields[1]

View File

@ -0,0 +1,52 @@
# Copyright 2014 OpenStack Foundation
# All Rights Reserved.
#
# 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.
from nova import exception
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log
from nova.openstack.common import processutils
from nova import utils
LOG = log.getLogger(__name__)
def teardown_network(container_id):
try:
output, err = utils.execute('ip', '-o', 'netns', 'list')
for line in output.split('\n'):
if container_id == line.strip():
utils.execute('ip', 'netns', 'delete', container_id,
run_as_root=True)
break
except processutils.ProcessExecutionError:
LOG.warning(_('Cannot remove network namespace, netns id: %s'),
container_id)
def find_fixed_ip(instance, network_info):
for subnet in network_info['subnets']:
netmask = subnet['cidr'].split('/')[1]
for ip in subnet['ips']:
if ip['type'] == 'fixed' and ip['address']:
return ip['address'] + "/" + netmask
raise exception.InstanceDeployFailure(_('Cannot find fixed ip'),
instance_id=instance['uuid'])
def find_gateway(instance, network_info):
for subnet in network_info['subnets']:
return subnet['gateway']['address']
raise exception.InstanceDeployFailure(_('Cannot find gateway'),
instance_id=instance['uuid'])