[lxc-devel] [pylxd/master] Update all objects to the model API
rockstar on Github
lxc-bot at linuxcontainers.org
Mon Jun 27 22:56:54 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 444 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160627/a913988a/attachment.bin>
-------------- next part --------------
From 2422bbb4120594b854be143d095e174824d5690c Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Sat, 25 Jun 2016 22:55:19 -0600
Subject: [PATCH 1/9] Update Certificate to use the model code
---
pylxd/certificate.py | 34 +++++++++++-----------------------
pylxd/model.py | 5 ++++-
pylxd/network.py | 4 ++++
pylxd/tests/test_certificate.py | 4 ++--
4 files changed, 21 insertions(+), 26 deletions(-)
diff --git a/pylxd/certificate.py b/pylxd/certificate.py
index 25b0087..7f4fc1e 100644
--- a/pylxd/certificate.py
+++ b/pylxd/certificate.py
@@ -17,23 +17,23 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import Encoding
-import six
+from pylxd import model
-class Certificate(object):
+
+class Certificate(model.Model):
"""A LXD certificate."""
- __slots__ = [
- '_client',
- 'certificate', 'fingerprint', 'type',
- ]
+ certificate = model.Attribute()
+ fingerprint = model.Attribute()
+ type = model.Attribute()
@classmethod
def get(cls, client, fingerprint):
"""Get a certificate by fingerprint."""
response = client.api.certificates[fingerprint].get()
- return cls(_client=client, **response.json()['metadata'])
+ return cls(client, **response.json()['metadata'])
@classmethod
def all(cls, client):
@@ -43,7 +43,7 @@ def all(cls, client):
certs = []
for cert in response.json()['metadata']:
fingerprint = cert.split('/')[-1]
- certs.append(cls(_client=client, fingerprint=fingerprint))
+ certs.append(cls(client, fingerprint=fingerprint))
return certs
@classmethod
@@ -66,18 +66,6 @@ def create(cls, client, password, cert_data):
cert.fingerprint(hashes.SHA256())).decode('utf-8')
return cls.get(client, fingerprint)
- def __init__(self, **kwargs):
- super(Certificate, self).__init__()
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
-
- def delete(self):
- """Delete the certificate."""
- self._client.api.certificates[self.fingerprint].delete()
-
- def fetch(self):
- """Fetch an updated representation of the certificate."""
- response = self._client.api.certificates[self.fingerprint].get()
-
- for key, value in six.iteritems(response.json()['metadata']):
- setattr(self, key, value)
+ @property
+ def api(self):
+ return self.client.api.certificates[self.fingerprint]
diff --git a/pylxd/model.py b/pylxd/model.py
index 58ec560..778e4ef 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -13,6 +13,8 @@
# under the License.
import six
+from pylxd.deprecation import deprecated
+
class Attribute(object):
"""A metadata class for model attributes."""
@@ -115,6 +117,7 @@ def sync(self):
response = self.api.get()
for key, val in response.json()['metadata'].items():
setattr(self, key, val)
+ fetch = deprecated("fetch is deprecated; please use sync")(sync)
def save(self):
"""Save data to the server.
@@ -127,4 +130,4 @@ def save(self):
def delete(self):
"""Delete an object from the server."""
- raise NotImplementedError('delete is not implemented')
+ self.api.delete()
diff --git a/pylxd/network.py b/pylxd/network.py
index 9609172..5c24165 100644
--- a/pylxd/network.py
+++ b/pylxd/network.py
@@ -42,3 +42,7 @@ def all(cls, client):
@property
def api(self):
return self.client.api.networks[self.name]
+
+ def delete(self):
+ """Delete an object from the server."""
+ raise NotImplementedError('delete is not implemented')
diff --git a/pylxd/tests/test_certificate.py b/pylxd/tests/test_certificate.py
index 34a85eb..e3a7f86 100644
--- a/pylxd/tests/test_certificate.py
+++ b/pylxd/tests/test_certificate.py
@@ -46,7 +46,7 @@ def test_create(self):
def test_fetch(self):
"""A partial object is fully fetched."""
an_certificate = certificate.Certificate(
- _client=self.client, fingerprint='an-certificate')
+ self.client, fingerprint='an-certificate')
an_certificate.fetch()
@@ -57,6 +57,6 @@ def test_delete(self):
# XXX: rockstar (08 Jun 2016) - This just executes a code path. An
# assertion should be added.
an_certificate = certificate.Certificate(
- _client=self.client, fingerprint='an-certificate')
+ self.client, fingerprint='an-certificate')
an_certificate.delete()
From 6c6e66026b4decb6a4db603b8ab2cecc6ced58d3 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Sun, 26 Jun 2016 10:32:02 -0600
Subject: [PATCH 2/9] Convert the Profile to the model api
---
pylxd/model.py | 15 ++++++++++++++-
pylxd/profile.py | 42 ++++++++++++------------------------------
pylxd/tests/test_profile.py | 12 ++----------
3 files changed, 28 insertions(+), 41 deletions(-)
diff --git a/pylxd/model.py b/pylxd/model.py
index 778e4ef..e84200b 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -13,6 +13,7 @@
# under the License.
import six
+from pylxd import exceptions
from pylxd.deprecation import deprecated
@@ -114,7 +115,12 @@ def sync(self):
"""
# XXX: rockstar (25 Jun 2016) - This has the potential to step
# on existing attributes.
- response = self.api.get()
+ try:
+ response = self.api.get()
+ except exceptions.LXDAPIException as e:
+ if e.response.status_code == 404:
+ raise exceptions.NotFound()
+ raise
for key, val in response.json()['metadata'].items():
setattr(self, key, val)
fetch = deprecated("fetch is deprecated; please use sync")(sync)
@@ -131,3 +137,10 @@ def save(self):
def delete(self):
"""Delete an object from the server."""
self.api.delete()
+
+ def marshall(self):
+ """Marshall the object in preparation for updating to the server."""
+ marshalled = {}
+ for key, val in self.__attributes__.items():
+ marshalled[key] = getattr(self, key)
+ return marshalled
diff --git a/pylxd/profile.py b/pylxd/profile.py
index 1bcc0e0..fe68c9d 100644
--- a/pylxd/profile.py
+++ b/pylxd/profile.py
@@ -11,17 +11,16 @@
# 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 six
-from pylxd import exceptions, mixin
+from pylxd import exceptions, model
-class Profile(mixin.Marshallable):
+class Profile(model.Model):
"""A LXD profile."""
- __slots__ = [
- '_client',
- 'config', 'devices', 'name'
- ]
+ name = model.Attribute()
+ description = model.Attribute()
+ config = model.Attribute()
+ devices = model.Attribute()
@classmethod
def get(cls, client, name):
@@ -33,7 +32,7 @@ def get(cls, client, name):
raise exceptions.NotFound()
raise
- return cls(_client=client, **response.json()['metadata'])
+ return cls(client, **response.json()['metadata'])
@classmethod
def all(cls, client):
@@ -43,7 +42,7 @@ def all(cls, client):
profiles = []
for url in response.json()['metadata']:
name = url.split('/')[-1]
- profiles.append(cls(_client=client, name=name))
+ profiles.append(cls(client, name=name))
return profiles
@classmethod
@@ -61,10 +60,9 @@ def create(cls, client, name, config=None, devices=None):
return cls.get(client, name)
- def __init__(self, **kwargs):
- super(Profile, self).__init__()
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
+ @property
+ def api(self):
+ return self.client.api.profiles[self.name]
def update(self):
"""Update the profile in LXD based on local changes."""
@@ -75,25 +73,9 @@ def update(self):
# The name property cannot be updated.
del marshalled['name']
- self._client.api.profiles[self.name].put(json=marshalled)
+ self.api.put(json=marshalled)
def rename(self, new):
"""Rename the profile."""
raise NotImplementedError(
'LXD does not currently support renaming profiles')
-
- def delete(self):
- """Delete a profile."""
- self._client.api.profiles[self.name].delete()
-
- def fetch(self):
- """Fetch the object from LXD, populating attributes."""
- try:
- response = self._client.api.profiles[self.name].get()
- except exceptions.LXDAPIException as e:
- if e.response.status_code == 404:
- raise exceptions.NotFound()
- raise
-
- for key, val in six.iteritems(response.json()['metadata']):
- setattr(self, key, val)
diff --git a/pylxd/tests/test_profile.py b/pylxd/tests/test_profile.py
index d964ec7..2d7b80a 100644
--- a/pylxd/tests/test_profile.py
+++ b/pylxd/tests/test_profile.py
@@ -101,14 +101,6 @@ def test_update(self):
self.assertEqual({}, an_profile.config)
- def test_update_partial_objects(self):
- """A partially fetched profile can't be pushed."""
- an_profile = self.client.profiles.all()[0]
-
- self.assertRaises(
- exceptions.ObjectIncomplete,
- an_profile.update)
-
def test_fetch(self):
"""A partially fetched profile is made complete."""
an_profile = self.client.profiles.all()[0]
@@ -131,7 +123,7 @@ def not_found(request, context):
'url': r'^http://pylxd.test/1.0/profiles/an-profile$',
})
- an_profile = profile.Profile(name='an-profile', _client=self.client)
+ an_profile = profile.Profile(self.client, name='an-profile')
self.assertRaises(exceptions.NotFound, an_profile.fetch)
@@ -149,7 +141,7 @@ def error(request, context):
'url': r'^http://pylxd.test/1.0/profiles/an-profile$',
})
- an_profile = profile.Profile(name='an-profile', _client=self.client)
+ an_profile = profile.Profile(self.client, name='an-profile')
self.assertRaises(exceptions.LXDAPIException, an_profile.fetch)
From fe2f5212f8a3e7e7d5c3cb70a8c63b153752d851 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Sun, 26 Jun 2016 11:02:10 -0600
Subject: [PATCH 3/9] Update Image to the model api
---
pylxd/image.py | 65 +++++++++++++++++------------------------------
pylxd/model.py | 9 +++++--
pylxd/operation.py | 1 -
pylxd/tests/test_image.py | 12 ++-------
4 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/pylxd/image.py b/pylxd/image.py
index ccc1f9d..e4d02c3 100644
--- a/pylxd/image.py
+++ b/pylxd/image.py
@@ -13,20 +13,29 @@
# under the License.
import hashlib
-import six
-
-from pylxd import exceptions, mixin
+from pylxd import exceptions, model
from pylxd.operation import Operation
-class Image(mixin.Marshallable):
+class Image(model.Model):
"""A LXD Image."""
-
- __slots__ = [
- '_client',
- 'aliases', 'architecture', 'created_at', 'expires_at', 'filename',
- 'fingerprint', 'properties', 'public', 'size', 'uploaded_at'
- ]
+ aliases = model.Attribute()
+ auto_update = model.Attribute()
+ architecture = model.Attribute()
+ cached = model.Attribute()
+ created_at = model.Attribute()
+ expires_at = model.Attribute()
+ filename = model.Attribute()
+ fingerprint = model.Attribute()
+ last_used_at = model.Attribute()
+ properties = model.Attribute()
+ public = model.Attribute()
+ size = model.Attribute()
+ uploaded_at = model.Attribute()
+
+ @property
+ def api(self):
+ return self.client.api.images[self.fingerprint]
@classmethod
def get(cls, client, fingerprint):
@@ -38,7 +47,7 @@ def get(cls, client, fingerprint):
raise exceptions.NotFound()
raise
- image = cls(_client=client, **response.json()['metadata'])
+ image = cls(client, **response.json()['metadata'])
return image
@classmethod
@@ -49,7 +58,7 @@ def all(cls, client):
images = []
for url in response.json()['metadata']:
fingerprint = url.split('/')[-1]
- images.append(cls(_client=client, fingerprint=fingerprint))
+ images.append(cls(client, fingerprint=fingerprint))
return images
@classmethod
@@ -68,12 +77,7 @@ def create(cls, client, image_data, public=False, wait=False):
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
- return cls(_client=client, fingerprint=fingerprint)
-
- def __init__(self, **kwargs):
- super(Image, self).__init__()
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
+ return cls(client, fingerprint=fingerprint)
def update(self):
"""Update LXD based on changes to this image."""
@@ -81,33 +85,12 @@ def update(self):
marshalled = self.marshall()
except AttributeError:
raise exceptions.ObjectIncomplete()
- self._client.api.images[self.fingerprint].put(
- json=marshalled)
-
- def delete(self, wait=False):
- """Delete the image."""
- response = self._client.api.images[self.fingerprint].delete()
-
- if wait:
- Operation.wait_for_operation(
- self._client, response.json()['operation'])
-
- def fetch(self):
- """Fetch the object from LXD, populating attributes."""
- try:
- response = self._client.api.images[self.fingerprint].get()
- except exceptions.LXDAPIException as e:
- if e.response.status_code == 404:
- raise exceptions.NotFound()
- raise
-
- for key, val in six.iteritems(response.json()['metadata']):
- setattr(self, key, val)
+ self.api.put(json=marshalled)
def export(self):
"""Export the image."""
try:
- response = self._client.api.images[self.fingerprint].export.get()
+ response = self.api.export.get()
except exceptions.LXDAPIException as e:
if e.response.status_code == 404:
raise exceptions.NotFound()
diff --git a/pylxd/model.py b/pylxd/model.py
index e84200b..3a5f601 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -15,6 +15,7 @@
from pylxd import exceptions
from pylxd.deprecation import deprecated
+from pylxd.operation import Operation
class Attribute(object):
@@ -134,9 +135,13 @@ def save(self):
"""
raise NotImplementedError('save is not implemented')
- def delete(self):
+ def delete(self, wait=False):
"""Delete an object from the server."""
- self.api.delete()
+ response = self.api.delete()
+
+ if response.json()['type'] == 'async' and wait:
+ Operation.wait_for_operation(
+ self.client, response.json()['operation'])
def marshall(self):
"""Marshall the object in preparation for updating to the server."""
diff --git a/pylxd/operation.py b/pylxd/operation.py
index 5ec9ea0..09c9d24 100644
--- a/pylxd/operation.py
+++ b/pylxd/operation.py
@@ -11,7 +11,6 @@
# 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 six
diff --git a/pylxd/tests/test_image.py b/pylxd/tests/test_image.py
index fa3995a..bac6a5e 100644
--- a/pylxd/tests/test_image.py
+++ b/pylxd/tests/test_image.py
@@ -94,14 +94,6 @@ def test_update(self):
a_image.update()
- def test_update_partial_objects(self):
- """A partially fetched image can't be pushed."""
- a_image = self.client.images.all()[0]
-
- self.assertRaises(
- exceptions.ObjectIncomplete,
- a_image.update)
-
def test_fetch(self):
"""A partial object is fetched and populated."""
a_image = self.client.images.all()[0]
@@ -125,7 +117,7 @@ def not_found(request, context):
})
fingerprint = hashlib.sha256(b'').hexdigest()
- a_image = image.Image(fingerprint=fingerprint, _client=self.client)
+ a_image = image.Image(self.client, fingerprint=fingerprint)
self.assertRaises(exceptions.NotFound, a_image.fetch)
@@ -144,7 +136,7 @@ def not_found(request, context):
})
fingerprint = hashlib.sha256(b'').hexdigest()
- a_image = image.Image(fingerprint=fingerprint, _client=self.client)
+ a_image = image.Image(self.client, fingerprint=fingerprint)
self.assertRaises(exceptions.LXDAPIException, a_image.fetch)
From dfb21a5ea5bc402854c32d3b013403a3d1e4d3f2 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Sun, 26 Jun 2016 17:25:07 -0600
Subject: [PATCH 4/9] Update Container to the model api
---
pylxd/client.py | 8 ----
pylxd/container.py | 91 +++++++++++++++++++------------------------
pylxd/model.py | 14 +++++++
pylxd/tests/mock_lxd.py | 4 ++
pylxd/tests/test_client.py | 32 ---------------
pylxd/tests/test_container.py | 16 ++++----
6 files changed, 66 insertions(+), 99 deletions(-)
diff --git a/pylxd/client.py b/pylxd/client.py
index 2890b43..81a278c 100644
--- a/pylxd/client.py
+++ b/pylxd/client.py
@@ -74,14 +74,6 @@ def _assert_response(self, response, allowed_status_codes=(200,)):
# Missing 'type' in response
raise exceptions.LXDAPIException(response)
- if response.status_code == 202:
- try:
- if data['type'] != 'async':
- raise exceptions.LXDAPIException(response)
- except KeyError:
- # Missing 'type' in response
- raise exceptions.LXDAPIException(response)
-
def get(self, *args, **kwargs):
"""Perform an HTTP GET."""
response = self.session.get(self._api_endpoint, *args, **kwargs)
diff --git a/pylxd/container.py b/pylxd/container.py
index 164601b..4286894 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -18,7 +18,7 @@
from ws4py.client import WebSocketBaseClient
from ws4py.manager import WebSocketManager
-from pylxd import exceptions, managers, mixin
+from pylxd import exceptions, managers, mixin, model
from pylxd.deprecation import deprecated
from pylxd.operation import Operation
@@ -31,12 +31,29 @@ def __init__(self, **kwargs):
setattr(self, key, value)
-class Container(mixin.Marshallable):
+class Container(model.Model):
"""An LXD Container.
This class is not intended to be used directly, but rather to be used
via `Client.containers.create`.
"""
+ architecture = model.Attribute()
+ config = model.Attribute()
+ created_at = model.Attribute()
+ devices = model.Attribute()
+ ephemeral = model.Attribute()
+ expanded_config = model.Attribute()
+ expanded_devices = model.Attribute()
+ name = model.Attribute()
+ profiles = model.Attribute()
+ status = model.Attribute()
+
+ snapshots = model.Manager()
+ files = model.Manager()
+
+ @property
+ def api(self):
+ return self.client.api.containers[self.name]
class FilesManager(object):
"""A pseudo-manager for namespacing file operations."""
@@ -63,12 +80,6 @@ def get(self, filepath):
raise
return response.content
- __slots__ = [
- '_client',
- 'architecture', 'config', 'created_at', 'devices', 'ephemeral',
- 'expanded_config', 'expanded_devices', 'name', 'profiles', 'status'
- ]
-
@classmethod
def get(cls, client, name):
"""Get a container by name."""
@@ -79,7 +90,7 @@ def get(cls, client, name):
raise exceptions.NotFound()
raise
- container = cls(_client=client, **response.json()['metadata'])
+ container = cls(client, **response.json()['metadata'])
return container
@classmethod
@@ -96,7 +107,7 @@ def all(cls, client):
containers = []
for url in response.json()['metadata']:
name = url.split('/')[-1]
- containers.append(cls(_client=client, name=name))
+ containers.append(cls(client, name=name))
return containers
@classmethod
@@ -109,31 +120,19 @@ def create(cls, client, config, wait=False):
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
- return cls(name=config['name'], _client=client)
+ return cls(client, name=config['name'])
- def __init__(self, **kwargs):
- super(Container, self).__init__()
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
+ def __init__(self, *args, **kwargs):
+ super(Container, self).__init__(*args, **kwargs)
- self.snapshots = managers.SnapshotManager(self._client, self)
- self.files = self.FilesManager(self._client, self)
+ self.snapshots = managers.SnapshotManager(self.client, self)
+ self.files = self.FilesManager(self.client, self)
- def fetch(self):
- """Reload the container information."""
- try:
- response = self._client.api.containers[self.name].get()
- except exceptions.LXDAPIException as e:
- if e.response.status_code == 404:
- raise exceptions.NotFound()
- raise
- for key, value in six.iteritems(response.json()['metadata']):
- setattr(self, key, value)
# XXX: rockstar (28 Mar 2016) - This method was named improperly
# originally. It's being kept here for backwards compatibility.
reload = deprecated(
- "Container.reload is deprecated. Please use Container.fetch")(
- fetch)
+ "Container.reload is deprecated. Please use Container.sync")(
+ model.Model.sync)
def update(self, wait=False):
"""Update the container in lxd from local changes."""
@@ -145,44 +144,34 @@ def update(self, wait=False):
del marshalled['name']
del marshalled['status']
- response = self._client.api.containers[self.name].put(
- json=marshalled)
+ response = self.api.put(json=marshalled)
if wait:
Operation.wait_for_operation(
- self._client, response.json()['operation'])
+ self.client, response.json()['operation'])
def rename(self, name, wait=False):
"""Rename a container."""
- response = self._client.api.containers[
- self.name].post(json={'name': name})
+ response = self.api.post(json={'name': name})
if wait:
Operation.wait_for_operation(
- self._client, response.json()['operation'])
+ self.client, response.json()['operation'])
self.name = name
- def delete(self, wait=False):
- """Delete the container."""
- response = self._client.api.containers[self.name].delete()
-
- if wait:
- Operation.wait_for_operation(
- self._client, response.json()['operation'])
-
def _set_state(self, state, timeout=30, force=True, wait=False):
- response = self._client.api.containers[self.name].state.put(json={
+ response = self.api.state.put(json={
'action': state,
'timeout': timeout,
'force': force
})
if wait:
Operation.wait_for_operation(
- self._client, response.json()['operation'])
+ self.client, response.json()['operation'])
self.fetch()
def state(self):
- response = self._client.api.containers[self.name].state.get()
+ response = self.api.state.get()
state = ContainerState(**response.json()['metadata'])
return state
@@ -257,7 +246,7 @@ def execute(self, commands, environment={}):
"""Execute a command on the container."""
if isinstance(commands, six.string_types):
raise TypeError("First argument must be a list.")
- response = self._client.api.containers[self.name]['exec'].post(json={
+ response = self.api['exec'].post(json={
'command': commands,
'environment': environment,
'wait-for-websocket': True,
@@ -267,17 +256,17 @@ def execute(self, commands, environment={}):
fds = response.json()['metadata']['metadata']['fds']
operation_id = response.json()['operation'].split('/')[-1]
parsed = parse.urlparse(
- self._client.api.operations[operation_id].websocket._api_endpoint)
+ self.client.api.operations[operation_id].websocket._api_endpoint)
manager = WebSocketManager()
- stdin = _StdinWebsocket(manager, self._client.websocket_url)
+ stdin = _StdinWebsocket(manager, self.client.websocket_url)
stdin.resource = '{}?secret={}'.format(parsed.path, fds['0'])
stdin.connect()
- stdout = _CommandWebsocketClient(manager, self._client.websocket_url)
+ stdout = _CommandWebsocketClient(manager, self.client.websocket_url)
stdout.resource = '{}?secret={}'.format(parsed.path, fds['1'])
stdout.connect()
- stderr = _CommandWebsocketClient(manager, self._client.websocket_url)
+ stderr = _CommandWebsocketClient(manager, self.client.websocket_url)
stderr.resource = '{}?secret={}'.format(parsed.path, fds['2'])
stderr.connect()
diff --git a/pylxd/model.py b/pylxd/model.py
index 3a5f601..012f6ea 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -25,6 +25,14 @@ def __init__(self, validator=None):
self.validator = validator
+class Manager(object):
+ """A manager declaration.
+
+ This class signals to the model that it will have a Manager
+ attribute.
+ """
+
+
class ModelType(type):
"""A Model metaclass.
@@ -37,11 +45,15 @@ def __new__(cls, name, bases, attrs):
raise TypeError('__slots__ should not be specified.')
attributes = {}
for_removal = []
+ managers = []
for key, val in attrs.items():
if type(val) == Attribute:
attributes[key] = val
for_removal.append(key)
+ if type(val) == Manager:
+ managers.append(key)
+ for_removal.append(key)
for key in for_removal:
del attrs[key]
@@ -51,6 +63,8 @@ def __new__(cls, name, bases, attrs):
for base in bases:
if '__slots__' in dir(base):
slots = slots + base.__slots__
+ if len(managers) > 0:
+ slots = slots + managers
attrs['__slots__'] = slots
attrs['__attributes__'] = attributes
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 8f5d531..8affaa1 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -112,6 +112,10 @@ def profile_GET(request, context):
'url': r'^http://pylxd.test/1.0/certificates/an-certificate$',
},
{
+ 'json': {
+ 'type': 'sync',
+ 'metadata': {},
+ },
'status_code': 202,
'method': 'DELETE',
'url': r'^http://pylxd.test/1.0/certificates/an-certificate$',
diff --git a/pylxd/tests/test_client.py b/pylxd/tests/test_client.py
index 3dbc65e..b0a0030 100644
--- a/pylxd/tests/test_client.py
+++ b/pylxd/tests/test_client.py
@@ -275,22 +275,6 @@ def test_post_200_not_sync(self, Session):
node.post)
@mock.patch('pylxd.client.requests.Session')
- def test_post_202_sync(self, Session):
- """A status code of 202 with sync request raises an exception."""
- response = mock.Mock(**{
- 'status_code': 202,
- 'json.return_value': {'type': 'sync'},
- })
- session = mock.Mock(**{'post.return_value': response})
- Session.return_value = session
-
- node = client._APINode('http://test.com')
-
- self.assertRaises(
- exceptions.LXDAPIException,
- node.post)
-
- @mock.patch('pylxd.client.requests.Session')
def test_post_missing_type_200(self, Session):
"""A missing response type raises an exception."""
response = mock.Mock(**{
@@ -307,22 +291,6 @@ def test_post_missing_type_200(self, Session):
node.post)
@mock.patch('pylxd.client.requests.Session')
- def test_post_missing_type_202(self, Session):
- """A missing response type raises an exception."""
- response = mock.Mock(**{
- 'status_code': 202,
- 'json.return_value': {},
- })
- session = mock.Mock(**{'post.return_value': response})
- Session.return_value = session
-
- node = client._APINode('http://test.com')
-
- self.assertRaises(
- exceptions.LXDAPIException,
- node.post)
-
- @mock.patch('pylxd.client.requests.Session')
def test_put(self, Session):
"""Perform a session put."""
response = mock.Mock(**{
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index 35b17e7..bb3ff6f 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -94,7 +94,7 @@ def create_fail(request, context):
def test_fetch(self):
"""A fetch updates the properties of a container."""
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
an_container.fetch()
@@ -115,7 +115,7 @@ def not_found(request, context):
})
an_container = container.Container(
- name='an-missing-container', _client=self.client)
+ self.client, name='an-missing-container')
self.assertRaises(exceptions.NotFound, an_container.fetch)
@@ -134,14 +134,14 @@ def not_found(request, context):
})
an_container = container.Container(
- name='an-missing-container', _client=self.client)
+ self.client, name='an-missing-container')
self.assertRaises(exceptions.LXDAPIException, an_container.fetch)
def test_update(self):
"""A container is updated."""
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
an_container.architecture = 1
an_container.config = {}
an_container.created_at = 1
@@ -166,7 +166,7 @@ def test_update_partial_objects(self):
def test_rename(self):
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
an_container.rename('an-renamed-container', wait=True)
@@ -178,7 +178,7 @@ def test_delete(self):
# a code path. There should be an assertion here, but
# it's not clear how to assert that, just yet.
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
an_container.delete(wait=True)
@@ -192,7 +192,7 @@ def test_execute(self, _CommandWebsocketClient, _StdinWebsocket):
_CommandWebsocketClient.return_value = fake_websocket
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
stdout, _ = an_container.execute(['echo', 'test'])
@@ -201,7 +201,7 @@ def test_execute(self, _CommandWebsocketClient, _StdinWebsocket):
def test_execute_string(self):
"""A command passed as string raises a TypeError."""
an_container = container.Container(
- name='an-container', _client=self.client)
+ self.client, name='an-container')
self.assertRaises(TypeError, an_container.execute, 'apt-get update')
From e26ab48771ec12ad9a8455ac66921ad721ef4756 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 09:13:30 -0600
Subject: [PATCH 5/9] Convert Snapshot to the model API
---
pylxd/container.py | 41 +++++++++++++++++------------------------
pylxd/model.py | 9 ++++++++-
pylxd/tests/test_container.py | 10 +++++-----
3 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 4286894..0b86b9c 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -18,7 +18,7 @@
from ws4py.client import WebSocketBaseClient
from ws4py.manager import WebSocketManager
-from pylxd import exceptions, managers, mixin, model
+from pylxd import exceptions, managers, model
from pylxd.deprecation import deprecated
from pylxd.operation import Operation
@@ -314,8 +314,17 @@ def handshake_ok(self):
self.close()
-class Snapshot(mixin.Marshallable):
+class Snapshot(model.Model):
"""A container snapshot."""
+ name = model.Attribute()
+ stateful = model.Attribute()
+
+ container = model.Parent()
+
+ @property
+ def api(self):
+ return self.client.api.containers[
+ self.container.name].snapshots[self.name]
@classmethod
def get(cls, client, container, name):
@@ -328,7 +337,7 @@ def get(cls, client, container, name):
raise
snapshot = cls(
- _client=client, _container=container,
+ client, container=container,
**response.json()['metadata'])
# Snapshot names are namespaced in LXD, as
# container-name/snapshot-name. We hide that implementation
@@ -341,8 +350,8 @@ def all(cls, client, container):
response = client.api.containers[container.name].snapshots.get()
return [cls(
- name=snapshot.split('/')[-1], _client=client,
- _container=container)
+ client, name=snapshot.split('/')[-1],
+ container=container)
for snapshot in response.json()['metadata']]
@classmethod
@@ -350,31 +359,15 @@ def create(cls, client, container, name, stateful=False, wait=False):
response = client.api.containers[container.name].snapshots.post(json={
'name': name, 'stateful': stateful})
- snapshot = cls(_client=client, _container=container, name=name)
+ snapshot = cls(client, container=container, name=name)
if wait:
Operation.wait_for_operation(client, response.json()['operation'])
return snapshot
- def __init__(self, **kwargs):
- super(Snapshot, self).__init__()
- for key, value in six.iteritems(kwargs):
- setattr(self, key, value)
-
def rename(self, new_name, wait=False):
"""Rename a snapshot."""
- response = self._client.api.containers[
- self._container.name].snapshots[self.name].post(
- json={'name': new_name})
+ response = self.api.post(json={'name': new_name})
if wait:
Operation.wait_for_operation(
- self._client, response.json()['operation'])
+ self.client, response.json()['operation'])
self.name = new_name
-
- def delete(self, wait=False):
- """Delete a snapshot."""
- response = self._client.api.containers[
- self._container.name].snapshots[self.name].delete()
-
- if wait:
- Operation.wait_for_operation(
- self._client, response.json()['operation'])
diff --git a/pylxd/model.py b/pylxd/model.py
index 012f6ea..bcfdd43 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -33,6 +33,13 @@ class Manager(object):
"""
+class Parent(object):
+ """A parent declaration.
+
+ Child managers must keep a reference to their parent.
+ """
+
+
class ModelType(type):
"""A Model metaclass.
@@ -51,7 +58,7 @@ def __new__(cls, name, bases, attrs):
if type(val) == Attribute:
attributes[key] = val
for_removal.append(key)
- if type(val) == Manager:
+ if type(val) in (Manager, Parent):
managers.append(key)
for_removal.append(key)
for key in for_removal:
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index bb3ff6f..d1b2b86 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -269,8 +269,8 @@ def test_all(self):
self.assertEqual(1, len(snapshots))
self.assertEqual('an-snapshot', snapshots[0].name)
- self.assertEqual(self.client, snapshots[0]._client)
- self.assertEqual(self.container, snapshots[0]._container)
+ self.assertEqual(self.client, snapshots[0].client)
+ self.assertEqual(self.container, snapshots[0].container)
def test_create(self):
"""Create a snapshot."""
@@ -290,7 +290,7 @@ def setUp(self):
def test_rename(self):
"""A snapshot is renamed."""
snapshot = container.Snapshot(
- _client=self.client, _container=self.container,
+ self.client, container=self.container,
name='an-snapshot')
snapshot.rename('an-renamed-snapshot', wait=True)
@@ -300,7 +300,7 @@ def test_rename(self):
def test_delete(self):
"""A snapshot is deleted."""
snapshot = container.Snapshot(
- _client=self.client, _container=self.container,
+ self.client, container=self.container,
name='an-snapshot')
snapshot.delete(wait=True)
@@ -322,7 +322,7 @@ def not_found(request, context):
})
snapshot = container.Snapshot(
- _client=self.client, _container=self.container,
+ self.client, container=self.container,
name='an-snapshot')
self.assertRaises(exceptions.LXDAPIException, snapshot.delete)
From 20c133b5f12ab937b5463699ad8c895895eb6707 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 12:34:05 -0600
Subject: [PATCH 6/9] Remove calls to `update`, opting for `save`
---
pylxd/container.py | 20 ++------------------
pylxd/image.py | 8 --------
pylxd/model.py | 20 ++++++++++++++++----
pylxd/network.py | 6 +++++-
pylxd/profile.py | 13 +------------
5 files changed, 24 insertions(+), 43 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 0b86b9c..5b5ec07 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -44,9 +44,9 @@ class Container(model.Model):
ephemeral = model.Attribute()
expanded_config = model.Attribute()
expanded_devices = model.Attribute()
- name = model.Attribute()
+ name = model.Attribute(readonly=True)
profiles = model.Attribute()
- status = model.Attribute()
+ status = model.Attribute(readonly=True)
snapshots = model.Manager()
files = model.Manager()
@@ -134,22 +134,6 @@ def __init__(self, *args, **kwargs):
"Container.reload is deprecated. Please use Container.sync")(
model.Model.sync)
- def update(self, wait=False):
- """Update the container in lxd from local changes."""
- try:
- marshalled = self.marshall()
- except AttributeError:
- raise exceptions.ObjectIncomplete()
- # These two properties are explicitly not allowed.
- del marshalled['name']
- del marshalled['status']
-
- response = self.api.put(json=marshalled)
-
- if wait:
- Operation.wait_for_operation(
- self.client, response.json()['operation'])
-
def rename(self, name, wait=False):
"""Rename a container."""
response = self.api.post(json={'name': name})
diff --git a/pylxd/image.py b/pylxd/image.py
index e4d02c3..795db3e 100644
--- a/pylxd/image.py
+++ b/pylxd/image.py
@@ -79,14 +79,6 @@ def create(cls, client, image_data, public=False, wait=False):
Operation.wait_for_operation(client, response.json()['operation'])
return cls(client, fingerprint=fingerprint)
- def update(self):
- """Update LXD based on changes to this image."""
- try:
- marshalled = self.marshall()
- except AttributeError:
- raise exceptions.ObjectIncomplete()
- self.api.put(json=marshalled)
-
def export(self):
"""Export the image."""
try:
diff --git a/pylxd/model.py b/pylxd/model.py
index bcfdd43..99538bc 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -21,8 +21,9 @@
class Attribute(object):
"""A metadata class for model attributes."""
- def __init__(self, validator=None):
+ def __init__(self, validator=None, readonly=False):
self.validator = validator
+ self.readonly = False
class Manager(object):
@@ -147,14 +148,24 @@ def sync(self):
setattr(self, key, val)
fetch = deprecated("fetch is deprecated; please use sync")(sync)
- def save(self):
+ def save(self, wait=False):
"""Save data to the server.
This method should write the new data to the server via marshalling.
It should be a no-op when the object is not dirty, to prevent needless
I/O.
"""
- raise NotImplementedError('save is not implemented')
+ try:
+ marshalled = self.marshall()
+ except AttributeError:
+ raise exceptions.ObjectIncomplete()
+
+ response = self.api.put(json=marshalled)
+
+ if wait:
+ Operation.wait_for_operation(
+ self.client, response.json()['operation'])
+ update = deprecated('update is deprecated; please use save')(save)
def delete(self, wait=False):
"""Delete an object from the server."""
@@ -168,5 +179,6 @@ def marshall(self):
"""Marshall the object in preparation for updating to the server."""
marshalled = {}
for key, val in self.__attributes__.items():
- marshalled[key] = getattr(self, key)
+ if not val.readonly:
+ marshalled[key] = getattr(self, key)
return marshalled
diff --git a/pylxd/network.py b/pylxd/network.py
index 5c24165..5b2c5e9 100644
--- a/pylxd/network.py
+++ b/pylxd/network.py
@@ -43,6 +43,10 @@ def all(cls, client):
def api(self):
return self.client.api.networks[self.name]
+ def save(self, wait=False):
+ """Save is not available for networks."""
+ raise NotImplementedError('save is not implemented')
+
def delete(self):
- """Delete an object from the server."""
+ """Delete is not available for networks."""
raise NotImplementedError('delete is not implemented')
diff --git a/pylxd/profile.py b/pylxd/profile.py
index fe68c9d..58c2106 100644
--- a/pylxd/profile.py
+++ b/pylxd/profile.py
@@ -17,7 +17,7 @@
class Profile(model.Model):
"""A LXD profile."""
- name = model.Attribute()
+ name = model.Attribute(readonly=True)
description = model.Attribute()
config = model.Attribute()
devices = model.Attribute()
@@ -64,17 +64,6 @@ def create(cls, client, name, config=None, devices=None):
def api(self):
return self.client.api.profiles[self.name]
- def update(self):
- """Update the profile in LXD based on local changes."""
- try:
- marshalled = self.marshall()
- except AttributeError:
- raise exceptions.ObjectIncomplete()
- # The name property cannot be updated.
- del marshalled['name']
-
- self.api.put(json=marshalled)
-
def rename(self, new):
"""Rename the profile."""
raise NotImplementedError(
From c58f42dcd3b17bd895c1b9c8d582e6944941cb89 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 13:06:54 -0600
Subject: [PATCH 7/9] Fix integration test issues
---
pylxd/container.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pylxd/container.py b/pylxd/container.py
index 5b5ec07..130052f 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -48,6 +48,9 @@ class Container(model.Model):
profiles = model.Attribute()
status = model.Attribute(readonly=True)
+ status_code = model.Attribute()
+ stateful = model.Attribute()
+
snapshots = model.Manager()
files = model.Manager()
From 11155f4246f1bc4971c4b81b77d8ede7d3a60003 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 16:11:12 -0600
Subject: [PATCH 8/9] Fix unit and integration tests
---
pylxd/container.py | 4 ++--
pylxd/model.py | 6 +-----
pylxd/tests/mock_lxd.py | 41 +++++++++++++++++++++++++++++++++++++----
pylxd/tests/test_container.py | 8 --------
4 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index 130052f..e66f0dc 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -48,8 +48,8 @@ class Container(model.Model):
profiles = model.Attribute()
status = model.Attribute(readonly=True)
- status_code = model.Attribute()
- stateful = model.Attribute()
+ status_code = model.Attribute(readonly=True)
+ stateful = model.Attribute(readonly=True)
snapshots = model.Manager()
files = model.Manager()
diff --git a/pylxd/model.py b/pylxd/model.py
index 99538bc..2d185be 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -155,11 +155,7 @@ def save(self, wait=False):
It should be a no-op when the object is not dirty, to prevent needless
I/O.
"""
- try:
- marshalled = self.marshall()
- except AttributeError:
- raise exceptions.ObjectIncomplete()
-
+ marshalled = self.marshall()
response = self.api.put(json=marshalled)
if wait:
diff --git a/pylxd/tests/mock_lxd.py b/pylxd/tests/mock_lxd.py
index 8affaa1..e544aff 100644
--- a/pylxd/tests/mock_lxd.py
+++ b/pylxd/tests/mock_lxd.py
@@ -138,17 +138,50 @@ def profile_GET(request, context):
'url': r'^http://pylxd.test/1.0/containers$',
},
{
- 'text': json.dumps({
+ 'json': {
'type': 'sync',
'metadata': {
'name': 'an-container',
+
+ 'architecture': "x86_64",
+ 'config': {
+ 'security.privileged': "true",
+ },
+ 'created_at': "1983-06-16T00:00:00-00:00",
+ 'devices': {
+ 'root': {
+ 'path': "/",
+ 'type': "disk"
+ }
+ },
'ephemeral': True,
- }}),
+ 'expanded_config': {
+ 'security.privileged': "true",
+ },
+ 'expanded_devices': {
+ 'eth0': {
+ 'name': "eth0",
+ 'nictype': "bridged",
+ 'parent': "lxdbr0",
+ 'type': "nic"
+ },
+ 'root': {
+ 'path': "/",
+ 'type': "disk"
+ }
+ },
+ 'profiles': [
+ "default"
+ ],
+ 'stateful': False,
+ 'status': "Running",
+ 'status_code': 103
+ }},
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container$',
},
{
- 'text': json.dumps({
+ 'json': {
'type': 'sync',
'metadata': {
'status': 'Running',
@@ -176,7 +209,7 @@ def profile_GET(request, context):
},
'pid': 69,
'processes': 100,
- }}),
+ }},
'method': 'GET',
'url': r'^http://pylxd.test/1.0/containers/an-container/state$', # NOQA
},
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index d1b2b86..167a1a3 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -156,14 +156,6 @@ def test_update(self):
self.assertTrue(an_container.ephemeral)
- def test_update_partial_objects(self):
- """A partially fetched profile can't be pushed."""
- an_container = self.client.containers.all()[0]
-
- self.assertRaises(
- exceptions.ObjectIncomplete,
- an_container.update)
-
def test_rename(self):
an_container = container.Container(
self.client, name='an-container')
From 91bb82661e49b83339b59abf53fc36b24ec2548b Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 16:49:21 -0600
Subject: [PATCH 9/9] Update deprecated API calls in tests
---
pylxd/container.py | 4 ++--
pylxd/tests/test_certificate.py | 2 +-
pylxd/tests/test_container.py | 10 +++++-----
pylxd/tests/test_image.py | 10 +++++-----
pylxd/tests/test_profile.py | 8 ++++----
5 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/pylxd/container.py b/pylxd/container.py
index e66f0dc..199c629 100644
--- a/pylxd/container.py
+++ b/pylxd/container.py
@@ -102,7 +102,7 @@ def all(cls, client):
Containers returned from this method will only have the name
set, as that is the only property returned from LXD. If more
- information is needed, `Container.fetch` is the method call
+ information is needed, `Container.sync` is the method call
that should be used.
"""
response = client.api.containers.get()
@@ -155,7 +155,7 @@ def _set_state(self, state, timeout=30, force=True, wait=False):
if wait:
Operation.wait_for_operation(
self.client, response.json()['operation'])
- self.fetch()
+ self.sync()
def state(self):
response = self.api.state.get()
diff --git a/pylxd/tests/test_certificate.py b/pylxd/tests/test_certificate.py
index e3a7f86..ef5a57f 100644
--- a/pylxd/tests/test_certificate.py
+++ b/pylxd/tests/test_certificate.py
@@ -48,7 +48,7 @@ def test_fetch(self):
an_certificate = certificate.Certificate(
self.client, fingerprint='an-certificate')
- an_certificate.fetch()
+ an_certificate.sync()
self.assertEqual('certificate-content', an_certificate.certificate)
diff --git a/pylxd/tests/test_container.py b/pylxd/tests/test_container.py
index 167a1a3..e9c5968 100644
--- a/pylxd/tests/test_container.py
+++ b/pylxd/tests/test_container.py
@@ -92,11 +92,11 @@ def create_fail(request, context):
container.Container.create, self.client, config)
def test_fetch(self):
- """A fetch updates the properties of a container."""
+ """A sync updates the properties of a container."""
an_container = container.Container(
self.client, name='an-container')
- an_container.fetch()
+ an_container.sync()
self.assertTrue(an_container.ephemeral)
@@ -117,7 +117,7 @@ def not_found(request, context):
an_container = container.Container(
self.client, name='an-missing-container')
- self.assertRaises(exceptions.NotFound, an_container.fetch)
+ self.assertRaises(exceptions.NotFound, an_container.sync)
def test_fetch_error(self):
"""LXDAPIException is raised on error."""
@@ -136,7 +136,7 @@ def not_found(request, context):
an_container = container.Container(
self.client, name='an-missing-container')
- self.assertRaises(exceptions.LXDAPIException, an_container.fetch)
+ self.assertRaises(exceptions.LXDAPIException, an_container.sync)
def test_update(self):
"""A container is updated."""
@@ -152,7 +152,7 @@ def test_update(self):
an_container.profiles = 1
an_container.status = 1
- an_container.update(wait=True)
+ an_container.save(wait=True)
self.assertTrue(an_container.ephemeral)
diff --git a/pylxd/tests/test_image.py b/pylxd/tests/test_image.py
index bac6a5e..d889553 100644
--- a/pylxd/tests/test_image.py
+++ b/pylxd/tests/test_image.py
@@ -90,15 +90,15 @@ def create_fail(request, context):
def test_update(self):
"""An image is updated."""
a_image = self.client.images.all()[0]
- a_image.fetch()
+ a_image.sync()
- a_image.update()
+ a_image.save()
def test_fetch(self):
"""A partial object is fetched and populated."""
a_image = self.client.images.all()[0]
- a_image.fetch()
+ a_image.sync()
self.assertEqual(1, a_image.size)
@@ -119,7 +119,7 @@ def not_found(request, context):
a_image = image.Image(self.client, fingerprint=fingerprint)
- self.assertRaises(exceptions.NotFound, a_image.fetch)
+ self.assertRaises(exceptions.NotFound, a_image.sync)
def test_fetch_error(self):
"""A 500 error raises LXDAPIException."""
@@ -138,7 +138,7 @@ def not_found(request, context):
a_image = image.Image(self.client, fingerprint=fingerprint)
- self.assertRaises(exceptions.LXDAPIException, a_image.fetch)
+ self.assertRaises(exceptions.LXDAPIException, a_image.sync)
def test_delete(self):
"""An image is deleted."""
diff --git a/pylxd/tests/test_profile.py b/pylxd/tests/test_profile.py
index 2d7b80a..1cb79f6 100644
--- a/pylxd/tests/test_profile.py
+++ b/pylxd/tests/test_profile.py
@@ -97,7 +97,7 @@ def test_update(self):
# it's not clear how to assert that, just yet.
an_profile = profile.Profile.get(self.client, 'an-profile')
- an_profile.update()
+ an_profile.save()
self.assertEqual({}, an_profile.config)
@@ -105,7 +105,7 @@ def test_fetch(self):
"""A partially fetched profile is made complete."""
an_profile = self.client.profiles.all()[0]
- an_profile.fetch()
+ an_profile.sync()
self.assertEqual('An description', an_profile.description)
@@ -125,7 +125,7 @@ def not_found(request, context):
an_profile = profile.Profile(self.client, name='an-profile')
- self.assertRaises(exceptions.NotFound, an_profile.fetch)
+ self.assertRaises(exceptions.NotFound, an_profile.sync)
def test_fetch_error(self):
"""LXDAPIException is raised on fetch error."""
@@ -143,7 +143,7 @@ def error(request, context):
an_profile = profile.Profile(self.client, name='an-profile')
- self.assertRaises(exceptions.LXDAPIException, an_profile.fetch)
+ self.assertRaises(exceptions.LXDAPIException, an_profile.sync)
def test_delete(self):
"""A profile is deleted."""
More information about the lxc-devel
mailing list