Merge lp:~gz/pyjuju/os_scheduler_hints_1049858 into lp:pyjuju

Proposed by Martin Packman
Status: Merged
Approved by: Jim Baker
Approved revision: 599
Merged at revision: 601
Proposed branch: lp:~gz/pyjuju/os_scheduler_hints_1049858
Merge into: lp:pyjuju
Diff against target: 221 lines (+105/-3)
7 files modified
juju/providers/openstack/client.py (+3/-1)
juju/providers/openstack/launch.py (+5/-1)
juju/providers/openstack/provider.py (+17/-0)
juju/providers/openstack/tests/__init__.py (+2/-0)
juju/providers/openstack/tests/test_client.py (+30/-0)
juju/providers/openstack/tests/test_launch.py (+19/-1)
juju/providers/openstack/tests/test_provider.py (+29/-0)
To merge this branch: bzr merge lp:~gz/pyjuju/os_scheduler_hints_1049858
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+131642@code.launchpad.net

Description of the change

Support Openstack scheduler hints as a constraint

Adds Openstack provider specific 'os-scheduler-hints' pseudo-constraint
that instead of helping determine the flavor to use, is simply passed
through when creating the server. This depends on the SchedulerHints
extension that can then influence where to spin up the underlying vm.

As the api expects a json object of string to object pairs, the format
of the constraint is simply json too, with basic validation.

https://codereview.appspot.com/6783059/

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Reviewers: mp+131642_code.launchpad.net,

Message:
Please take a look.

Description:
Support Openstack scheduler hints as a constraint

Adds Openstack provider specific 'os-scheduler-hints' pseudo-constraint
that instead of helping determine the flavor to use, is simply passed
through when creating the server. This depends on the SchedulerHints
extension that can then influence where to spin up the underlying vm.

As the api expects a json object of string to object pairs, the format
of the constraint is simply json too, with basic validation.

https://code.launchpad.net/~gz/juju/os_scheduler_hints_1049858/+merge/131642

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6783059/

Affected files:
   A [revision details]
   M juju/providers/openstack/client.py
   M juju/providers/openstack/launch.py
   M juju/providers/openstack/provider.py
   M juju/providers/openstack/tests/__init__.py
   M juju/providers/openstack/tests/test_client.py
   M juju/providers/openstack/tests/test_launch.py
   M juju/providers/openstack/tests/test_provider.py

Revision history for this message
Jim Baker (jimbaker) wrote :

+1, LGTM, with one minor

https://codereview.appspot.com/6783059/diff/1/juju/providers/openstack/launch.py
File juju/providers/openstack/launch.py (right):

https://codereview.appspot.com/6783059/diff/1/juju/providers/openstack/launch.py#newcode95
juju/providers/openstack/launch.py:95: hints =
self._constraints["os-scheduler-hints"]
Is this key guaranteed to be set? run_server does not assume it is set,
but the constraint is currently registered. However, the comments on
that registration muse that it might not be registered. Given the flow,
maybe should just be:

hints = self._constraints.get("os-scheduler-hints")

https://codereview.appspot.com/6783059/

Revision history for this message
Martin Packman (gz) wrote :

On 2012/10/29 11:51:37, jimbaker wrote:

> juju/providers/openstack/launch.py:95: hints =
> self._constraints["os-scheduler-hints"]
> Is this key guaranteed to be set? run_server does not assume it is
set, but the
> constraint is currently registered. However, the comments on that
registration
> muse that it might not be registered. Given the flow, maybe should
just be:

> hints = self._constraints.get("os-scheduler-hints")

This does look a little confusing, but see
machine.constraints.Constraints.__getitem__ - a KeyError is raised only
if the constraint key name does not exist, and the pre-registered
default is returned if no constraint of that name was given.

https://codereview.appspot.com/6783059/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'juju/providers/openstack/client.py'
2--- juju/providers/openstack/client.py 2012-10-03 15:16:42 +0000
3+++ juju/providers/openstack/client.py 2012-10-26 15:42:22 +0000
4@@ -391,7 +391,7 @@
5 return self.delete(["servers", server_id], code=204)
6
7 def run_server(self, image_id, flavor_id, name, security_group_names=None,
8- user_data=None):
9+ user_data=None, scheduler_hints=None):
10 server = {
11 'name': name,
12 'flavorRef': flavor_id,
13@@ -402,6 +402,8 @@
14 if security_group_names is not None:
15 server["security_groups"] = [{'name': n}
16 for n in security_group_names]
17+ if scheduler_hints is not None:
18+ server["OS-SCH-HNT:scheduler_hints"] = scheduler_hints
19 return self.post(["servers"], {'server': server},
20 root="server", code=202)
21
22
23=== modified file 'juju/providers/openstack/launch.py'
24--- juju/providers/openstack/launch.py 2012-08-21 16:30:17 +0000
25+++ juju/providers/openstack/launch.py 2012-10-26 15:42:22 +0000
26@@ -92,13 +92,17 @@
27 flavors = yield self._provider.nova.list_flavor_details()
28 flavor_id = _solve_flavor(self._constraints, flavor_name, flavors)
29
30+ hints = self._constraints["os-scheduler-hints"]
31+
32 server = yield self._provider.nova.run_server(
33 name="juju %s instance %s" %
34 (self._provider.environment_name, machine_id,),
35 image_id=image_id,
36 flavor_id=flavor_id,
37 security_group_names=security_groups,
38- user_data=user_data)
39+ user_data=user_data,
40+ scheduler_hints=hints,
41+ )
42
43 if self._master:
44 yield filestorage.put(id_name, StringIO(str(server['id'])))
45
46=== modified file 'juju/providers/openstack/provider.py'
47--- juju/providers/openstack/provider.py 2012-09-05 11:07:35 +0000
48+++ juju/providers/openstack/provider.py 2012-10-26 15:42:22 +0000
49@@ -11,6 +11,7 @@
50 """
51
52 import logging
53+import json
54
55 from twisted.internet.defer import inlineCallbacks, returnValue
56
57@@ -33,6 +34,15 @@
58 log = logging.getLogger("juju.openstack")
59
60
61+def _convert_scheduler_hints(string):
62+ """Check constraint value suitable for Nova SchedulerHints extension"""
63+ obj = json.loads(string)
64+ if not isinstance(obj, dict):
65+ raise ValueError("Need json object of key/value strings")
66+ # GZ 2012-10-26: Does nova have other restrictions on what it will accept?
67+ return obj
68+
69+
70 class MachineProvider(MachineProviderBase):
71 """MachineProvider for use in an OpenStack environment"""
72
73@@ -83,6 +93,13 @@
74 returnValue(cs)
75 cs = yield super(MachineProvider, self).get_constraint_set()
76
77+ # Pseudo-constraint that does not affect flavor selected but is passed
78+ # through to server creation for influencing the scheduler, for
79+ # instance in the placement of the server.
80+ # Perhaps only register this constraint if the deployment advertises
81+ # the SchedulerHints extension as available?
82+ cs.register("os-scheduler-hints", converter=_convert_scheduler_hints)
83+
84 # Fetch provider defined instance types (just names)
85 flavors = yield self.nova.list_flavors()
86 flavor_names = [f['name'] for f in flavors]
87
88=== modified file 'juju/providers/openstack/tests/__init__.py'
89--- juju/providers/openstack/tests/__init__.py 2012-08-24 19:22:13 +0000
90+++ juju/providers/openstack/tests/__init__.py 2012-10-26 15:42:22 +0000
91@@ -47,6 +47,8 @@
92
93 def setup_constraints(self):
94 self.constraint_set = ConstraintSet(self.provider_type)
95+ self.constraint_set.register("os-scheduler-hints",
96+ converter=json.loads)
97 self.constraint_set.register_generics(
98 [f['name'] for f in self.default_flavors])
99
100
101=== modified file 'juju/providers/openstack/tests/test_client.py'
102--- juju/providers/openstack/tests/test_client.py 2012-09-28 15:53:06 +0000
103+++ juju/providers/openstack/tests/test_client.py 2012-10-26 15:42:22 +0000
104@@ -198,6 +198,36 @@
105 deferred = osc.authenticate()
106 return self.assertFailure(deferred, errors.SSLVerificationError)
107
108+ def is_server_with_hints(self, producer):
109+ obj = json.loads(producer.content)
110+ self.assertEqual({"server": {
111+ "imageRef": "an-image",
112+ "flavorRef": "a-flavor",
113+ "name": "Test",
114+ "OS-SCH-HNT:scheduler_hints": {"hint-key": "hint-val"},
115+ }}, obj)
116+ return True
117+
118+ @defer.inlineCallbacks
119+ def test_run_server_passes_hints(self):
120+ config, osc = self.make_client_legacy()
121+ self.mock_agent.request("POST",
122+ "https://testing.invalid/compute/servers", mocker.ANY,
123+ mocker.MATCH(self.is_server_with_hints))
124+ response = FakeResponse(202, http_headers.Headers({
125+ "Content-Type": ["application/json"],
126+ }),
127+ json.dumps({'server': {
128+ }}))
129+ self.mocker.result(defer.succeed(response))
130+ self.mocker.replay()
131+ # Fake having already authenticated by setting token and services
132+ osc.token = "tok"
133+ osc.services = {"compute": "https://testing.invalid/compute"}
134+ novac = client._NovaClient(osc)
135+ result = yield novac.run_server("an-image", "a-flavor", "Test",
136+ scheduler_hints={"hint-key": "hint-val"})
137+
138
139 class TestReauthentication(testing.TestCase):
140
141
142=== modified file 'juju/providers/openstack/tests/test_launch.py'
143--- juju/providers/openstack/tests/test_launch.py 2012-09-10 03:20:20 +0000
144+++ juju/providers/openstack/tests/test_launch.py 2012-10-26 15:42:22 +0000
145@@ -32,13 +32,15 @@
146 self.nova.list_flavor_details()
147 self.mocker.result(succeed(self.default_flavors))
148
149- def expect_run_server(self, machine_id, cc_match, response, flavor_id=1):
150+ def expect_run_server(self, machine_id, cc_match, response, flavor_id=1,
151+ hints=None):
152 self.nova.run_server(
153 name="juju testing instance " + machine_id,
154 image_id=42,
155 flavor_id=flavor_id,
156 security_group_names=["juju-x", "juju-y"],
157 user_data=MATCH(cc_match),
158+ scheduler_hints=hints,
159 )
160 self.mocker.result(succeed(response))
161
162@@ -128,6 +130,22 @@
163 self.mocker.replay()
164 return provider.launch("1", constraints=["cpu=2", "mem=3G"])
165
166+ def test_start_machine_with_scheduler_hints(self):
167+ provider = MockedLaunchProvider(self.mocker)
168+ provider.expect_zookeeper_machines(1000, "master.invalid")
169+ provider.expect_launch_setup("1")
170+ provider.expect_run_server(
171+ "1",
172+ self.get_cc_matcher("1", provider),
173+ response={
174+ 'id': 1001,
175+ 'addresses': {'public': []},
176+ },
177+ hints={"hint-key": "hint-value"})
178+ self.mocker.replay()
179+ return provider.launch("1", constraints=[
180+ "os-scheduler-hints={\"hint-key\": \"hint-value\"}"])
181+
182 def test_start_machine(self):
183 provider = MockedLaunchProvider(self.mocker)
184 provider.expect_zookeeper_machines(1000, "master.invalid")
185
186=== modified file 'juju/providers/openstack/tests/test_provider.py'
187--- juju/providers/openstack/tests/test_provider.py 2012-08-28 16:11:04 +0000
188+++ juju/providers/openstack/tests/test_provider.py 2012-10-26 15:42:22 +0000
189@@ -192,3 +192,32 @@
190 cs2 = yield provider.get_constraint_set()
191 self.assertIsInstance(cs2, ConstraintSet)
192 self.assertEqual(cs, cs2)
193+
194+ def create_constraint_set(self):
195+ self.expect_nova_get("flavors",
196+ response={'flavors': self.default_flavors})
197+ self.mocker.replay()
198+ provider = self.get_provider()
199+ return provider.get_constraint_set()
200+
201+ @inlineCallbacks
202+ def test_parse_scheduler_hints_one(self):
203+ cs = yield self.create_constraint_set()
204+ c = cs.parse(["os-scheduler-hints={\"hint-key\": \"hint-val\"}"])
205+ self.assertEqual({"hint-key": "hint-val"}, c["os-scheduler-hints"])
206+
207+ @inlineCallbacks
208+ def test_parse_scheduler_hints_bad_value(self):
209+ cs = yield self.create_constraint_set()
210+ err = self.assertRaises(errors.ConstraintError,
211+ cs.parse, ["os-scheduler-hints=notjson"])
212+ self.assertRegexpMatches(str(err),
213+ "Bad 'os-scheduler-hints' constraint 'notjson': .*")
214+
215+ @inlineCallbacks
216+ def test_parse_scheduler_hints_bad_array(self):
217+ cs = yield self.create_constraint_set()
218+ err = self.assertRaises(errors.ConstraintError,
219+ cs.parse, ["os-scheduler-hints=[]"])
220+ self.assertRegexpMatches(str(err),
221+ "Bad 'os-scheduler-hints' constraint '\\[\\]': .*")

Subscribers

People subscribed via source and target branches

to status/vote changes: