[lxc-devel] [pylxd/master] Added minimal cluster support
felix-engelmann on Github
lxc-bot at linuxcontainers.org
Mon Oct 8 21:09:46 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 401 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181008/daba4a81/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 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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
=============== ==================================
More information about the lxc-devel
mailing list