[lxc-devel] [pylxd/master] Documentation for Cluster feature
felix-engelmann on Github
lxc-bot at linuxcontainers.org
Wed Dec 12 22:02:40 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 386 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181212/79d379b4/attachment.bin>
-------------- next part --------------
From 676d0114f6622fe345a861f0c5cf3790e13f0f4b Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Mon, 24 Sep 2018 15:44:06 +0200
Subject: [PATCH 01/16] cluster endpoint read support
Signed-off-by: Felix Engelmann <fe-github at 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-github at nlogn.org>
Date: Mon, 24 Sep 2018 15:52:01 +0200
Subject: [PATCH 02/16] renamed node to more close cluster_member
Signed-off-by: Felix Engelmann <fe-github at 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-github at nlogn.org>
Date: Wed, 26 Sep 2018 00:10:01 +0200
Subject: [PATCH 03/16] Added functionality to specify target cluster member in
containers.create
Signed-off-by: Felix Engelmann <fe-github at 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-github at nlogn.org>
Date: Mon, 8 Oct 2018 22:56:22 +0200
Subject: [PATCH 04/16] Added tests for Cluster Members and Targeted create
container
Signed-off-by: Felix Engelmann <fe-github at 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-github at nlogn.org>
Date: Mon, 8 Oct 2018 22:58:39 +0200
Subject: [PATCH 05/16] Added to contributors
Signed-off-by: Felix Engelmann <fe-github at 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
=============== ==================================
From 997d47336d63ce0787ffb5ffe7d8313c76064271 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Mon, 8 Oct 2018 23:15:08 +0200
Subject: [PATCH 06/16] pep8 compliance
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/tests/mock_lxd.py | 10 ++--------
pylxd/tests/models/test_cluster_member.py | 2 --
2 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 5139f44a..90bce5ad 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -7,12 +7,6 @@ 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
@@ -244,7 +238,7 @@ def snapshot_DELETE(request, context):
'url': r'^http://pylxd.test/1.0/containers$',
},
{
- 'text': containers_remote_POST,
+ 'text': containers_POST,
'method': 'POST',
'url': r'^http://pylxd.test/1.0/containers\?target=an-remote',
},
@@ -342,7 +336,7 @@ def snapshot_DELETE(request, context):
'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",
+ 'location': "an-remote",
'status': "Running",
'status_code': 103,
'unsupportedbypylxd': "This attribute is not supported by "\
diff --git a/pylxd/tests/models/test_cluster_member.py b/pylxd/tests/models/test_cluster_member.py
index a36e40fd..3e9b6356 100644
--- a/pylxd/tests/models/test_cluster_member.py
+++ b/pylxd/tests/models/test_cluster_member.py
@@ -11,9 +11,7 @@
# 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
From fab37f584b57cec29426d021c1b958f591401e74 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Tue, 9 Oct 2018 01:16:15 +0200
Subject: [PATCH 07/16] Added integration test for cluster member info and all
attributes
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
integration/test_cluster_members.py | 41 +++++++++++++++++++++++++++++
pylxd/models/cluster_member.py | 11 +++++---
2 files changed, 48 insertions(+), 4 deletions(-)
create mode 100644 integration/test_cluster_members.py
diff --git a/integration/test_cluster_members.py b/integration/test_cluster_members.py
new file mode 100644
index 00000000..a69cc360
--- /dev/null
+++ b/integration/test_cluster_members.py
@@ -0,0 +1,41 @@
+# 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.
+
+from integration.testing import IntegrationTestCase
+
+
+class ClusterMemberTestCase(IntegrationTestCase):
+
+ def setUp(self):
+ super(ClusterMemberTestCase, self).setUp()
+
+ if not self.client.has_api_extension('clustering'):
+ self.skipTest('Required LXD API extension not available!')
+
+
+class TestClusterMembers(ClusterMemberTestCase):
+ """Tests for `Client.cluster_members.`"""
+
+ def test_get(self):
+ """A cluster member is fetched by its name."""
+
+ members = self.client.cluster_members.all()
+
+ random_member_name = "%s" % members[0].name
+ random_member_url = "%s" % members[0].url
+
+ member = self.client.cluster_members.get(random_member_name)
+
+ new_url = "%s" % member.url
+ self.assertEqual(random_member_url, new_url)
diff --git a/pylxd/models/cluster_member.py b/pylxd/models/cluster_member.py
index 96f7bd7c..caa4abd2 100644
--- a/pylxd/models/cluster_member.py
+++ b/pylxd/models/cluster_member.py
@@ -17,10 +17,13 @@
class ClusterMember(model.Model):
"""A LXD certificate."""
- name = model.Attribute()
- url = model.Attribute()
- database = model.Attribute()
- state = model.Attribute()
+ name = model.Attribute(readonly=True)
+ url = model.Attribute(readonly=True)
+ database = model.Attribute(readonly=True)
+ state = model.Attribute(readonly=True)
+ server_name = model.Attribute(readonly=True)
+ status = model.Attribute(readonly=True)
+ message = model.Attribute(readonly=True)
@classmethod
def get(cls, client, name):
From e6304b8f936a01ad0ed8afd971622deaa19e2aa9 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Tue, 11 Dec 2018 02:30:30 +0100
Subject: [PATCH 08/16] more efficient pop and params post parameter
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/client.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/pylxd/client.py b/pylxd/client.py
index 0293ba30..2836d6e4 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -153,15 +153,14 @@ def get(self, *args, **kwargs):
def post(self, *args, **kwargs):
"""Perform an HTTP POST."""
kwargs['timeout'] = kwargs.get('timeout', self._timeout)
- target = kwargs.get('target', None)
- kwargs.pop("target", None)
+ target = kwargs.pop("target", None)
if target is not None:
- endpoint = "{}?target={}".format(self._api_endpoint, target)
- else:
- endpoint = self._api_endpoint
+ params = kwargs.get("params", {})
+ params["target"] = target
+ kwargs["params"] = params
- response = self.session.post(endpoint, *args, **kwargs)
+ response = self.session.post(self._api_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))
From 676badfe99eef05f30202ec8c5a9bdd367f30d31 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Tue, 11 Dec 2018 23:25:53 +0100
Subject: [PATCH 09/16] Copied approach of a singleton manager from ajkavanagh
Adapted it that the class directly exposes .members @property of members with
getter to _members did not work
missing tests
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/client.py | 4 +-
pylxd/managers.py | 21 +++++++--
pylxd/models/__init__.py | 2 +-
pylxd/models/_model.py | 3 ++
.../models/{cluster_member.py => cluster.py} | 45 +++++++++++++++----
5 files changed, 59 insertions(+), 16 deletions(-)
rename pylxd/models/{cluster_member.py => cluster.py} (53%)
diff --git a/pylxd/client.py b/pylxd/client.py
index 0293ba30..5c622681 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -71,8 +71,6 @@ def __getattr__(self, name):
# Special case for storage_pools which needs to become 'storage-pools'
if name == 'storage_pools':
name = 'storage-pools'
- if name == 'cluster_members':
- name = 'cluster/members'
return self.__class__('{}/{}'.format(self._api_endpoint, name),
cert=self.session.cert,
verify=self.session.verify)
@@ -306,7 +304,7 @@ def __init__(
requests.exceptions.InvalidURL):
raise exceptions.ClientConnectionFailed()
- self.cluster_members = managers.ClusterMemberManager(self)
+ self.cluster = managers.ClusterManager(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 5ed834bd..958a2cfe 100644
--- a/pylxd/managers.py
+++ b/pylxd/managers.py
@@ -4,6 +4,8 @@
import importlib
import inspect
+from pylxd import managers
+
class BaseManager(object):
"""A BaseManager class for handling collection operations."""
@@ -27,10 +29,6 @@ def __init__(self, *args, **kwargs):
return super(BaseManager, self).__init__()
-class ClusterMemberManager(BaseManager):
- manager_for = 'pylxd.models.ClusterMember'
-
-
class CertificateManager(BaseManager):
manager_for = 'pylxd.models.Certificate'
@@ -63,6 +61,21 @@ class StoragePoolManager(BaseManager):
manager_for = 'pylxd.models.StoragePool'
+class ClusterMemberManager(BaseManager):
+ manager_for = 'pylxd.models.ClusterMember'
+
+
+class ClusterManager(BaseManager):
+
+ manager_for = 'pylxd.models.Cluster'
+
+ def __init__(self, client, *args, **kwargs):
+ super(ClusterManager, self).__init__(client, *args, **kwargs)
+ self._client = client
+ self.members = managers.ClusterMemberManager(client)
+
+
+
@contextmanager
def web_socket_manager(manager):
try:
diff --git a/pylxd/models/__init__.py b/pylxd/models/__init__.py
index 56c8693d..c71504cc 100644
--- a/pylxd/models/__init__.py
+++ b/pylxd/models/__init__.py
@@ -1,4 +1,4 @@
-from pylxd.models.cluster_member import ClusterMember # NOQA
+from pylxd.models.cluster import (Cluster, 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/_model.py b/pylxd/models/_model.py
index 83fd0673..bf69a172 100644
--- a/pylxd/models/_model.py
+++ b/pylxd/models/_model.py
@@ -121,6 +121,8 @@ def __getattribute__(self, name):
try:
return super(Model, self).__getattribute__(name)
except AttributeError:
+ print(self.__attributes__)
+ print(self.__slots__)
if name in self.__attributes__:
self.sync()
return super(Model, self).__getattribute__(name)
@@ -153,6 +155,7 @@ def sync(self, rollback=False):
# on existing attributes.
response = self.api.get()
payload = response.json()['metadata']
+ print(payload)
for key, val in payload.items():
if key not in self.__dirty__ or rollback:
try:
diff --git a/pylxd/models/cluster_member.py b/pylxd/models/cluster.py
similarity index 53%
rename from pylxd/models/cluster_member.py
rename to pylxd/models/cluster.py
index caa4abd2..dcdfa46a 100644
--- a/pylxd/models/cluster_member.py
+++ b/pylxd/models/cluster.py
@@ -12,12 +12,39 @@
# License for the specific language governing permissions and limitations
# under the License.
from pylxd.models import _model as model
+from pylxd import managers
+class Cluster(model.Model):
+ """An LXD Cluster.
+ """
+
+ server_name = model.Attribute()
+ enabled = model.Attribute()
+ member_config = model.Attribute()
+
+ members = model.Manager()
+
+ def __init__(self, *args, **kwargs):
+ super(Cluster, self).__init__(*args, **kwargs)
+ self.members = managers.ClusterMemberManager(self.client, self)
+
+ @property
+ def api(self):
+ return self.client.api.cluster
+
+ @classmethod
+ def get(cls, client, *args):
+ """Get cluster details"""
+ print(args)
+ response = client.api.cluster.get()
+ print(response.json())
+ container = cls(client, **response.json()['metadata'])
+ return container
+
class ClusterMember(model.Model):
- """A LXD certificate."""
+ """A LXD cluster member."""
- name = model.Attribute(readonly=True)
url = model.Attribute(readonly=True)
database = model.Attribute(readonly=True)
state = model.Attribute(readonly=True)
@@ -25,24 +52,26 @@ class ClusterMember(model.Model):
status = model.Attribute(readonly=True)
message = model.Attribute(readonly=True)
+ cluster = model.Parent()
+
@classmethod
def get(cls, client, name):
- """Get a certificate by fingerprint."""
- response = client.api.cluster_members[name].get()
+ """Get a cluster member by name."""
+ response = client.api.cluster.members[name].get()
return cls(client, **response.json()['metadata'])
@classmethod
- def all(cls, client):
+ def all(cls, client, *args):
"""Get all certificates."""
- response = client.api.cluster_members.get()
+ response = client.api.cluster.members.get()
nodes = []
for node in response.json()['metadata']:
name = node.split('/')[-1]
- nodes.append(cls(client, name=name))
+ nodes.append(cls(client, server_name=name))
return nodes
@property
def api(self):
- return self.client.api.cluster_members[self.name]
+ return self.client.api.cluster.members[self.server_name]
From f1f6e94d09c6aef544aa17f0176fbec5305bc734 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Tue, 11 Dec 2018 23:47:32 +0100
Subject: [PATCH 10/16] Adapted tests for ClusterMembers at client.members
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
integration/test_cluster_members.py | 4 ++--
pylxd/managers.py | 6 +-----
pylxd/models/cluster.py | 2 ++
pylxd/tests/models/test_cluster_member.py | 6 +++---
4 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/integration/test_cluster_members.py b/integration/test_cluster_members.py
index a69cc360..cf958a17 100644
--- a/integration/test_cluster_members.py
+++ b/integration/test_cluster_members.py
@@ -30,12 +30,12 @@ class TestClusterMembers(ClusterMemberTestCase):
def test_get(self):
"""A cluster member is fetched by its name."""
- members = self.client.cluster_members.all()
+ members = self.client.cluster.members.all()
random_member_name = "%s" % members[0].name
random_member_url = "%s" % members[0].url
- member = self.client.cluster_members.get(random_member_name)
+ member = self.client.cluster.members.get(random_member_name)
new_url = "%s" % member.url
self.assertEqual(random_member_url, new_url)
diff --git a/pylxd/managers.py b/pylxd/managers.py
index 958a2cfe..102f8921 100644
--- a/pylxd/managers.py
+++ b/pylxd/managers.py
@@ -4,9 +4,6 @@
import importlib
import inspect
-from pylxd import managers
-
-
class BaseManager(object):
"""A BaseManager class for handling collection operations."""
@@ -72,8 +69,7 @@ class ClusterManager(BaseManager):
def __init__(self, client, *args, **kwargs):
super(ClusterManager, self).__init__(client, *args, **kwargs)
self._client = client
- self.members = managers.ClusterMemberManager(client)
-
+ self.members = ClusterMemberManager(client)
@contextmanager
diff --git a/pylxd/models/cluster.py b/pylxd/models/cluster.py
index dcdfa46a..b854b217 100644
--- a/pylxd/models/cluster.py
+++ b/pylxd/models/cluster.py
@@ -42,9 +42,11 @@ def get(cls, client, *args):
container = cls(client, **response.json()['metadata'])
return container
+
class ClusterMember(model.Model):
"""A LXD cluster member."""
+ name = model.Attribute(readonly=True)
url = model.Attribute(readonly=True)
database = model.Attribute(readonly=True)
state = model.Attribute(readonly=True)
diff --git a/pylxd/tests/models/test_cluster_member.py b/pylxd/tests/models/test_cluster_member.py
index 3e9b6356..c593b3a5 100644
--- a/pylxd/tests/models/test_cluster_member.py
+++ b/pylxd/tests/models/test_cluster_member.py
@@ -20,12 +20,12 @@ class TestClusterMember(testing.PyLXDTestCase):
def test_get(self):
"""A cluster member is retrieved."""
- member = self.client.cluster_members.get('an-member')
+ 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()
+ members = self.client.cluster.members.all()
- self.assertIn('an-member', [m.name for m in members])
+ self.assertIn('an-member', [m.server_name for m in members])
From 5cd87998bafe74fe5be054dff98013528b89f20c Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Tue, 11 Dec 2018 23:51:15 +0100
Subject: [PATCH 11/16] pep8 compliance
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/managers.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/pylxd/managers.py b/pylxd/managers.py
index 102f8921..1b6cd478 100644
--- a/pylxd/managers.py
+++ b/pylxd/managers.py
@@ -4,6 +4,7 @@
import importlib
import inspect
+
class BaseManager(object):
"""A BaseManager class for handling collection operations."""
From 76cda97b187510755f8ebd22e1e6a69abc78d7f4 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Wed, 12 Dec 2018 14:22:03 +0100
Subject: [PATCH 12/16] removed attributes name and state, as they are not
returned
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
integration/test_cluster_members.py | 2 +-
pylxd/models/cluster.py | 10 ++++------
pylxd/tests/mock_lxd.py | 7 ++++---
3 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/integration/test_cluster_members.py b/integration/test_cluster_members.py
index cf958a17..7708a264 100644
--- a/integration/test_cluster_members.py
+++ b/integration/test_cluster_members.py
@@ -32,7 +32,7 @@ def test_get(self):
members = self.client.cluster.members.all()
- random_member_name = "%s" % members[0].name
+ random_member_name = "%s" % members[0].server_name
random_member_url = "%s" % members[0].url
member = self.client.cluster.members.get(random_member_name)
diff --git a/pylxd/models/cluster.py b/pylxd/models/cluster.py
index b854b217..85baa0b1 100644
--- a/pylxd/models/cluster.py
+++ b/pylxd/models/cluster.py
@@ -46,10 +46,8 @@ def get(cls, client, *args):
class ClusterMember(model.Model):
"""A LXD cluster member."""
- name = model.Attribute(readonly=True)
url = model.Attribute(readonly=True)
database = model.Attribute(readonly=True)
- state = model.Attribute(readonly=True)
server_name = model.Attribute(readonly=True)
status = model.Attribute(readonly=True)
message = model.Attribute(readonly=True)
@@ -57,9 +55,9 @@ class ClusterMember(model.Model):
cluster = model.Parent()
@classmethod
- def get(cls, client, name):
+ def get(cls, client, server_name):
"""Get a cluster member by name."""
- response = client.api.cluster.members[name].get()
+ response = client.api.cluster.members[server_name].get()
return cls(client, **response.json()['metadata'])
@@ -70,8 +68,8 @@ def all(cls, client, *args):
nodes = []
for node in response.json()['metadata']:
- name = node.split('/')[-1]
- nodes.append(cls(client, server_name=name))
+ server_name = node.split('/')[-1]
+ nodes.append(cls(client, server_name=server_name))
return nodes
@property
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 90bce5ad..7efd2297 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -207,10 +207,11 @@ def snapshot_DELETE(request, context):
'text': json.dumps({
'type': 'sync',
'metadata': {
- "name": "an-member",
+ "server_name": "an-member",
"url": "https://10.1.1.101:8443",
- "database": "true",
- "state": "Online",
+ "database": 'false',
+ "status": "Online",
+ "message": "fully operational",
}}),
'method': 'GET',
'url': r'^http://pylxd.test/1.0/cluster/members/an-member$', # NOQA
From b9162a27e7dcdfc6c3d75b594ed1efbe13d37b2d Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Wed, 12 Dec 2018 15:03:44 +0100
Subject: [PATCH 13/16] Removed debug print outputs and Integration tests
working
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/models/_model.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/pylxd/models/_model.py b/pylxd/models/_model.py
index bf69a172..83fd0673 100644
--- a/pylxd/models/_model.py
+++ b/pylxd/models/_model.py
@@ -121,8 +121,6 @@ def __getattribute__(self, name):
try:
return super(Model, self).__getattribute__(name)
except AttributeError:
- print(self.__attributes__)
- print(self.__slots__)
if name in self.__attributes__:
self.sync()
return super(Model, self).__getattribute__(name)
@@ -155,7 +153,6 @@ def sync(self, rollback=False):
# on existing attributes.
response = self.api.get()
payload = response.json()['metadata']
- print(payload)
for key, val in payload.items():
if key not in self.__dirty__ or rollback:
try:
From a22c40151c3f019b579fb37d6c4c503f86761e84 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Wed, 12 Dec 2018 15:38:55 +0100
Subject: [PATCH 14/16] Added tests for cluster endpoint
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
pylxd/tests/mock_lxd.py | 31 ++++++++++++++++++++++++++++++
pylxd/tests/models/test_cluster.py | 25 ++++++++++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 pylxd/tests/models/test_cluster.py
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 7efd2297..f0470609 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -192,6 +192,37 @@ def snapshot_DELETE(request, context):
},
+ # Cluster
+ {
+ 'text': json.dumps({
+ 'type': 'sync',
+ 'metadata': {
+ "server_name": "an-member",
+ "enabled": 'true',
+ "member_config": [{
+ "entity": "storage-pool",
+ "name": "local",
+ "key": "source",
+ "value": "",
+ "description":
+ "\"source\" property for storage pool \"local\""
+ },
+ {
+ "entity": "storage-pool",
+ "name": "local",
+ "key": "volatile.initial_source",
+ "value": "",
+ "description":
+ "\"volatile.initial_source\" property for"
+ " storage pool \"local\""
+ }]
+ }
+ }),
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/cluster$',
+ },
+
+
# Cluster Members
{
'text': json.dumps({
diff --git a/pylxd/tests/models/test_cluster.py b/pylxd/tests/models/test_cluster.py
new file mode 100644
index 00000000..1b4733c2
--- /dev/null
+++ b/pylxd/tests/models/test_cluster.py
@@ -0,0 +1,25 @@
+# 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.
+
+from pylxd.tests import testing
+
+
+class TestCluster(testing.PyLXDTestCase):
+ """Tests for pylxd.models.Cluster."""
+
+ def test_get(self):
+ """A cluster is retrieved."""
+ cluster = self.client.cluster.get()
+
+ self.assertEqual("an-member", cluster.server_name)
From 013cc0d56b71e6d05ec263ec911ab8aeed9bf796 Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Wed, 12 Dec 2018 22:53:29 +0100
Subject: [PATCH 15/16] Added documentation for clustering and target in
container creation
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
.gitignore | 4 ++
doc/source/clustering.rst | 85 ++++++++++++++++++++++++++++++++++++
doc/source/index.rst | 1 +
doc/source/storage-pools.rst | 2 +-
pylxd/models/container.py | 15 ++++++-
5 files changed, 105 insertions(+), 2 deletions(-)
create mode 100644 doc/source/clustering.rst
diff --git a/.gitignore b/.gitignore
index 96f9cc7b..185c3b90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ pip-log.txt
nosetests.xml
.testrepository
.venv
+htmlcov/
# Translations
*.mo
@@ -37,6 +38,9 @@ nosetests.xml
.project
.pydevproject
+# Pycharm
+.idea/
+
# Complexity
output/*.html
output/*/index.html
diff --git a/doc/source/clustering.rst b/doc/source/clustering.rst
new file mode 100644
index 00000000..299e2b32
--- /dev/null
+++ b/doc/source/clustering.rst
@@ -0,0 +1,85 @@
+Clustering
+=============
+
+LXD supports clustering. There is only one cluster object.
+
+Cluster object
+--------------
+
+The :py:class:`~pylxd.models.cluster.Cluster` object represents the json
+object that is returned from `GET /1.0/cluster`.
+
+There is also a :py:class:`~pylxd.models.cluster.ClusterMember` and object that represents a
+cluster member at `GET
+/1.0/cluster/members`. Note that it should be
+accessed from the cluster object. For example:
+
+.. code:: python
+
+ client = pylxd.Client()
+ cluster = client.cluster.get()
+ member = cluster.members.get('node-5')
+
+
+.. note:: Please see the pylxd API documentation for more information on
+ storage pool methods and parameters. The following is a summary.
+
+Cluster methods
+^^^^^^^^^^^^^^^
+
+A cluster can be queried through the following client manager methods:
+
+
+ - `get()` - Returns the cluster.
+
+
+Cluster Object attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For more information about the specifics of these attributes, please see
+the `LXD Cluster REST API`_ documentation.
+
+ - `server_name` - the name of the server in the cluster
+ - `enabled` - if the node is enabled
+ - `member_config` - configuration information for new cluster members.
+
+
+Cluster Members
+---------------
+
+Cluster Members are stored in a cluster. On the `pylxd` API they are
+accessed from a cluster object:
+
+.. code:: Python
+
+ cluster = client.cluster.get()
+ members = cluster.members.all()
+ named_member = cluster.members.get('membername')
+
+
+Methods available on `<cluster_object>.members`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The following methods are accessed from the `members` attribute on the cluster object.
+
+ - `all` - get all the members of the cluster.
+ - `get` - a get a single named member of the cluster.
+
+
+Cluster Member Object attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For more information about the specifics of these attributes, please see
+the `LXD Cluster REST API`_ documentation.
+
+ - `server_name` - the name of the server in the cluster
+ - `url` - the url the lxd endpoint
+ - `database` - if the distributed database is replicated on this node
+ - `status` - if the member is off or online
+ - `message` - a general message
+
+.. links
+
+.. _LXD Storage Pools: https://lxd.readthedocs.io/en/latest/storage/
+.. _LXD REST API: https://github.com/lxc/lxd/blob/master/doc/rest-api.md
+.. _LXD Cluster REST API: https://github.com/lxc/lxd/blob/master/doc/rest-api.md#10cluster
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 7f5409c5..5e38792d 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -23,6 +23,7 @@ Contents:
profiles
operations
storage-pools
+ clustering
contributing
api
diff --git a/doc/source/storage-pools.rst b/doc/source/storage-pools.rst
index f2929ef3..53a08392 100644
--- a/doc/source/storage-pools.rst
+++ b/doc/source/storage-pools.rst
@@ -98,7 +98,7 @@ Storage Volumes
Storage Volumes are stored in storage pools. On the `pylxd` API they are
accessed from a storage pool object:
-... code:: Python
+.. code:: Python
storage_pool = client.storage_pools.get('pool1')
volumes = storage_pool.volumes.all()
diff --git a/pylxd/models/container.py b/pylxd/models/container.py
index ed3f8d63..d725da15 100644
--- a/pylxd/models/container.py
+++ b/pylxd/models/container.py
@@ -258,7 +258,20 @@ def all(cls, client):
@classmethod
def create(cls, client, config, wait=False, target=None):
- """Create a new container config."""
+ """Create a new container config.
+
+ :param client: client instance
+ :type client: Client
+ :param config: The configuration for the new container.
+ :type config: dict
+ :param wait: Whether to wait for async operations to complete.
+ :type wait: bool
+ :param target: If in cluster mode, the target member.
+ :type target: str
+ :raises LXDAPIException: if something goes wrong.
+ :returns: a container if successful
+ :rtype: :class:`Container`
+ """
response = client.api.containers.post(json=config, target=target)
if wait:
From 119ad719cea47fb9fdc9f92ba859d8a68c8b56bc Mon Sep 17 00:00:00 2001
From: Felix Engelmann <fe-github at nlogn.org>
Date: Wed, 12 Dec 2018 22:57:05 +0100
Subject: [PATCH 16/16] Add autogenerated class documentation for cluster
Signed-off-by: Felix Engelmann <fe-github at nlogn.org>
---
doc/source/api.rst | 9 +++++++++
pylxd/models/cluster.py | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/doc/source/api.rst b/doc/source/api.rst
index a7215e89..db0d9000 100644
--- a/doc/source/api.rst
+++ b/doc/source/api.rst
@@ -62,3 +62,12 @@ Storage Pool
.. autoclass:: pylxd.models.StoragePool
:members:
+
+Cluster
+------------
+
+.. autoclass:: pylxd.models.Cluster
+ :members:
+
+.. autoclass:: pylxd.models.ClusterMember
+ :members:
diff --git a/pylxd/models/cluster.py b/pylxd/models/cluster.py
index 85baa0b1..bb1c57ba 100644
--- a/pylxd/models/cluster.py
+++ b/pylxd/models/cluster.py
@@ -63,7 +63,7 @@ def get(cls, client, server_name):
@classmethod
def all(cls, client, *args):
- """Get all certificates."""
+ """Get all cluster members."""
response = client.api.cluster.members.get()
nodes = []
More information about the lxc-devel
mailing list