~hazmat/pyjuju/proposed-support

« back to all changes in this revision

Viewing changes to juju/machine/tests/test_constraints.py

  • Committer: kapil.thangavelu at canonical
  • Date: 2012-05-22 22:08:15 UTC
  • mfrom: (484.1.53 trunk)
  • Revision ID: kapil.thangavelu@canonical.com-20120522220815-acyt8m89i9ybe0w1
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import operator
 
2
 
1
3
from juju.errors import ConstraintError
2
4
from juju.lib.testing import TestCase
3
 
from juju.machine.constraints import Constraints
 
5
from juju.machine.constraints import Constraints, ConstraintSet
4
6
 
 
7
# These objects exist for the convenience of other test files
 
8
dummy_cs = ConstraintSet("dummy")
 
9
dummy_cs.register_generics([])
 
10
dummy_constraints = dummy_cs.parse([])
 
11
series_constraints = dummy_constraints.with_series("series")
5
12
 
6
13
generic_defaults = {
7
 
    "arch": None, "cpu": 1, "mem": 512,
 
14
    "arch": "amd64", "cpu": 1, "mem": 512,
8
15
    "ubuntu-series": None, "provider-type": None}
9
16
dummy_defaults = dict(generic_defaults, **{
10
17
    "provider-type": "dummy"})
11
18
ec2_defaults = dict(generic_defaults, **{
12
19
    "provider-type": "ec2",
13
20
    "ec2-zone": None,
14
 
    "ec2-instance-type": None})
15
 
orchestra_defaults = dict(generic_defaults, **{
 
21
    "instance-type": None})
 
22
orchestra_defaults = {
16
23
    "provider-type": "orchestra",
17
 
    "orchestra-name": None,
18
 
    "orchestra-classes": None})
 
24
    "ubuntu-series": None,
 
25
    "orchestra-classes": None}
19
26
 
20
27
all_providers = ["dummy", "ec2", "orchestra"]
21
28
 
 
29
 
 
30
def _raiser(exc_type):
 
31
    def raise_(s):
 
32
        raise exc_type(s)
 
33
    return raise_
 
34
 
 
35
 
22
36
class ConstraintsTestCase(TestCase):
23
37
 
24
38
    def assert_error(self, message, *raises_args):
25
39
        e = self.assertRaises(ConstraintError, *raises_args)
26
40
        self.assertEquals(str(e), message)
27
41
 
28
 
    def assert_roundtrip_equal(self, constraints, expected):
 
42
    def assert_roundtrip_equal(self, cs, constraints, expected):
 
43
        self.assertEquals(dict(constraints), expected)
29
44
        self.assertEquals(constraints, expected)
30
 
        self.assertEquals(Constraints(constraints.data), expected)
 
45
        self.assertEquals(dict(cs.load(constraints.data)), expected)
 
46
        self.assertEquals(cs.load(constraints.data), expected)
31
47
 
32
48
 
33
49
class ConstraintsTest(ConstraintsTestCase):
34
50
 
35
 
    def test_defaults(self):
36
 
        constraints = Constraints.from_strs("orchestra", [])
37
 
        self.assert_roundtrip_equal(constraints, orchestra_defaults)
38
 
        constraints = Constraints.from_strs("ec2", [])
39
 
        self.assert_roundtrip_equal(constraints, ec2_defaults)
40
 
        constraints = Constraints.from_strs("dummy", [])
41
 
        self.assert_roundtrip_equal(constraints, dummy_defaults)
 
51
    def test_equality(self):
 
52
        self.assert_roundtrip_equal(
 
53
            dummy_cs, dummy_constraints, dummy_defaults)
42
54
 
43
55
    def test_complete(self):
44
 
        incomplete_constraints = Constraints.from_strs("womble", [])
45
 
        self.assertFalse(incomplete_constraints.complete)
 
56
        incomplete_constraints = dummy_cs.parse([])
46
57
        complete_constraints = incomplete_constraints.with_series("wandering")
47
58
        self.assertTrue(complete_constraints.complete)
48
59
 
49
 
    def assert_invalid(self, message, providers, *constraint_strs):
50
 
        for provider in providers:
51
 
            self.assert_error(
52
 
                message, Constraints.from_strs, provider, constraint_strs)
53
 
 
54
 
        if len(providers) != len(all_providers):
55
 
            # Check it *is* valid for other providers
56
 
            for provider in all_providers:
57
 
                if provider in providers:
58
 
                    continue
59
 
                Constraints.from_strs(provider, constraint_strs)
 
60
    def assert_invalid(self, message, *constraint_strs):
 
61
        self.assert_error(
 
62
            message, dummy_cs.parse, constraint_strs)
60
63
 
61
64
    def test_invalid_input(self):
62
65
        """Reject nonsense constraints"""
63
66
        self.assert_invalid(
64
67
            "Could not interpret 'BLAH' constraint: need more than 1 value to "
65
68
            "unpack",
66
 
            all_providers, "BLAH")
 
69
            "BLAH")
67
70
        self.assert_invalid(
68
71
            "Unknown constraint: 'foo'",
69
 
            all_providers, "foo=", "bar=")
 
72
            "foo=", "bar=")
70
73
 
71
74
    def test_invalid_constraints(self):
72
75
        """Reject nonsensical constraint values"""
73
76
        self.assert_invalid(
74
 
            "Bad 'arch' constraint 'leg': unknown architecture", all_providers,
 
77
            "Bad 'arch' constraint 'leg': unknown architecture",
75
78
            "arch=leg")
76
79
        self.assert_invalid(
77
 
            "Bad 'cpu' constraint '-1': must be non-negative", all_providers,
 
80
            "Bad 'cpu' constraint '-1': must be non-negative",
78
81
            "cpu=-1")
79
82
        self.assert_invalid(
80
83
            "Bad 'cpu' constraint 'fish': could not convert string to float: "
81
84
            "fish",
82
 
            all_providers, "cpu=fish")
 
85
            "cpu=fish")
83
86
        self.assert_invalid(
84
 
            "Bad 'mem' constraint '-1': must be non-negative", all_providers,
 
87
            "Bad 'mem' constraint '-1': must be non-negative",
85
88
            "mem=-1")
86
89
        self.assert_invalid(
87
90
            "Bad 'mem' constraint '4P': invalid literal for float(): 4P",
88
 
            all_providers, "mem=4P")
89
 
        self.assert_invalid(
90
 
            "Bad 'ec2-zone' constraint 'Q': expected lowercase ascii char",
91
 
            ["ec2"], "ec2-zone=Q")
 
91
            "mem=4P")
92
92
 
93
93
    def test_hidden_constraints(self):
94
94
        """Reject attempts to explicitly specify computed constraints"""
95
95
        self.assert_invalid(
96
 
            "Cannot set computed constraint: 'ubuntu-series'", all_providers,
 
96
            "Cannot set computed constraint: 'ubuntu-series'",
97
97
            "ubuntu-series=cheesy")
98
98
        self.assert_invalid(
99
 
            "Cannot set computed constraint: 'provider-type'", all_providers,
 
99
            "Cannot set computed constraint: 'provider-type'",
100
100
            "provider-type=dummy")
101
101
 
102
 
    def test_overlap_ec2(self):
103
 
        """Overlapping ec2 constraints should be detected"""
104
 
        self.assert_invalid(
105
 
            "Ambiguous constraints: 'arch' overlaps with 'ec2-instance-type'",
106
 
            ["ec2"], "ec2-instance-type=m1.small", "arch=i386")
107
 
        self.assert_invalid(
108
 
            "Ambiguous constraints: 'cpu' overlaps with 'ec2-instance-type'",
109
 
            ["ec2"], "ec2-instance-type=m1.small", "cpu=1")
110
 
        self.assert_invalid(
111
 
            "Ambiguous constraints: 'ec2-instance-type' overlaps with 'mem'",
112
 
            ["ec2"], "ec2-instance-type=m1.small", "mem=2G")
113
 
 
114
 
    def test_overlap_orchestra(self):
115
 
        """Overlapping orchestra constraints should be detected"""
116
 
        self.assert_invalid(
117
 
            "Ambiguous constraints: 'arch' overlaps with 'orchestra-name'",
118
 
            ["orchestra"], "orchestra-name=herbert", "arch=i386")
119
 
        self.assert_invalid(
120
 
            "Ambiguous constraints: 'cpu' overlaps with 'orchestra-name'",
121
 
            ["orchestra"], "orchestra-name=herbert", "cpu=1")
122
 
        self.assert_invalid(
123
 
            "Ambiguous constraints: 'mem' overlaps with 'orchestra-name'",
124
 
            ["orchestra"], "orchestra-name=herbert", "mem=2G")
125
 
        self.assert_invalid(
126
 
            "Ambiguous constraints: 'orchestra-classes' overlaps with "
127
 
            "'orchestra-name'",
128
 
            ["orchestra"], "orchestra-name=herbert", "orchestra-classes=x,y")
129
 
 
130
102
 
131
103
class ConstraintsUpdateTest(ConstraintsTestCase):
132
104
 
133
 
    def assert_constraints(self, provider, strss, expected):
134
 
        constraints = Constraints.from_strs(provider, strss[0])
 
105
    def assert_constraints(self, strss, expected):
 
106
        constraints = dummy_cs.parse(strss[0])
135
107
        for strs in strss[1:]:
136
 
            constraints.update(Constraints.from_strs(provider, strs))
137
 
        self.assert_roundtrip_equal(constraints, expected)
138
 
 
139
 
    def assert_constraints_dummy(self, strss, expected):
 
108
            constraints.update(dummy_cs.parse(strs))
140
109
        expected = dict(dummy_defaults, **expected)
141
 
        self.assert_constraints("dummy", strss, expected)
 
110
        self.assert_roundtrip_equal(dummy_cs, constraints, expected)
142
111
 
143
 
    def test_constraints_dummy(self):
 
112
    def test_constraints(self):
144
113
        """Sane constraints dicts are generated for unknown environments"""
145
 
        self.assert_constraints_dummy([[]], {})
146
 
        self.assert_constraints_dummy([["arch=arm"]], {"arch": "arm"})
147
 
        self.assert_constraints_dummy([["cpu=0.1"]], {"cpu": 0.1})
148
 
        self.assert_constraints_dummy([["mem=128"]], {"mem": 128})
149
 
        self.assert_constraints_dummy(
 
114
        self.assert_constraints([[]], {})
 
115
        self.assert_constraints([["cpu=", "mem="]], {})
 
116
        self.assert_constraints([["arch=arm"]], {"arch": "arm"})
 
117
        self.assert_constraints([["cpu=0.1"]], {"cpu": 0.1})
 
118
        self.assert_constraints([["mem=128"]], {"mem": 128})
 
119
        self.assert_constraints([["cpu=0"]], {"cpu": 0})
 
120
        self.assert_constraints([["mem=0"]], {"mem": 0})
 
121
        self.assert_constraints(
150
122
            [["arch=amd64", "cpu=6", "mem=1.5G"]],
151
 
            {"arch": "amd64", "cpu": 6, "mem": 1536,})
 
123
            {"arch": "amd64", "cpu": 6, "mem": 1536})
152
124
 
153
125
    def test_overwriting_basic(self):
154
126
        """Later values shadow earlier values"""
155
 
        self.assert_constraints_dummy(
 
127
        self.assert_constraints(
156
128
            [["cpu=4", "mem=512"], ["arch=i386", "mem=1G"]],
157
129
            {"arch": "i386", "cpu": 4, "mem": 1024})
158
130
 
159
131
    def test_reset(self):
160
132
        """Empty string resets to juju default"""
161
 
        self.assert_constraints_dummy(
 
133
        self.assert_constraints(
162
134
            [["arch=arm", "cpu=4", "mem=1024"], ["arch=", "cpu=", "mem="]],
163
 
            {"arch": None, "cpu": 1, "mem": 512})
164
 
        self.assert_constraints_dummy(
 
135
            {"arch": "amd64", "cpu": 1, "mem": 512})
 
136
        self.assert_constraints(
165
137
            [["arch=", "cpu=", "mem="], ["arch=arm", "cpu=4", "mem=1024"]],
166
138
            {"arch": "arm", "cpu": 4, "mem": 1024})
167
139
 
168
 
    def assert_constraints_ec2(self, strss, expected):
169
 
        expected = dict(ec2_defaults, **expected)
170
 
        self.assert_constraints("ec2", strss, expected)
171
 
 
172
 
    def test_constraints_ec2(self):
173
 
        """Sane constraints dicts are generated for ec2"""
174
 
        self.assert_constraints_ec2([[]], {})
175
 
        self.assert_constraints_ec2([["arch=arm"]], {"arch": "arm"})
176
 
        self.assert_constraints_ec2([["cpu=128"]], {"cpu": 128})
177
 
        self.assert_constraints_ec2([["mem=2G"]], {"mem": 2048})
178
 
        self.assert_constraints_ec2([["ec2-zone=b"]], {"ec2-zone": "b"})
179
 
        self.assert_constraints_ec2(
180
 
            [["arch=amd64", "cpu=32", "mem=2G", "ec2-zone=b"]],
181
 
            {"arch": "amd64", "cpu": 32, "mem": 2048, "ec2-zone": "b"})
182
 
 
183
 
    def test_overwriting_ec2_instance_type(self):
184
 
        """ec2-instance-type interacts correctly with arch, cpu, mem"""
185
 
        self.assert_constraints_ec2(
186
 
            [["ec2-instance-type=t1.micro"]],
187
 
            {"ec2-instance-type": "t1.micro", "cpu": None, "mem": None})
188
 
        self.assert_constraints_ec2(
189
 
            [["arch=arm", "cpu=8"], ["ec2-instance-type=t1.micro"]],
190
 
            {"ec2-instance-type": "t1.micro", "cpu": None, "mem": None})
191
 
        self.assert_constraints_ec2(
192
 
            [["ec2-instance-type=t1.micro"], ["arch=arm", "cpu=8"]],
193
 
            {"ec2-instance-type": None, "arch": "arm", "cpu": 8, "mem": None})
194
 
 
195
 
    def assert_constraints_orchestra(self, strss, expected):
196
 
        expected = dict(orchestra_defaults, **expected)
197
 
        self.assert_constraints("orchestra", strss, expected)
198
 
 
199
 
    def test_constraints_orchestra(self):
200
 
        """Sane constraints dicts are generated for orchestra"""
201
 
        self.assert_constraints_orchestra([[]], {})
202
 
        self.assert_constraints_orchestra([["arch=arm"]], {"arch": "arm"})
203
 
        self.assert_constraints_orchestra([["cpu=128"]], {"cpu": 128})
204
 
        self.assert_constraints_orchestra([["mem=0.25T"]], {"mem": 262144})
205
 
        self.assert_constraints_orchestra(
206
 
            [["orchestra-classes=x,y"]], {"orchestra-classes": ["x", "y"]})
207
 
        self.assert_constraints_orchestra(
208
 
            [["arch=i386", "cpu=2", "mem=768M", "orchestra-classes=a,b"]],
209
 
            {"arch": "i386", "cpu": 2, "mem": 768,
210
 
                "orchestra-classes": ["a", "b"]})
211
 
 
212
 
    def test_overwriting_orchestra_name(self):
213
 
        """orchestra-name interacts correctly with arch, cpu, mem"""
214
 
        self.assert_constraints_orchestra(
215
 
            [["orchestra-name=baggins"]],
216
 
            {"orchestra-name": "baggins", "cpu": None, "mem": None})
217
 
        self.assert_constraints_orchestra(
218
 
            [["orchestra-name=baggins"], ["arch=arm"]],
219
 
            {"orchestra-name": None, "arch": "arm", "cpu": None, "mem": None})
220
 
        self.assert_constraints_orchestra(
221
 
            [["arch=arm", "cpu=2"], ["orchestra-name=baggins"]],
222
 
            {"orchestra-name": "baggins", "arch": None, "cpu": None,
223
 
                "mem": None})
224
 
 
225
 
    def test_overwriting_orchestra_classes(self):
226
 
        """orchestra-classes interacts correctly with orchestra-name"""
227
 
        self.assert_constraints_orchestra(
228
 
            [["orchestra-name=baggins"], ["orchestra-classes=c,d", "cpu=2"]],
229
 
            {"orchestra-classes": ["c", "d"], "cpu": 2, "mem": None})
230
 
        self.assert_constraints_orchestra(
231
 
            [["orchestra-classes=zz,top", "arch=amd64", "cpu=2"],
232
 
                ["orchestra-name=baggins"]],
233
 
            {"orchestra-name": "baggins", "orchestra-classes": None,
234
 
                "cpu": None, "mem": None})
235
 
 
236
140
 
237
141
class ConstraintsFulfilmentTest(ConstraintsTestCase):
238
142
 
239
 
    def completed_constraints(self, provider="provider", series="series"):
240
 
        return Constraints.from_strs(provider, []).with_series(series)
241
 
 
242
 
    def assert_incomparable(self, c1, c2):
243
 
        self.assert_error(
244
 
            "Cannot compare incomplete constraints", c1.can_satisfy, c2)
245
 
        self.assert_error(
246
 
            "Cannot compare incomplete constraints", c2.can_satisfy, c1)
247
 
 
248
143
    def assert_match(self, c1, c2, expected):
249
144
        self.assertEquals(c1.can_satisfy(c2), expected)
250
145
        self.assertEquals(c2.can_satisfy(c1), expected)
251
146
 
252
147
    def test_fulfil_completeness(self):
253
 
        """can_satisfy needs to be called on and with complete Constraints~s"""
254
 
        c1 = Constraints({"provider-type": "a"})
255
 
        c2 = Constraints({"provider-type": "a"})
256
 
        self.assert_incomparable(c1, c2)
257
 
        c3 = self.completed_constraints("a")
258
 
        self.assert_incomparable(c1, c3)
259
 
        c4 = self.completed_constraints("a")
260
 
        self.assert_match(c3, c4, True)
 
148
        """
 
149
        can_satisfy needs to be called on and with complete Constraints~s to
 
150
        have any chance of working.
 
151
        """
 
152
        good = Constraints(
 
153
            dummy_cs, {"provider-type": "dummy", "ubuntu-series": "x"})
 
154
        self.assert_match(good, good, True)
 
155
        bad = [Constraints(dummy_cs, {}),
 
156
               Constraints(dummy_cs, {"provider-type": "dummy"}),
 
157
               Constraints(dummy_cs, {"ubuntu-series": "x"})]
 
158
        for i, bad1 in enumerate(bad):
 
159
            self.assert_match(bad1, good, False)
 
160
            for bad2 in bad[i:]:
 
161
                self.assert_match(bad1, bad2, False)
261
162
 
262
163
    def test_fulfil_matches(self):
 
164
        other_cs = ConstraintSet("other")
 
165
        other_cs.register_generics([])
 
166
        other_constraints = other_cs.parse([])
263
167
        instances = (
264
 
            Constraints({"provider-type": "a", "ubuntu-series": "x"}),
265
 
            Constraints({"provider-type": "a", "ubuntu-series": "y"}),
266
 
            Constraints({"provider-type": "b", "ubuntu-series": "x"}),
267
 
            Constraints({"provider-type": "b", "ubuntu-series": "y"}))
 
168
            dummy_constraints.with_series("x"),
 
169
            dummy_constraints.with_series("y"),
 
170
            other_constraints.with_series("x"),
 
171
            other_constraints.with_series("y"))
268
172
 
269
173
        for i, c1 in enumerate(instances):
270
174
            self.assert_match(c1, c1, True)
272
176
                self.assert_match(c1, c2, False)
273
177
 
274
178
    def assert_can_satisfy(
275
 
            self, machine_strs, unit_strs, expected, provider="provider"):
276
 
        machine = Constraints.from_strs(provider, machine_strs)
 
179
            self, machine_strs, unit_strs, expected):
 
180
        machine = dummy_cs.parse(machine_strs)
277
181
        machine = machine.with_series("shiny")
278
 
        unit = Constraints.from_strs(provider, unit_strs)
 
182
        unit = dummy_cs.parse(unit_strs)
279
183
        unit = unit.with_series("shiny")
280
184
        self.assertEquals(machine.can_satisfy(unit), expected)
281
185
 
282
 
        if provider != "provider":
283
 
            # Check that a different provider doesn't detect any problem,
284
 
            # because it's ignoring all off-provider constraints.
285
 
            machine = Constraints.from_strs("provider", machine_strs)
286
 
            machine = machine.with_series("shiny")
287
 
            unit = Constraints.from_strs("provider", unit_strs)
288
 
            unit = unit.with_series("shiny")
289
 
            self.assertEquals(machine.can_satisfy(unit), True)
290
 
 
291
 
 
292
186
    def test_can_satisfy(self):
293
187
        self.assert_can_satisfy([], [], True)
294
188
 
295
 
        self.assert_can_satisfy(["arch=arm"], [], True)
 
189
        self.assert_can_satisfy(["arch=arm"], [], False)
 
190
        self.assert_can_satisfy(["arch=amd64"], [], True)
296
191
        self.assert_can_satisfy([], ["arch=arm"], False)
297
192
        self.assert_can_satisfy(["arch=i386"], ["arch=arm"], False)
298
193
        self.assert_can_satisfy(["arch=arm"], ["arch=amd64"], False)
299
194
        self.assert_can_satisfy(["arch=amd64"], ["arch=amd64"], True)
 
195
        self.assert_can_satisfy(["arch=i386"], ["arch=any"], True)
 
196
        self.assert_can_satisfy(["arch=arm"], ["arch=any"], True)
 
197
        self.assert_can_satisfy(["arch=amd64"], ["arch=any"], True)
 
198
        self.assert_can_satisfy(["arch=any"], ["arch=any"], True)
 
199
        self.assert_can_satisfy(["arch=any"], ["arch=i386"], False)
 
200
        self.assert_can_satisfy(["arch=any"], ["arch=amd64"], False)
 
201
        self.assert_can_satisfy(["arch=any"], ["arch=arm"], False)
300
202
 
301
203
        self.assert_can_satisfy(["cpu=64"], [], True)
302
204
        self.assert_can_satisfy([], ["cpu=64"], False)
303
205
        self.assert_can_satisfy(["cpu=64"], ["cpu=32"], True)
304
206
        self.assert_can_satisfy(["cpu=32"], ["cpu=64"], False)
305
207
        self.assert_can_satisfy(["cpu=64"], ["cpu=64"], True)
 
208
        self.assert_can_satisfy(["cpu=0.01"], ["cpu=any"], True)
 
209
        self.assert_can_satisfy(["cpu=9999"], ["cpu=any"], True)
 
210
        self.assert_can_satisfy(["cpu=any"], ["cpu=any"], True)
 
211
        self.assert_can_satisfy(["cpu=any"], ["cpu=0.01"], False)
 
212
        self.assert_can_satisfy(["cpu=any"], ["cpu=9999"], False)
306
213
 
307
214
        self.assert_can_satisfy(["mem=8G"], [], True)
308
215
        self.assert_can_satisfy([], ["mem=8G"], False)
309
216
        self.assert_can_satisfy(["mem=8G"], ["mem=4G"], True)
310
217
        self.assert_can_satisfy(["mem=4G"], ["mem=8G"], False)
311
218
        self.assert_can_satisfy(["mem=8G"], ["mem=8G"], True)
312
 
 
313
 
        self.assert_can_satisfy(
314
 
            # orchestra-name clears default cpu/mem values.
315
 
            # This may be a problem.
316
 
            ["orchestra-name=henry"], [], False, "orchestra")
317
 
        self.assert_can_satisfy(
318
 
            [], ["orchestra-name=henry"], False, "orchestra")
319
 
        self.assert_can_satisfy(
320
 
            ["orchestra-name=henry"], ["orchestra-name=jane"], False,
321
 
            "orchestra")
322
 
        self.assert_can_satisfy(
323
 
            ["orchestra-name=jane"], ["orchestra-name=henry"], False,
324
 
            "orchestra")
325
 
        self.assert_can_satisfy(
326
 
            ["orchestra-name=henry"], ["orchestra-name=henry"], True,
327
 
            "orchestra")
328
 
 
329
 
        self.assert_can_satisfy(
330
 
            ["orchestra-classes=a,b"], [], True, "orchestra")
331
 
        self.assert_can_satisfy(
332
 
            [], ["orchestra-classes=a,b"], False, "orchestra")
333
 
        self.assert_can_satisfy(
334
 
            ["orchestra-classes=a,b"], ["orchestra-classes=a"], True,
335
 
            "orchestra")
336
 
        self.assert_can_satisfy(
337
 
            ["orchestra-classes=a"], ["orchestra-classes=a,b"], False,
338
 
            "orchestra")
339
 
        self.assert_can_satisfy(
340
 
            ["orchestra-classes=a,b"], ["orchestra-classes=a,b"], True,
341
 
            "orchestra")
342
 
        self.assert_can_satisfy(
343
 
            ["orchestra-classes=a,b"], ["orchestra-classes=a,c"], False,
344
 
            "orchestra")
345
 
 
346
 
        self.assert_can_satisfy(["ec2-zone=a"], [], True, "ec2")
347
 
        self.assert_can_satisfy([], ["ec2-zone=a"], False, "ec2")
348
 
        self.assert_can_satisfy(["ec2-zone=a"], ["ec2-zone=b"], False, "ec2")
349
 
        self.assert_can_satisfy(["ec2-zone=b"], ["ec2-zone=a"], False, "ec2")
350
 
        self.assert_can_satisfy(["ec2-zone=a"], ["ec2-zone=a"], True, "ec2")
351
 
 
352
 
        self.assert_can_satisfy(
353
 
            # ec2-instance-type clears default cpu/mem values.
354
 
            # This may be a problem.
355
 
            ["ec2-instance-type=m1.small"], [], False, "ec2")
356
 
        self.assert_can_satisfy([], ["ec2-instance-type=m1.small"], False, "ec2")
357
 
        self.assert_can_satisfy(
358
 
            ["ec2-instance-type=m1.large"], ["ec2-instance-type=m1.small"],
359
 
            False, "ec2")
360
 
        self.assert_can_satisfy(
361
 
            ["ec2-instance-type=m1.small"], ["ec2-instance-type=m1.large"],
362
 
            False, "ec2")
363
 
        self.assert_can_satisfy(
364
 
            ["ec2-instance-type=m1.small"], ["ec2-instance-type=m1.small"],
365
 
            True, "ec2")
 
219
        self.assert_can_satisfy(["mem=2M"], ["mem=any"], True)
 
220
        self.assert_can_satisfy(["mem=256T"], ["mem=any"], True)
 
221
        self.assert_can_satisfy(["mem=any"], ["mem=any"], True)
 
222
        self.assert_can_satisfy(["mem=any"], ["mem=2M"], False)
 
223
        self.assert_can_satisfy(["mem=any"], ["mem=256T"], False)
 
224
 
 
225
 
 
226
class ConstraintSetTest(TestCase):
 
227
 
 
228
    def test_unregistered_name(self):
 
229
        cs = ConstraintSet("provider")
 
230
        cs.register("bar")
 
231
        e = self.assertRaises(ConstraintError, cs.parse, ["bar=2", "baz=3"])
 
232
        self.assertEquals(str(e), "Unknown constraint: 'baz'")
 
233
 
 
234
    def test_register_invisible(self):
 
235
        cs = ConstraintSet("provider")
 
236
        cs.register("foo", visible=False)
 
237
        e = self.assertRaises(ConstraintError, cs.parse, ["foo=bar"])
 
238
        self.assertEquals(str(e), "Cannot set computed constraint: 'foo'")
 
239
 
 
240
    def test_register_comparer(self):
 
241
        cs = ConstraintSet("provider")
 
242
        cs.register("foo", comparer=operator.ne)
 
243
        c1 = cs.parse(["foo=bar"]).with_series("series")
 
244
        c2 = cs.parse(["foo=bar"]).with_series("series")
 
245
        self.assertFalse(c1.can_satisfy(c2))
 
246
        self.assertFalse(c2.can_satisfy(c1))
 
247
        c3 = cs.parse(["foo=baz"]).with_series("series")
 
248
        self.assertTrue(c1.can_satisfy(c3))
 
249
        self.assertTrue(c3.can_satisfy(c1))
 
250
 
 
251
    def test_register_default_and_converter(self):
 
252
        cs = ConstraintSet("provider")
 
253
        cs.register("foo", default="star", converter=lambda s: "death-" + s)
 
254
        c1 = cs.parse([])
 
255
        self.assertEquals(c1["foo"], "death-star")
 
256
        c1 = cs.parse(["foo=clock"])
 
257
        self.assertEquals(c1["foo"], "death-clock")
 
258
 
 
259
    def test_convert_wraps_ValueError(self):
 
260
        cs = ConstraintSet("provider")
 
261
        cs.register("foo", converter=_raiser(ValueError))
 
262
        cs.register("bar", converter=_raiser(KeyError))
 
263
        self.assertRaises(ConstraintError, cs.parse, ["foo=1"])
 
264
        self.assertRaises(KeyError, cs.parse, ["bar=1"])
 
265
 
 
266
    def test_register_conflicts(self):
 
267
        cs = ConstraintSet("provider")
 
268
        cs.register("foo")
 
269
        cs.register("bar")
 
270
        cs.register("baz")
 
271
        cs.register("qux")
 
272
        cs.parse(["foo=1", "bar=2", "baz=3", "qux=4"])
 
273
 
 
274
        def assert_ambiguous(strs):
 
275
            e = self.assertRaises(ConstraintError, cs.parse, strs)
 
276
            self.assertTrue(str(e).startswith("Ambiguous constraints"))
 
277
 
 
278
        cs.register_conflicts(["foo"], ["bar", "baz", "qux"])
 
279
        assert_ambiguous(["foo=1", "bar=2"])
 
280
        assert_ambiguous(["foo=1", "baz=3"])
 
281
        assert_ambiguous(["foo=1", "qux=4"])
 
282
        cs.parse(["foo=1"])
 
283
        cs.parse(["bar=2", "baz=3", "qux=4"])
 
284
 
 
285
        cs.register_conflicts(["bar", "baz"], ["qux"])
 
286
        assert_ambiguous(["bar=2", "qux=4"])
 
287
        assert_ambiguous(["baz=3", "qux=4"])
 
288
        cs.parse(["foo=1"])
 
289
        cs.parse(["bar=2", "baz=3"])
 
290
        cs.parse(["qux=4"])
 
291
 
 
292
    def test_register_generics_no_instance_types(self):
 
293
        cs = ConstraintSet("provider")
 
294
        cs.register_generics([])
 
295
        c1 = cs.parse([])
 
296
        self.assertEquals(c1["arch"], "amd64")
 
297
        self.assertEquals(c1["cpu"], 1.0)
 
298
        self.assertEquals(c1["mem"], 512.0)
 
299
        self.assertFalse("instance-type" in c1)
 
300
 
 
301
        c2 = cs.parse(["arch=any", "cpu=0", "mem=8G"])
 
302
        self.assertEquals(c2["arch"], None)
 
303
        self.assertEquals(c2["cpu"], 0.0)
 
304
        self.assertEquals(c2["mem"], 8192.0)
 
305
        self.assertFalse("instance-type" in c2)
 
306
 
 
307
    def test_register_generics_with_instance_types(self):
 
308
        cs = ConstraintSet("provider")
 
309
        cs.register_generics(["a1.big", "c7.peculiar"])
 
310
        c1 = cs.parse([])
 
311
        self.assertEquals(c1["arch"], "amd64")
 
312
        self.assertEquals(c1["cpu"], 1.0)
 
313
        self.assertEquals(c1["mem"], 512.0)
 
314
        self.assertEquals(c1["instance-type"], None)
 
315
 
 
316
        c2 = cs.parse(["arch=any", "cpu=0", "mem=8G"])
 
317
        self.assertEquals(c2["arch"], None)
 
318
        self.assertEquals(c2["cpu"], 0.0)
 
319
        self.assertEquals(c2["mem"], 8192.0)
 
320
        self.assertEquals(c2["instance-type"], None)
 
321
 
 
322
        c3 = cs.parse(["instance-type=c7.peculiar", "arch=i386"])
 
323
        self.assertEquals(c3["arch"], "i386")
 
324
        self.assertEquals(c3["cpu"], None)
 
325
        self.assertEquals(c3["mem"], None)
 
326
        self.assertEquals(c3["instance-type"], "c7.peculiar")
 
327
 
 
328
        def assert_ambiguous(strs):
 
329
            e = self.assertRaises(ConstraintError, cs.parse, strs)
 
330
            self.assertTrue(str(e).startswith("Ambiguous constraints"))
 
331
 
 
332
        assert_ambiguous(["cpu=1", "instance-type=c7.peculiar"])
 
333
        assert_ambiguous(["mem=1024", "instance-type=c7.peculiar"])
 
334
 
 
335
        c4 = cs.parse([])
 
336
        c4.update(c2)
 
337
        self.assertEquals(c4["arch"], None)
 
338
        self.assertEquals(c4["cpu"], 0.0)
 
339
        self.assertEquals(c4["mem"], 8192.0)
 
340
        self.assertEquals(c4["instance-type"], None)
 
341
 
 
342
        c5 = cs.parse(["instance-type=a1.big"])
 
343
        c5.update(cs.parse(["arch=i386"]))
 
344
        self.assertEquals(c5["arch"], "i386")
 
345
        self.assertEquals(c5["cpu"], None)
 
346
        self.assertEquals(c5["mem"], None)
 
347
        self.assertEquals(c5["instance-type"], "a1.big")
 
348
 
 
349
        c6 = cs.parse(["instance-type=a1.big"])
 
350
        c6.update(cs.parse(["cpu=20"]))
 
351
        self.assertEquals(c6["arch"], "amd64")
 
352
        self.assertEquals(c6["cpu"], 20.0)
 
353
        self.assertEquals(c6["mem"], None)
 
354
        self.assertEquals(c6["instance-type"], None)
 
355
 
 
356
        c7 = cs.parse(["instance-type="])
 
357
        self.assertEquals(c7["arch"], "amd64")
 
358
        self.assertEquals(c7["cpu"], 1.0)
 
359
        self.assertEquals(c7["mem"], 512.0)
 
360
        self.assertEquals(c7["instance-type"], None)
 
361
 
 
362
        c8 = cs.parse(["instance-type=any"])
 
363
        self.assertEquals(c8["arch"], "amd64")
 
364
        self.assertEquals(c8["cpu"], 1.0)
 
365
        self.assertEquals(c8["mem"], 512.0)
 
366
        self.assertEquals(c8["instance-type"], None)
 
367
 
 
368
    def test_load_validates_known(self):
 
369
        cs = ConstraintSet("provider")
 
370
        cs.register("foo", converter=_raiser(ValueError))
 
371
        e = self.assertRaises(ConstraintError, cs.load, {"foo": "bar"})
 
372
        self.assertEquals(str(e), "Bad 'foo' constraint 'bar': bar")
 
373
 
 
374
    def test_load_preserves_unknown(self):
 
375
        cs = ConstraintSet("provider")
 
376
        constraints = cs.load({"foo": "bar"})
 
377
        self.assertNotIn("foo", constraints)
 
378
        self.assertEquals(constraints.data, {"foo": "bar"})