Merge lp:~fwereade/pyjuju/webdav-storage into lp:pyjuju

Proposed by William Reade
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 297
Merged at revision: 294
Proposed branch: lp:~fwereade/pyjuju/webdav-storage
Merge into: lp:pyjuju
Prerequisite: lp:~fwereade/pyjuju/cobbler-instance-ids
Diff against target: 353 lines (+215/-24)
8 files modified
ensemble/environment/config.py (+3/-1)
ensemble/environment/tests/test_config.py (+2/-1)
ensemble/providers/orchestra/__init__.py (+15/-20)
ensemble/providers/orchestra/files.py (+35/-0)
ensemble/providers/orchestra/tests/test_bootstrap.py (+10/-0)
ensemble/providers/orchestra/tests/test_files.py (+101/-0)
ensemble/providers/orchestra/tests/test_provider.py (+0/-2)
ensemble/providers/orchestra/tests/test_state.py (+49/-0)
To merge this branch: bzr merge lp:~fwereade/pyjuju/webdav-storage
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
Review via email: mp+69453@code.launchpad.net

Description of the change

WebDAV FileStorage class now exists, and is hooked up to the orchestra MachineProvider; as a bonus, so are save_state and load_state, because they're trivial now.

NOTE: also depends on lp:~fwereade/ensemble/generic-state-ops

To post a comment you must log in.
lp:~fwereade/pyjuju/webdav-storage updated
291. By William Reade

merge from lp:~fwereade/ensemble/generic-state-ops

292. By William Reade

merge and fix from lp:~fwereade/ensemble/cobbler-instance-ids

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Very nice code as well, thanks. +1, with one constraint:

[1]

The new entry points in orchestra/__init__.py need some quick
test coverage to ensure they basically work at least.

review: Approve
lp:~fwereade/pyjuju/webdav-storage updated
293. By William Reade

storage-url is no longer mandatory in config; if not present, assume %(orchestra-server)s/webdav

294. By William Reade

merge lp:~fwereade/ensemble/generic-state-ops

295. By William Reade

merge lp:~fwereade/ensemble/cobbler-instance-ids

296. By William Reade

per review, direct verification tests for the happy path of orchestra.MachineProvider's .save_state and .load_state

297. By William Reade

merge trunk

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

William, if you are reading this without a second review, please go ahead and merge it.

The branch looks good, and has been waiting for the second review for long enough.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ensemble/environment/config.py'
--- ensemble/environment/config.py 2011-07-20 23:32:19 +0000
+++ ensemble/environment/config.py 2011-08-03 06:46:34 +0000
@@ -50,7 +50,9 @@
50 "orchestra-pass": String(),50 "orchestra-pass": String(),
51 "admin-secret": String(),51 "admin-secret": String(),
52 "acquired-mgmt-class": String(),52 "acquired-mgmt-class": String(),
53 "available-mgmt-class": String()}),53 "available-mgmt-class": String(),
54 "storage-url": String()},
55 optional=["storage-url"]),
54 "dummy": KeyDict({})}))},56 "dummy": KeyDict({})}))},
55 optional=["default"])57 optional=["default"])
5658
5759
=== modified file 'ensemble/environment/tests/test_config.py'
--- ensemble/environment/tests/test_config.py 2011-07-21 08:15:51 +0000
+++ ensemble/environment/tests/test_config.py 2011-08-03 06:46:34 +0000
@@ -36,6 +36,7 @@
36 admin-secret: garden36 admin-secret: garden
37 acquired-mgmt-class: acquired37 acquired-mgmt-class: acquired
38 available-mgmt-class: available38 available-mgmt-class: available
39 storage-url: http://somewhereel.se
39"""40"""
4041
41class EnvironmentsConfigTestBase(TestCase):42class EnvironmentsConfigTestBase(TestCase):
@@ -554,7 +555,7 @@
554 def test_orchestra_schema_requires(self):555 def test_orchestra_schema_requires(self):
555 requires = (556 requires = (
556 "type orchestra-server orchestra-user orchestra-pass "557 "type orchestra-server orchestra-user orchestra-pass "
557 "admin-secret acquired-mgmt-class available-mgmt-class").split()558 "admin-secret acquired-mgmt-class available-mgmt-class ").split()
558 for require in requires:559 for require in requires:
559 config = yaml.load(SAMPLE_ORCHESTRA)560 config = yaml.load(SAMPLE_ORCHESTRA)
560 del config["environments"]["sample"][require]561 del config["environments"]["sample"][require]
561562
=== modified file 'ensemble/providers/orchestra/__init__.py'
--- ensemble/providers/orchestra/__init__.py 2011-07-20 07:49:20 +0000
+++ ensemble/providers/orchestra/__init__.py 2011-08-03 06:46:34 +0000
@@ -1,9 +1,11 @@
1from twisted.internet.defer import fail, succeed1from twisted.internet.defer import fail
22
3from ensemble.errors import EnvironmentNotFound3from ensemble.errors import EnvironmentNotFound
4from ensemble.providers.common.bootstrap import Bootstrap4from ensemble.providers.common.bootstrap import Bootstrap
5from ensemble.providers.common.state import SaveState, LoadState
56
6from .cobbler import CobblerConnect7from .cobbler import CobblerConnect
8from .files import FileStorage
7from .launch import OrchestraLaunchMachine9from .launch import OrchestraLaunchMachine
810
911
@@ -70,23 +72,27 @@
70 raise NotImplementedError()72 raise NotImplementedError()
7173
72 def save_state(self, state):74 def save_state(self, state):
73 """75 """Save state to the provider.
74 Save state to the provider.
7576
76 @param state77 @param state
77 @type dict78 @type dict
78 """79 """
79 return succeed(None)80 serializer = SaveState(self)
81 return serializer.run(state)
8082
81 def load_state(self):83 def load_state(self):
82 """84 """Load state from the provider.
83 Load state from the provider, returns a dictionary.85
84 """86 @return: a dictionary.
85 raise NotImplementedError()87 """
88 deserializer = LoadState(self)
89 return deserializer.run()
8690
87 def get_file_storage(self):91 def get_file_storage(self):
88 """Retrieve the provider C{FileStorage} abstraction."""92 """Retrieve the provider C{FileStorage} abstraction."""
89 raise NotImplementedError()93 if "storage-url" not in self.config:
94 return FileStorage("http://%(orchestra-server)s/webdav" % self.config)
95 return FileStorage(self.config["storage-url"])
9096
91 def get_serialization_data(self):97 def get_serialization_data(self):
92 """Return a dictionary serialization of the provider configuration.98 """Return a dictionary serialization of the provider configuration.
@@ -124,14 +130,3 @@
124 @raise: EnvironmentNotFound or EnvironmentPending130 @raise: EnvironmentNotFound or EnvironmentPending
125 """131 """
126 return fail(EnvironmentNotFound())132 return fail(EnvironmentNotFound())
127
128
129class FileStorage(object):
130
131 def get(self, name):
132 """Returns open file or fails with FileNotFound"""
133 raise NotImplementedError()
134
135 def put(self, remote_path, local_path):
136 """Copies contents of local_path to remote_path"""
137 raise NotImplementedError()
138133
=== added file 'ensemble/providers/orchestra/files.py'
--- ensemble/providers/orchestra/files.py 1970-01-01 00:00:00 +0000
+++ ensemble/providers/orchestra/files.py 2011-08-03 06:46:34 +0000
@@ -0,0 +1,35 @@
1from cStringIO import StringIO
2
3from twisted.web.client import getPage
4from twisted.web.error import Error
5
6from ensemble.errors import FileNotFound
7
8
9class FileStorage(object):
10
11 def __init__(self, base_url):
12 self._base_url = base_url
13
14 def _key_to_url(self, name):
15 return "%s/%s" % (self._base_url, name)
16
17 def get(self, name):
18 url = self._key_to_url(name)
19 d = getPage(url)
20 d.addCallback(StringIO)
21
22 def convert_404(failure):
23 failure.trap(Error)
24 if failure.value.status == "404":
25 raise FileNotFound(url)
26 return failure
27 d.addErrback(convert_404)
28 return d
29
30 def put(self, remote_path, file_object):
31 url = self._key_to_url(remote_path)
32 data = file_object.read()
33 d = getPage(url, method="PUT", postdata=data)
34 d.addCallback(lambda _: True)
35 return d
036
=== modified file 'ensemble/providers/orchestra/tests/test_bootstrap.py'
--- ensemble/providers/orchestra/tests/test_bootstrap.py 2011-08-02 08:07:12 +0000
+++ ensemble/providers/orchestra/tests/test_bootstrap.py 2011-08-03 06:46:34 +0000
@@ -1,4 +1,5 @@
1from xmlrpclib import Fault1from xmlrpclib import Fault
2from yaml import dump
23
3from twisted.internet.defer import fail, succeed4from twisted.internet.defer import fail, succeed
4from twisted.web.xmlrpc import Proxy5from twisted.web.xmlrpc import Proxy
@@ -14,6 +15,8 @@
14 "orchestra-pass": "pass",15 "orchestra-pass": "pass",
15 "acquired-mgmt-class": "acquired",16 "acquired-mgmt-class": "acquired",
16 "available-mgmt-class": "available",17 "available-mgmt-class": "available",
18 "admin-secret": "SEEKRIT",
19 "storage-url": "http://somewhe.re/webdav",
17 "admin-secret": "SEEKRIT"}20 "admin-secret": "SEEKRIT"}
1821
1922
@@ -92,6 +95,12 @@
92 "TOKEN")95 "TOKEN")
93 self.mocker.result(succeed("[some-timestamp]_power"))96 self.mocker.result(succeed("[some-timestamp]_power"))
9497
98 def mock_save_state(self):
99 getPage = self.mocker.replace("twisted.web.client.getPage")
100 getPage("http://somewhe.re/webdav/provider-state",
101 method="PUT", postdata=dump({"zookeeper-instances": ["winston-uid"]}))
102 self.mocker.result(succeed(None))
103
95 def test_no_machines_available(self):104 def test_no_machines_available(self):
96 self.mock_proxy()105 self.mock_proxy()
97 self.mock_get_systems(acceptable=False)106 self.mock_get_systems(acceptable=False)
@@ -137,6 +146,7 @@
137 self.mock_acquire_system()146 self.mock_acquire_system()
138 self.mock_set_ks_meta()147 self.mock_set_ks_meta()
139 self.mock_start_system()148 self.mock_start_system()
149 self.mock_save_state()
140 self.mocker.replay()150 self.mocker.replay()
141151
142 provider = MachineProvider("tetrascape", CONFIG)152 provider = MachineProvider("tetrascape", CONFIG)
143153
=== added file 'ensemble/providers/orchestra/tests/test_files.py'
--- ensemble/providers/orchestra/tests/test_files.py 1970-01-01 00:00:00 +0000
+++ ensemble/providers/orchestra/tests/test_files.py 2011-08-03 06:46:34 +0000
@@ -0,0 +1,101 @@
1from cStringIO import StringIO
2
3from twisted.internet.defer import fail, succeed
4from twisted.web.error import Error
5
6from ensemble.errors import FileNotFound
7from ensemble.lib.testing import TestCase
8from ensemble.providers.orchestra import MachineProvider
9
10
11def get_file_storage(with_storage_url=True):
12 config = {"storage-url": "http://somewhe.re/webdav",
13 "orchestra-server": "somewhereel.se",
14 "orchestra-user": "user",
15 "orchestra-pass": "pass",
16 "acquired-mgmt-class": "acquired",
17 "available-mgmt-class": "available"}
18 if not with_storage_url:
19 del config["storage-url"]
20 provider = MachineProvider("blah", config)
21 return provider.get_file_storage()
22
23
24class FileStorageTest(TestCase):
25
26 def test_get_works_no_storage_url(self):
27 getPage = self.mocker.replace("twisted.web.client.getPage")
28 getPage("http://somewhereel.se/webdav/rubber/chicken")
29 self.mocker.result(succeed("pulley"))
30 self.mocker.replay()
31
32 fs = get_file_storage(with_storage_url=False)
33 d = fs.get("rubber/chicken")
34
35 def verify(result):
36 self.assertEquals(result.read(), "pulley")
37 d.addCallback(verify)
38 return d
39
40 def test_get_works(self):
41 getPage = self.mocker.replace("twisted.web.client.getPage")
42 getPage("http://somewhe.re/webdav/rubber/chicken")
43 self.mocker.result(succeed("pulley"))
44 self.mocker.replay()
45
46 fs = get_file_storage()
47 d = fs.get("rubber/chicken")
48
49 def verify(result):
50 self.assertEquals(result.read(), "pulley")
51 d.addCallback(verify)
52 return d
53
54 def test_get_fails(self):
55 getPage = self.mocker.replace("twisted.web.client.getPage")
56 getPage("http://somewhe.re/webdav/rubber/chicken")
57 self.mocker.result(fail(Error("404")))
58 self.mocker.replay()
59
60 fs = get_file_storage()
61 d = fs.get("rubber/chicken")
62 self.assertFailure(d, FileNotFound)
63 return d
64
65 def test_get_errors(self):
66 getPage = self.mocker.replace("twisted.web.client.getPage")
67 getPage("http://somewhe.re/webdav/rubber/chicken")
68 self.mocker.result(fail(Error("500")))
69 self.mocker.replay()
70
71 fs = get_file_storage()
72 d = fs.get("rubber/chicken")
73 self.assertFailure(d, Error)
74 return d
75
76 def test_put_works(self):
77 getPage = self.mocker.replace("twisted.web.client.getPage")
78 getPage("http://somewhe.re/webdav/rubber/chicken",
79 method="PUT", postdata="pulley")
80 self.mocker.result(succeed(None))
81 self.mocker.replay()
82
83 fs = get_file_storage()
84 d = fs.put("rubber/chicken", StringIO("pulley"))
85
86 def verify(result):
87 self.assertEquals(result, True)
88 d.addCallback(verify)
89 return d
90
91 def test_put_errors(self):
92 getPage = self.mocker.replace("twisted.web.client.getPage")
93 getPage("http://somewhe.re/webdav/rubber/chicken",
94 method="PUT", postdata="pulley")
95 self.mocker.result(fail(Error("500")))
96 self.mocker.replay()
97
98 fs = get_file_storage()
99 d = fs.put("rubber/chicken", StringIO("pulley"))
100 self.assertFailure(d, Error)
101 return d
0102
=== modified file 'ensemble/providers/orchestra/tests/test_provider.py'
--- ensemble/providers/orchestra/tests/test_provider.py 2011-07-30 11:50:10 +0000
+++ ensemble/providers/orchestra/tests/test_provider.py 2011-08-03 06:46:34 +0000
@@ -15,5 +15,3 @@
15 "tetrascape", CONFIG)15 "tetrascape", CONFIG)
16 self.assertEquals(provider.environment_name, "tetrascape")16 self.assertEquals(provider.environment_name, "tetrascape")
17 self.assertEquals(provider.config, CONFIG)17 self.assertEquals(provider.config, CONFIG)
18
19
2018
=== added file 'ensemble/providers/orchestra/tests/test_state.py'
--- ensemble/providers/orchestra/tests/test_state.py 1970-01-01 00:00:00 +0000
+++ ensemble/providers/orchestra/tests/test_state.py 2011-08-03 06:46:34 +0000
@@ -0,0 +1,49 @@
1from yaml import dump
2
3from twisted.internet.defer import succeed
4
5from ensemble.lib.testing import TestCase
6from ensemble.providers.orchestra import MachineProvider
7
8
9def get_provider():
10 config = {"orchestra-server": "somewhe.re",
11 "orchestra-user": "user",
12 "orchestra-pass": "pass",
13 "acquired-mgmt-class": "acquired",
14 "available-mgmt-class": "available"}
15 return MachineProvider("tetrascape", config)
16
17
18class StateTest(TestCase):
19
20 def test_save(self):
21 state = {"foo": "blah blah"}
22 getPage = self.mocker.replace("twisted.web.client.getPage")
23 getPage("http://somewhe.re/webdav/provider-state",
24 method="PUT", postdata=dump(state))
25 self.mocker.result(succeed(None))
26 self.mocker.replay()
27
28 provider = get_provider()
29 d = provider.save_state(state)
30
31 def verify(result):
32 self.assertEquals(result, True)
33 d.addCallback(verify)
34 return d
35
36 def test_load(self):
37 expect_state = {"foo": "blah blah"}
38 getPage = self.mocker.replace("twisted.web.client.getPage")
39 getPage("http://somewhe.re/webdav/provider-state")
40 self.mocker.result(succeed(dump(expect_state)))
41 self.mocker.replay()
42
43 provider = get_provider()
44 d = provider.load_state()
45
46 def verify(state):
47 self.assertEquals(state, expect_state)
48 d.addCallback(verify)
49 return d

Subscribers

People subscribed via source and target branches

to status/vote changes: