Podman integration

Design and implement the podman client module

These changes introduce:
- the podman module
- the podman requirement setup to python-podman version 1.6.0
- related unit tests
- related functional tests

Change-Id: I9f81d086d66812ff3e7c2493d68e6cedb7f7d9bd
Co-authored-by: pkomarov <pkomarov@redhat.com>
This commit is contained in:
Hervé Beraud 2019-12-11 17:01:53 +01:00 committed by Federico Ressi
parent 5ee2ba5f4f
commit 008c9fcca0
13 changed files with 487 additions and 0 deletions

View File

@ -2,6 +2,7 @@
ansible>=2.4.0,<2.8.0 # GPLv3
docker>=4.0 # Apache-2.0
podman>=1.6.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
keystoneauth1>=3.4.0 # Apache-2.0
Jinja2>=2.8.0 # BSD

32
tobiko/podman/__init__.py Normal file
View File

@ -0,0 +1,32 @@
# 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.podman import _client
from tobiko.podman import _shell
from tobiko.podman import _exception
PodmanClientFixture = _client.PodmanClientFixture
get_podman_client = _client.get_podman_client
list_podman_containers = _client.list_podman_containers
podman_client = _client.podman_client
discover_podman_socket = _shell.discover_podman_socket
is_podman_running = _shell.is_podman_running
PodmanError = _exception.PodmanError
PodmanSocketNotFoundError = _exception.PodmanSocketNotFoundError

94
tobiko/podman/_client.py Normal file
View File

@ -0,0 +1,94 @@
# 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 six
import podman
import tobiko
from tobiko.podman import _exception
from tobiko.podman import _shell
from tobiko.shell import ssh
def get_podman_client(ssh_client=None):
return PodmanClientFixture(ssh_client=ssh_client)
def list_podman_containers(client=None, **kwargs):
try:
containers = podman_client(client).containers.list(**kwargs)
except _exception.PodmanSocketNotFoundError:
return tobiko.Selection()
else:
return tobiko.select(containers)
def podman_client(obj=None):
if obj is None:
obj = get_podman_client()
if tobiko.is_fixture(obj):
obj = tobiko.setup_fixture(obj).client
if isinstance(obj, podman.Client):
return obj
raise TypeError('Cannot obtain a Podman client from {!r}'.format(obj))
class PodmanClientFixture(tobiko.SharedFixture):
client = None
ssh_client = None
def __init__(self, ssh_client=None):
if six.PY2:
raise _exception.PodmanError(
"Podman isn't compatible with python 2.7")
super(PodmanClientFixture, self).__init__()
if ssh_client:
self.ssh_client = ssh_client
def setup_fixture(self):
self.setup_ssh_client()
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_client(self):
client = self.client
if client is None:
self.client = client = self.create_client()
return client
def create_client(self):
uri = self.discover_podman_socket()
if self.ssh_client:
uri = ssh.get_port_forward_url(ssh_client=self.ssh_client, url=uri)
client = podman.Client(uri=uri)
client.system.ping()
return client
def connect(self):
return tobiko.setup_fixture(self).client
def discover_podman_socket(self):
return _shell.discover_podman_socket(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 PodmanError(tobiko.TobikoException):
message = '{error!}'
class PodmanSocketNotFoundError(tobiko.TobikoException):
message = 'Socket not found: {details}'

43
tobiko/podman/_shell.py Normal file
View File

@ -0,0 +1,43 @@
# 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.podman import _exception
from tobiko.shell import sh
def discover_podman_socket(**execute_params):
cmd = "systemctl list-sockets | grep podman | awk '{print $1}'"
result = sh.execute(cmd, stdin=False, stdout=True, stderr=True,
expect_exit_status=None, **execute_params)
if result.exit_status or not result.stdout:
raise _exception.PodmanSocketNotFoundError(details=result.stderr)
try:
socket = result.stdout.splitlines()[0]
except IndexError:
raise _exception.PodmanSocketNotFoundError(details=result.stderr)
if '0 sockets listed' in socket:
raise _exception.PodmanSocketNotFoundError(details=socket)
return socket
def is_podman_running(ssh_client=None, **execute_params):
try:
discover_podman_socket(ssh_client=ssh_client, **execute_params)
except _exception.PodmanSocketNotFoundError:
return False
else:
return True

16
tobiko/podman/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

@ -0,0 +1,71 @@
# 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
import six
# We need to ignore this code under py2
# it's not compatible and parser will failed even if we use
# the `unittest.skipIf` decorator, because during the test discovery
# stestr and unittest will load this test
# module before running it and it will load podman
# too which isn't compatible in version leather than python 3
# Also the varlink mock module isn't compatible with py27, is using
# annotations syntaxe to generate varlink interface for the mocked service
# and it will raise related exceptions too.
# For all these reasons we can't run podman tests under a python 2 environment
if six.PY3:
from podman import client as podman_client
from podman.libs import containers
from tobiko import podman
from tobiko.openstack import topology
class PodmanClientTest(testtools.TestCase):
ssh_client = None
def setUp(self):
super(PodmanClientTest, 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 podman.is_podman_running(ssh_client=ssh_client):
self.skip('Podman server is not running')
def test_get_podman_client(self):
client = podman.get_podman_client(ssh_client=self.ssh_client)
self.assertIsInstance(client, podman.PodmanClientFixture)
def test_connect_podman_client(self):
client = podman.get_podman_client(
ssh_client=self.ssh_client).connect()
podman_clients_valid_types = (
podman_client.LocalClient,
podman_client.RemoteClient
)
self.assertIsInstance(client, podman_clients_valid_types)
client.ping()
def test_list_podman_containers(self):
for container in podman.list_podman_containers(
ssh_client=self.ssh_client):
self.assertIsInstance(container, containers.Container)

View File

@ -13,11 +13,17 @@
# under the License.
from __future__ import absolute_import
import six
from tobiko.tests.unit import _case
from tobiko.tests.unit import _patch
TobikoUnitTest = _case.TobikoUnitTest
if six.PY3:
from tobiko.tests.unit.podman import _mocked_service
mocked_service = _mocked_service
PatchFixture = _patch.PatchFixture
PatchMixin = _patch.PatchMixin

View File

View File

@ -0,0 +1,60 @@
types = """
type ListPodData (
id: string,
name: string,
createdat: string,
cgroup: string,
status: string,
labels: [string]string,
numberofcontainers: string,
containersinfo: []ListPodContainerInfo
)
type ListPodContainerInfo (
name: string,
id: string,
status: string
)
"""
class ServicePod:
def StartPod(self, name: str) -> str:
"""return pod"""
return { # type: ignore
"pod": "135d71b9495f7c3967f536edad57750bfd"
"b569336cd107d8aabab45565ffcfb6",
"name": name
}
def GetPod(self, name: str) -> str:
"""return pod: ListPodData"""
return { # type: ignore
"pod": {
"cgroup": "machine.slice",
"containersinfo": [
{
"id": "1840835294cf076a822e4e12ba4152411f131"
"bd869e7f6a4e8b16df9b0ea5c7f",
"name": "1840835294cf-infra",
"status": "running"
},
{
"id": "49a5cce72093a5ca47c6de86f10ad7bb36391e2"
"d89cef765f807e460865a0ec6",
"name": "upbeat_murdock",
"status": "running"
}
],
"createdat": "2018-12-07 13:10:15.014139258 -0600 CST",
"id": "135d71b9495f7c3967f536edad57750bfdb569336cd"
"107d8aabab45565ffcfb6",
"name": name,
"numberofcontainers": "2",
"status": "Running"
}
}
def GetVersion(self) -> str:
"""return version"""
return {"version": "testing"} # type: ignore

View File

@ -0,0 +1,50 @@
# Copyright 2018 Red Hat
#
# 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 mock
import six
# We need to ignore this code under py2
# it's not compatible and parser will failed even if we use
# the `unittest.skipIf` decorator, because during the test discovery
# stestr and unittest will load this test
# module before running it and it will load podman
# too which isn't compatible in version leather than python 3
# Also the varlink mock module isn't compatible with py27, is using
# annotations syntaxe to generate varlink interface for the mocked service
# and it will raise related exceptions too.
# For all these reasons we can't run podman tests under a python 2 environment
if six.PY3:
from tobiko import podman
from tobiko.tests import unit
from varlink import mock as varlink_mock
class TestPodmanClient(unit.TobikoUnitTest):
@varlink_mock.mockedservice(
fake_service=unit.mocked_service.ServicePod,
fake_types=unit.mocked_service.types,
name='io.podman',
address='unix:@podmantests'
)
@mock.patch(
'tobiko.podman._client.PodmanClientFixture.discover_podman_socket'
)
def test_init(self, mocked_discover_podman_socket):
mocked_discover_podman_socket.return_value = 'unix:@podmantests'
client = podman.get_podman_client().connect()
pods = client.pods.get('135d71b9495f')
self.assertEqual(pods["numberofcontainers"], "2")

View File

@ -0,0 +1,88 @@
# Copyright 2018 Red Hat
#
# 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 mock
import six
# We need to ignore this code under py2
# it's not compatible and parser will failed even if we use
# the `unittest.skipIf` decorator, because during the test discovery
# stestr and unittest will load this test
# module before running it and it will load podman
# too which isn't compatible in version leather than python 3
# Also the varlink mock module isn't compatible with py27, is using
# annotations syntaxe to generate varlink interface for the mocked service
# and it will raise related exceptions too.
# For all these reasons we can't run podman tests under a python 2 environment
if six.PY3:
from tobiko import podman
from tobiko.tests import unit
class TestShell(unit.TobikoUnitTest):
@mock.patch('tobiko.shell.sh.execute')
def test_discover_podman_socket(self, mock_execute):
class FakeProcess:
exit_status = 0
stdout = '/run/podman/io.podman'
stderr = ''
mock_execute.return_value = FakeProcess()
self.assertEqual(
podman.discover_podman_socket(),
'/run/podman/io.podman'
)
@mock.patch('tobiko.shell.sh.execute')
def test_discover_podman_socket_none_result(self, mock_execute):
class FakeProcess:
exit_status = 1
stdout = ''
stderr = 'boom'
mock_execute.return_value = FakeProcess()
self.assertRaises(
podman.PodmanSocketNotFoundError,
podman.discover_podman_socket
)
@mock.patch('tobiko.shell.sh.execute')
def test_discover_podman_socket_with_exit_code(self, mock_execute):
class FakeProcess:
exit_status = 0
stdout = ''
stderr = 'boom'
mock_execute.return_value = FakeProcess()
self.assertRaises(
podman.PodmanSocketNotFoundError,
podman.discover_podman_socket
)
@mock.patch('tobiko.shell.sh.execute')
def test_is_podman_running(self, mock_execute):
class FakeProcess:
exit_status = 0
stdout = '/run/podman/io.podman'
stderr = ''
mock_execute.return_value = FakeProcess()
self.assertEqual(podman.is_podman_running(), True)
@mock.patch('tobiko.shell.sh.execute')
def test_is_podman_running_without_socket(self, mock_execute):
class FakeProcess:
exit_status = 1
stdout = ''
stderr = 'boom'
mock_execute.return_value = FakeProcess()
self.assertEqual(podman.is_podman_running(), False)