Import nova driver, rootwrap config, and tests from nova
This commit is contained in:
parent
9e827fcaa1
commit
99be0391b8
|
@ -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
|
|
@ -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.'])
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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]
|
|
@ -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'])
|
Loading…
Reference in New Issue