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:
parent
5ee2ba5f4f
commit
008c9fcca0
@ -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
32
tobiko/podman/__init__.py
Normal 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
94
tobiko/podman/_client.py
Normal 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)
|
26
tobiko/podman/_exception.py
Normal file
26
tobiko/podman/_exception.py
Normal 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
43
tobiko/podman/_shell.py
Normal 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
16
tobiko/podman/config.py
Normal 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
|
0
tobiko/tests/functional/podman/__init__.py
Normal file
0
tobiko/tests/functional/podman/__init__.py
Normal file
71
tobiko/tests/functional/podman/test_client.py
Normal file
71
tobiko/tests/functional/podman/test_client.py
Normal 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)
|
@ -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
|
||||
|
0
tobiko/tests/unit/podman/__init__.py
Normal file
0
tobiko/tests/unit/podman/__init__.py
Normal file
60
tobiko/tests/unit/podman/_mocked_service.py
Normal file
60
tobiko/tests/unit/podman/_mocked_service.py
Normal 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
|
50
tobiko/tests/unit/podman/test_client.py
Normal file
50
tobiko/tests/unit/podman/test_client.py
Normal 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")
|
88
tobiko/tests/unit/podman/test_shell.py
Normal file
88
tobiko/tests/unit/podman/test_shell.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user