The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/pylxd/pull/331
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Added support to query cluster members and specify a target for creation of containers in a cluster
From 676d0114f6622fe345a861f0c5cf3790e13f0f4b Mon Sep 17 00:00:00 2001 From: Felix Engelmann <fe-git...@nlogn.org> Date: Mon, 24 Sep 2018 15:44:06 +0200 Subject: [PATCH 1/5] cluster endpoint read support Signed-off-by: Felix Engelmann <fe-git...@nlogn.org> --- pylxd/client.py | 3 +++ pylxd/managers.py | 3 +++ pylxd/models/__init__.py | 1 + pylxd/models/node.py | 52 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 pylxd/models/node.py diff --git a/pylxd/client.py b/pylxd/client.py index e3558f40..90df3096 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -71,6 +71,8 @@ def __getattr__(self, name): # Special case for storage_pools which needs to become 'storage-pools' if name == 'storage_pools': name = 'storage-pools' + if name == 'nodes': + name = 'cluster/members' return self.__class__('{}/{}'.format(self._api_endpoint, name), cert=self.session.cert, verify=self.session.verify) @@ -296,6 +298,7 @@ def __init__( requests.exceptions.InvalidURL): raise exceptions.ClientConnectionFailed() + self.nodes = managers.NodeManager(self) self.certificates = managers.CertificateManager(self) self.containers = managers.ContainerManager(self) self.images = managers.ImageManager(self) diff --git a/pylxd/managers.py b/pylxd/managers.py index a7dbb7ef..40bc0c53 100644 --- a/pylxd/managers.py +++ b/pylxd/managers.py @@ -27,6 +27,9 @@ def __init__(self, *args, **kwargs): return super(BaseManager, self).__init__() +class NodeManager(BaseManager): + manager_for = 'pylxd.models.Node' + class CertificateManager(BaseManager): manager_for = 'pylxd.models.Certificate' diff --git a/pylxd/models/__init__.py b/pylxd/models/__init__.py index 74b82ad5..d2f89a21 100644 --- a/pylxd/models/__init__.py +++ b/pylxd/models/__init__.py @@ -1,3 +1,4 @@ +from pylxd.models.node import Node # NOQA from pylxd.models.certificate import Certificate # NOQA from pylxd.models.container import Container, Snapshot # NOQA from pylxd.models.image import Image # NOQA diff --git a/pylxd/models/node.py b/pylxd/models/node.py new file mode 100644 index 00000000..009bebf2 --- /dev/null +++ b/pylxd/models/node.py @@ -0,0 +1,52 @@ +# Copyright (c) 2016 Canonical Ltd +# +# 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 binascii + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import Encoding + +from pylxd.models import _model as model + + +class Node(model.Model): + """A LXD certificate.""" + + name = model.Attribute() + url = model.Attribute() + database = model.Attribute() + state = model.Attribute() + + @classmethod + def get(cls, client, name): + """Get a certificate by fingerprint.""" + response = client.api.nodes[name].get() + + return cls(client, **response.json()['metadata']) + + @classmethod + def all(cls, client): + """Get all certificates.""" + response = client.api.nodes.get() + + nodes = [] + for node in response.json()['metadata']: + name = node.split('/')[-1] + nodes.append(cls(client, name=name)) + return nodes + + @property + def api(self): + return self.client.api.nodes[self.name] From 420c0d9e1f88123193f1dffe4d86cf0230372bce Mon Sep 17 00:00:00 2001 From: Felix Engelmann <fe-git...@nlogn.org> Date: Mon, 24 Sep 2018 15:52:01 +0200 Subject: [PATCH 2/5] renamed node to more close cluster_member Signed-off-by: Felix Engelmann <fe-git...@nlogn.org> --- pylxd/client.py | 4 ++-- pylxd/managers.py | 4 ++-- pylxd/models/__init__.py | 2 +- pylxd/models/{node.py => cluster_member.py} | 15 ++++----------- 4 files changed, 9 insertions(+), 16 deletions(-) rename pylxd/models/{node.py => cluster_member.py} (76%) diff --git a/pylxd/client.py b/pylxd/client.py index 90df3096..dc60f0da 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -71,7 +71,7 @@ def __getattr__(self, name): # Special case for storage_pools which needs to become 'storage-pools' if name == 'storage_pools': name = 'storage-pools' - if name == 'nodes': + if name == 'cluster_members': name = 'cluster/members' return self.__class__('{}/{}'.format(self._api_endpoint, name), cert=self.session.cert, @@ -298,7 +298,7 @@ def __init__( requests.exceptions.InvalidURL): raise exceptions.ClientConnectionFailed() - self.nodes = managers.NodeManager(self) + self.cluster_members = managers.ClusterMemberManager(self) self.certificates = managers.CertificateManager(self) self.containers = managers.ContainerManager(self) self.images = managers.ImageManager(self) diff --git a/pylxd/managers.py b/pylxd/managers.py index 40bc0c53..11447807 100644 --- a/pylxd/managers.py +++ b/pylxd/managers.py @@ -27,8 +27,8 @@ def __init__(self, *args, **kwargs): return super(BaseManager, self).__init__() -class NodeManager(BaseManager): - manager_for = 'pylxd.models.Node' +class ClusterMemberManager(BaseManager): + manager_for = 'pylxd.models.ClusterMember' class CertificateManager(BaseManager): manager_for = 'pylxd.models.Certificate' diff --git a/pylxd/models/__init__.py b/pylxd/models/__init__.py index d2f89a21..56c8693d 100644 --- a/pylxd/models/__init__.py +++ b/pylxd/models/__init__.py @@ -1,4 +1,4 @@ -from pylxd.models.node import Node # NOQA +from pylxd.models.cluster_member import ClusterMember # NOQA from pylxd.models.certificate import Certificate # NOQA from pylxd.models.container import Container, Snapshot # NOQA from pylxd.models.image import Image # NOQA diff --git a/pylxd/models/node.py b/pylxd/models/cluster_member.py similarity index 76% rename from pylxd/models/node.py rename to pylxd/models/cluster_member.py index 009bebf2..96f7bd7c 100644 --- a/pylxd/models/node.py +++ b/pylxd/models/cluster_member.py @@ -11,17 +11,10 @@ # 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 binascii - -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.serialization import Encoding - from pylxd.models import _model as model -class Node(model.Model): +class ClusterMember(model.Model): """A LXD certificate.""" name = model.Attribute() @@ -32,14 +25,14 @@ class Node(model.Model): @classmethod def get(cls, client, name): """Get a certificate by fingerprint.""" - response = client.api.nodes[name].get() + response = client.api.cluster_members[name].get() return cls(client, **response.json()['metadata']) @classmethod def all(cls, client): """Get all certificates.""" - response = client.api.nodes.get() + response = client.api.cluster_members.get() nodes = [] for node in response.json()['metadata']: @@ -49,4 +42,4 @@ def all(cls, client): @property def api(self): - return self.client.api.nodes[self.name] + return self.client.api.cluster_members[self.name] From 5e79bae092de28f98ce1e711ca2b38845f8ff61a Mon Sep 17 00:00:00 2001 From: Felix Engelmann <fe-git...@nlogn.org> Date: Wed, 26 Sep 2018 00:10:01 +0200 Subject: [PATCH 3/5] Added functionality to specify target cluster member in containers.create Signed-off-by: Felix Engelmann <fe-git...@nlogn.org> --- pylxd/client.py | 10 +++++++++- pylxd/models/container.py | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pylxd/client.py b/pylxd/client.py index dc60f0da..9334fc93 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -153,7 +153,15 @@ def get(self, *args, **kwargs): def post(self, *args, **kwargs): """Perform an HTTP POST.""" kwargs['timeout'] = kwargs.get('timeout', self._timeout) - response = self.session.post(self._api_endpoint, *args, **kwargs) + target = kwargs.get('target', None) + kwargs.pop("target", None) + + if target is not None: + endpoint="{}?target={}".format(self._api_endpoint,target) + else: + endpoint = self._api_endpoint + + response = self.session.post(endpoint, *args, **kwargs) # Prior to LXD 2.0.3, successful synchronous requests returned 200, # rather than 201. self._assert_response(response, allowed_status_codes=(200, 201, 202)) diff --git a/pylxd/models/container.py b/pylxd/models/container.py index a985691f..11570fca 100644 --- a/pylxd/models/container.py +++ b/pylxd/models/container.py @@ -256,9 +256,9 @@ def all(cls, client): return containers @classmethod - def create(cls, client, config, wait=False): + def create(cls, client, config, wait=False, target=None): """Create a new container config.""" - response = client.api.containers.post(json=config) + response = client.api.containers.post(json=config, target=target) if wait: client.operations.wait_for_operation(response.json()['operation']) From 4423a1a9ac2be3498d299c444d88d20124d9bc6d Mon Sep 17 00:00:00 2001 From: Felix Engelmann <fe-git...@nlogn.org> Date: Mon, 8 Oct 2018 22:56:22 +0200 Subject: [PATCH 4/5] Added tests for Cluster Members and Targeted create container Signed-off-by: Felix Engelmann <fe-git...@nlogn.org> --- pylxd/client.py | 2 +- pylxd/managers.py | 1 + pylxd/tests/mock_lxd.py | 59 +++++++++++++++++++++++ pylxd/tests/models/test_cluster_member.py | 33 +++++++++++++ pylxd/tests/models/test_container.py | 10 ++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 pylxd/tests/models/test_cluster_member.py diff --git a/pylxd/client.py b/pylxd/client.py index 9334fc93..0293ba30 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -157,7 +157,7 @@ def post(self, *args, **kwargs): kwargs.pop("target", None) if target is not None: - endpoint="{}?target={}".format(self._api_endpoint,target) + endpoint = "{}?target={}".format(self._api_endpoint, target) else: endpoint = self._api_endpoint diff --git a/pylxd/managers.py b/pylxd/managers.py index 11447807..5ed834bd 100644 --- a/pylxd/managers.py +++ b/pylxd/managers.py @@ -30,6 +30,7 @@ def __init__(self, *args, **kwargs): class ClusterMemberManager(BaseManager): manager_for = 'pylxd.models.ClusterMember' + class CertificateManager(BaseManager): manager_for = 'pylxd.models.Certificate' diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py index c8c21efd..5139f44a 100644 --- a/pylxd/tests/mock_lxd.py +++ b/pylxd/tests/mock_lxd.py @@ -7,6 +7,12 @@ def containers_POST(request, context): 'type': 'async', 'operation': 'operation-abc'}) +def containers_remote_POST(request, context): + context.status_code = 202 + return json.dumps({ + 'type': 'async', + 'operation': 'operation-abc'}) + def container_POST(request, context): context.status_code = 202 @@ -192,6 +198,31 @@ def snapshot_DELETE(request, context): }, + # Cluster Members + { + 'text': json.dumps({ + 'type': 'sync', + 'metadata': [ + 'http://pylxd.test/1.0/certificates/an-member', + 'http://pylxd.test/1.0/certificates/nd-member', + ]}), + 'method': 'GET', + 'url': r'^http://pylxd.test/1.0/cluster/members$', + }, + { + 'text': json.dumps({ + 'type': 'sync', + 'metadata': { + "name": "an-member", + "url": "https://10.1.1.101:8443", + "database": "true", + "state": "Online", + }}), + 'method': 'GET', + 'url': r'^http://pylxd.test/1.0/cluster/members/an-member$', # NOQA + }, + + # Containers { 'text': json.dumps({ @@ -212,6 +243,11 @@ def snapshot_DELETE(request, context): 'method': 'POST', 'url': r'^http://pylxd.test/1.0/containers$', }, + { + 'text': containers_remote_POST, + 'method': 'POST', + 'url': r'^http://pylxd.test/1.0/containers\?target=an-remote', + }, { 'json': { 'type': 'sync', @@ -293,6 +329,29 @@ def snapshot_DELETE(request, context): 'method': 'GET', 'url': r'^http://pylxd.test/1.0/containers/an-container/state$', # NOQA }, + { + 'json': { + 'type': 'sync', + 'metadata': { + 'name': 'an-new-remote-container', + + 'architecture': "x86_64", + 'config': { + 'security.privileged': "true", + }, + 'created_at': "1983-06-16T00:00:00-00:00", + 'last_used_at': "1983-06-16T00:00:00-00:00", + 'description': "Some description", + 'location':"an-remote", + 'status': "Running", + 'status_code': 103, + 'unsupportedbypylxd': "This attribute is not supported by "\ + "pylxd. We want to test whether the mere presence of it "\ + "makes it crash." + }}, + 'method': 'GET', + 'url': r'^http://pylxd.test/1.0/containers/an-new-remote-container$', + }, { 'status_code': 202, 'json': { diff --git a/pylxd/tests/models/test_cluster_member.py b/pylxd/tests/models/test_cluster_member.py new file mode 100644 index 00000000..a36e40fd --- /dev/null +++ b/pylxd/tests/models/test_cluster_member.py @@ -0,0 +1,33 @@ +# Copyright (c) 2016 Canonical Ltd +# +# 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 + +from pylxd import models +from pylxd.tests import testing + + +class TestClusterMember(testing.PyLXDTestCase): + """Tests for pylxd.models.ClusterMember.""" + + def test_get(self): + """A cluster member is retrieved.""" + member = self.client.cluster_members.get('an-member') + + self.assertEqual('https://10.1.1.101:8443', member.url) + + def test_all(self): + """All cluster members are returned.""" + members = self.client.cluster_members.all() + + self.assertIn('an-member', [m.name for m in members]) diff --git a/pylxd/tests/models/test_container.py b/pylxd/tests/models/test_container.py index b689387e..d18e3fc8 100644 --- a/pylxd/tests/models/test_container.py +++ b/pylxd/tests/models/test_container.py @@ -78,6 +78,16 @@ def test_create(self): self.assertEqual(config['name'], an_new_container.name) + def test_create_remote(self): + """A new container is created at target.""" + config = {'name': 'an-new-remote-container'} + + an_new_remote_container = models.Container.create( + self.client, config, wait=True, target="an-remote") + + self.assertEqual(config['name'], an_new_remote_container.name) + self.assertEqual("an-remote", an_new_remote_container.location) + def test_exists(self): """A container exists.""" name = 'an-container' From 34d1601652f55b4f2ac7243e641e9249f3bb353e Mon Sep 17 00:00:00 2001 From: Felix Engelmann <fe-git...@nlogn.org> Date: Mon, 8 Oct 2018 22:58:39 +0200 Subject: [PATCH 5/5] Added to contributors Signed-off-by: Felix Engelmann <fe-git...@nlogn.org> --- CONTRIBUTORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 86035b24..a992363a 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -34,5 +34,6 @@ These are the contributors to pylxd according to the Github repository. chrismacnaughton Chris MacNaughton ppkt Karol Werner mrtc0 Kohei Morita + felix-engelmann Felix Engelmann =============== ==================================
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel