Add docker client module

Change-Id: I27e87cc601b08fa44e7b4c1a0b8c007f4708b59c
This commit is contained in:
Federico Ressi 2019-10-10 12:20:02 +02:00
parent 88fb0ec424
commit 7dfeb04fcf
9 changed files with 305 additions and 0 deletions

View File

@ -1,6 +1,7 @@
# Tobiko framework requirements # Tobiko framework requirements
ansible>=2.4.0,<2.8.0 # GPLv3 ansible>=2.4.0,<2.8.0 # GPLv3
docker>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD fixtures>=3.0.0 # Apache-2.0/BSD
keystoneauth1>=3.4.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0
Jinja2>=2.8.0 # BSD Jinja2>=2.8.0 # BSD

31
tobiko/docker/__init__.py Normal file
View File

@ -0,0 +1,31 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import
from tobiko.docker import _client
from tobiko.docker import _shell
from tobiko.docker import _exception
DockerClientFixture = _client.DockerClientFixture
get_docker_client = _client.get_docker_client
list_docker_containers = _client.list_docker_containers
discover_docker_urls = _shell.discover_docker_urls
is_docker_running = _shell.is_docker_running
DockerError = _exception.DockerError
DockerUrlNotFoundError = _exception.DockerUrlNotFoundError

111
tobiko/docker/_client.py Normal file
View File

@ -0,0 +1,111 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import
import docker
import tobiko
from tobiko.docker import _exception
from tobiko.docker import _shell
from tobiko.shell import ssh
def get_docker_client(base_urls=None, ssh_client=None):
return DockerClientFixture(base_urls=base_urls,
ssh_client=ssh_client)
def list_docker_containers(client=None, **kwargs):
try:
containers = docker_client(client).containers.list(**kwargs)
except _exception.DockerUrlNotFoundError:
return tobiko.Selection()
else:
return tobiko.select(containers)
def docker_client(obj=None):
if obj is None:
obj = get_docker_client()
if tobiko.is_fixture(obj):
obj = tobiko.setup_fixture(obj).client
if isinstance(obj, docker.DockerClient):
return obj
raise TypeError('Cannot obtain a DockerClient from {!r}'.format(obj))
class DockerClientFixture(tobiko.SharedFixture):
base_urls = None
client = None
ssh_client = None
def __init__(self, base_urls=None, ssh_client=None):
super(DockerClientFixture, self).__init__()
if base_urls:
self.base_urls = list(base_urls)
if ssh_client:
self.ssh_client = ssh_client
def setup_fixture(self):
self.setup_ssh_client()
self.setup_base_urls()
self.setup_client()
def setup_ssh_client(self):
ssh_client = self.ssh_client
if ssh_client is None:
self.ssh_client = ssh_client = ssh.ssh_proxy_client() or False
if ssh_client:
tobiko.setup_fixture(ssh_client)
return ssh_client
def setup_base_urls(self):
base_urls = self.base_urls
if base_urls is None:
self.base_urls = base_urls = self.discover_docker_urls()
return base_urls
def setup_client(self):
client = self.client
if client is None:
self.client = client = self.create_client()
return client
def create_client(self):
exc_info = None
for base_url in self.base_urls:
if self.ssh_client:
base_url = ssh.get_port_forward_url(ssh_client=self.ssh_client,
url=base_url)
client = docker.DockerClient(base_url=base_url)
try:
client.ping()
except Exception:
exc_info = exc_info or tobiko.exc_info()
else:
return client
if exc_info:
exc_info.reraise()
else:
raise _exception.DockerError('Unable to create docker client')
def connect(self):
return tobiko.setup_fixture(self).client
def discover_docker_urls(self):
return _shell.discover_docker_urls(ssh_client=self.ssh_client)

View File

@ -0,0 +1,26 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import
import tobiko
class DockerError(tobiko.TobikoException):
message = '{error!}'
class DockerUrlNotFoundError(tobiko.TobikoException):
message = 'URL not found: {details}'

55
tobiko/docker/_shell.py Normal file
View File

@ -0,0 +1,55 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import
from tobiko.docker import _exception
from tobiko.shell import sh
def discover_docker_urls(**execute_params):
result = sh.execute('ps aux | grep dockerd', stdin=False, stdout=True,
stderr=True, expect_exit_status=None, **execute_params)
if result.exit_status or not result.stdout:
raise _exception.DockerUrlNotFoundError(details=result.stderr)
urls = []
for line in result.stdout.splitlines():
fields = line.strip().split()
if fields:
offset = 0
while True:
try:
offset = fields.index('-H', offset)
url = fields[offset + 1]
except (ValueError, IndexError):
break
else:
urls.append(url)
offset += 2
if not urls:
raise _exception.DockerUrlNotFoundError(details='\n' + result.stdout)
return urls
def is_docker_running(ssh_client=None, **execute_params):
try:
discover_docker_urls(ssh_client=ssh_client, **execute_params)
except _exception.DockerUrlNotFoundError:
return False
else:
return True

16
tobiko/docker/config.py Normal file
View File

@ -0,0 +1,16 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import

View File

@ -23,6 +23,7 @@ import six
from six.moves.urllib import parse from six.moves.urllib import parse
import tobiko import tobiko
from tobiko import docker
from tobiko.shell import ip from tobiko.shell import ip
from tobiko.shell import ping from tobiko.shell import ping
from tobiko.shell import sh from tobiko.shell import sh
@ -91,6 +92,8 @@ def set_default_openstack_topology_class(topology_class):
class OpenStackTopologyNode(object): class OpenStackTopologyNode(object):
_docker_client = None
def __init__(self, topology, name, public_ip, ssh_client): def __init__(self, topology, name, public_ip, ssh_client):
self._topology = weakref.ref(topology) self._topology = weakref.ref(topology)
self.name = name self.name = name
@ -109,6 +112,14 @@ class OpenStackTopologyNode(object):
def ssh_parameters(self): def ssh_parameters(self):
return self.ssh_client.setup_connect_parameters() return self.ssh_client.setup_connect_parameters()
@property
def docker_client(self):
docker_client = self._docker_client
if not docker_client:
self._docker_client = docker_client = docker.get_docker_client(
ssh_client=self.ssh_client)
return docker_client
def __repr__(self): def __repr__(self):
return "{cls!s}<name={name!r}>".format(cls=type(self).__name__, return "{cls!s}<name={name!r}>".format(cls=type(self).__name__,
name=self.name) name=self.name)

View File

@ -0,0 +1,54 @@
# Copyright (c) 2019 Red Hat, 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.
from __future__ import absolute_import
import testtools
from docker import client as docker_client
from docker.models import containers
from tobiko import docker
from tobiko.openstack import topology
class DockerClientTest(testtools.TestCase):
ssh_client = None
def setUp(self):
super(DockerClientTest, self).setUp()
for node in topology.list_openstack_nodes(group='controller'):
self.ssh_client = ssh_client = node.ssh_client
break
else:
self.skip('Any controller node found from OpenStack topology')
if not docker.is_docker_running(ssh_client=ssh_client):
self.skip('Docker server is not running')
def test_get_docker_client(self):
client = docker.get_docker_client(ssh_client=self.ssh_client)
self.assertIsInstance(client, docker.DockerClientFixture)
def test_connect_docker_client(self):
client = docker.get_docker_client(ssh_client=self.ssh_client).connect()
self.assertIsInstance(client, docker_client.DockerClient)
client.ping()
def test_list_docker_containers(self):
for container in docker.list_docker_containers(
ssh_client=self.ssh_client):
self.assertIsInstance(container, containers.Container)