2
"""This module tests the deployment with constraints."""
4
from __future__ import print_function
13
from deploy_stack import (
16
from jujucharm import (
21
add_basic_testing_arguments,
31
log = logging.getLogger("assess_constraints")
44
# This assumes instances are unique accross providers.
45
def get_instance_spec(instance_type):
46
"""Get the specifications of a given instance type."""
48
't2.micro': {'mem': '1G', 'cpu-power': '10', 'cores': '1'},
53
"""Convert an argument size into a number of megabytes."""
54
if not re.match(re.compile('^[0123456789]+[MGTP]?$'), size):
55
raise JujuAssertionError('Not a size format:', size)
56
if size[-1] in 'MGTP':
59
return val * (1024 ** 'MGTP'.find(unit))
65
"""Class that represents a set of contraints."""
68
def _list_to_str(constraints_list):
69
parts = ['{}={}'.format(name, value) for (name, value) in
70
constraints_list if value is not None]
71
return ' '.join(parts)
73
def __init__(self, mem=None, cores=None, virt_type=None,
74
instance_type=None, root_disk=None, cpu_power=None,
76
"""Create a new constraints instance from individual constraints."""
79
self.virt_type = virt_type
80
self.instance_type = instance_type
81
self.root_disk = root_disk
82
self.cpu_power = cpu_power
86
"""Convert the instance constraint values into an argument string."""
87
return Constraints._list_to_str(
88
[('mem', self.mem), ('cores', self.cores),
89
('virt-type', self.virt_type),
90
('instance-type', self.instance_type),
91
('root-disk', self.root_disk), ('cpu-power', self.cpu_power),
95
def __eq__(self, other):
96
return (self.mem == other.mem and self.cores == other.cores and
97
self.virt_type == other.virt_type and
98
self.instance_type == other.instance_type and
99
self.root_disk == other.root_disk and
100
self.cpu_power == other.cpu_power and
101
self.arch == other.arch
105
def _meets_string(constraint, actual):
106
if constraint is None:
108
return constraint == actual
111
def _meets_min_int(constraint, actual):
112
if constraint is None:
114
return int(constraint) <= int(actual)
117
def _meets_min_mem(constraint, actual):
118
if constraint is None:
120
return mem_to_int(constraint) <= mem_to_int(actual)
122
def meets_root_disk(self, actual_root_disk):
123
"""Check to see if a given value meets the root_disk constraint."""
124
return self._meets_min_mem(self.root_disk, actual_root_disk)
126
def meets_cores(self, actual_cores):
127
"""Check to see if a given value meets the cores constraint."""
128
return self._meets_min_int(self.cores, actual_cores)
130
def meets_cpu_power(self, actual_cpu_power):
131
"""Check to see if a given value meets the cpu_power constraint."""
132
return self._meets_min_int(self.cpu_power, actual_cpu_power)
134
def meets_arch(self, actual_arch):
135
"""Check to see if a given value meets the arch constraint."""
136
return self._meets_string(self.arch, actual_arch)
138
def meets_instance_type(self, actual_data):
139
"""Check to see if a given value meets the instance_type constraint.
141
Currently there is no direct way to check for it, so we 'fingerprint'
142
each instance_type in a dictionary."""
143
instance_data = get_instance_spec(self.instance_type)
144
for (key, value) in instance_data.iteritems():
145
# Temperary fix until cpu-cores -> cores switch is finished.
146
if key is 'cores' and 'cpu-cores' in actual_data:
148
if key not in actual_data:
149
raise JujuAssertionError('Missing data:', key)
150
elif key in ['mem', 'root-disk']:
151
if mem_to_int(value) != mem_to_int(actual_data[key]):
153
elif value != actual_data[key]:
159
def deploy_constraint(client, constraints, charm, series, charm_repo):
160
"""Test deploying charm with constraints."""
161
client.deploy(charm, series=series, repository=charm_repo,
162
constraints=str(constraints))
163
client.wait_for_workloads()
166
def deploy_charm_constraint(client, constraints, charm_name, charm_series,
168
"""Create a charm with constraints and test deploying it."""
169
constraints_charm = Charm(charm_name,
170
'Test charm for constraints',
171
series=[charm_series])
172
charm_root = constraints_charm.to_repo_dir(charm_dir)
174
charm = local_charm_path(charm=charm_name,
175
juju_ver=client.version,
177
repository=os.path.dirname(charm_root),
179
deploy_constraint(client, constraints, charm,
180
charm_series, charm_dir)
183
def juju_show_machine_hardware(client, machine):
184
"""Uses juju show-machine and returns information about the hardware."""
185
raw = client.get_juju_output('show-machine', machine, '--format', 'yaml')
186
raw_yaml = yaml.load(raw)
188
hardware = raw_yaml['machines'][machine]['hardware']
189
except KeyError as error:
190
raise KeyError(error.args, raw_yaml)
192
for kvp in hardware.split(' '):
193
(key, value) = kvp.split('=')
198
def application_machines(client, application):
199
"""Get all the machines used to host the given application."""
200
raw = client.get_juju_output('status', '--format', 'yaml')
201
raw_yaml = yaml.load(raw)
203
app_data = raw_yaml['applications'][application]
205
for (unit, unit_data) in app_data['units'].items():
206
machines.append(unit_data['machine'])
208
except KeyError as error:
209
raise KeyError(error.args, raw_yaml)
212
def prepare_constraint_test(client, constraints, charm_name,
213
charm_series='xenial'):
214
"""Deploy a charm with constraints and data to see if it meets them."""
215
with temp_dir() as charm_dir:
216
deploy_charm_constraint(client, constraints, charm_name,
217
charm_series, charm_dir)
218
client.wait_for_started()
219
machines = application_machines(client, charm_name)
220
return juju_show_machine_hardware(client, machines[0])
223
def assess_virt_type(client, virt_type):
224
"""Assess the virt-type option for constraints"""
225
if virt_type not in VIRT_TYPES:
226
raise JujuAssertionError(virt_type)
227
constraints = Constraints(virt_type=virt_type)
228
charm_name = 'virt-type-{}'.format(virt_type)
229
charm_series = 'xenial'
230
with temp_dir() as charm_dir:
231
deploy_charm_constraint(client, constraints, charm_name,
232
charm_series, charm_dir)
235
def assess_virt_type_constraints(client, test_kvm=False):
236
"""Assess deployment with virt-type constraints."""
238
VIRT_TYPES.append("kvm")
239
for virt_type in VIRT_TYPES:
240
assess_virt_type(client, virt_type)
242
assess_virt_type(client, 'aws')
243
except JujuAssertionError:
244
log.info("Correctly rejected virt-type aws")
246
raise JujuAssertionError("FAIL: Client deployed with virt-type aws")
248
VIRT_TYPES.remove("kvm")
251
def get_failure_exception(client, constraints):
252
"""Create a JujuAssertionError with a detailed error message."""
253
message = 'Test Failed: on {} with constraints "{}"'.format(
254
client.env.config.get('type'), str(constraints))
255
return JujuAssertionError(message)
258
def assess_instance_type(client, provider, instance_type):
259
"""Assess the instance-type option for constraints"""
260
if instance_type not in INSTANCE_TYPES[provider]:
261
raise JujuAssertionError(instance_type)
262
constraints = Constraints(instance_type=instance_type)
263
charm_name = 'instance-type-{}'.format(instance_type.replace('.', '-'))
264
data = prepare_constraint_test(client, constraints, charm_name)
265
if not constraints.meets_instance_type(data):
266
raise get_failure_exception(client, constraints)
269
def assess_instance_type_constraints(client, provider=None):
270
"""Assess deployment with instance-type constraints."""
272
provider = client.env.config.get('type')
273
if provider not in INSTANCE_TYPES:
275
for instance_type in INSTANCE_TYPES[provider]:
276
assess_instance_type(client, provider, instance_type)
279
def assess_root_disk_constraints(client, values):
280
"""Assess deployment with root-disk constraints."""
281
for root_disk in values:
282
constraints = Constraints(root_disk=root_disk)
283
charm_name = 'root-disk-{}'.format(root_disk.lower())
284
data = prepare_constraint_test(client, constraints, charm_name)
285
if not constraints.meets_root_disk(data['root-disk']):
286
raise get_failure_exception(client, constraints)
289
def assess_cores_constraints(client, values):
290
"""Assess deployment with cores constraints."""
292
constraints = Constraints(cores=cores)
293
charm_name = 'cores-{}c'.format(cores)
294
data = prepare_constraint_test(client, constraints, charm_name)
295
if not constraints.meets_cores(data['cores']):
296
raise get_failure_exception(client, constraints)
299
def assess_cpu_power_constraints(client, values):
300
"""Assess deployment with cpu_power constraints."""
301
for cpu_power in values:
302
constraints = Constraints(cpu_power=cpu_power)
303
charm_name = 'cpu-power-{}cp'.format(cpu_power)
304
data = prepare_constraint_test(client, constraints, charm_name)
305
if not constraints.meets_cpu_power(data['cpu-power']):
306
raise get_failure_exception(client, constraints)
309
def assess_constraints(client, test_kvm=False):
310
"""Assess deployment with constraints."""
311
provider = client.env.config.get('type')
312
if 'lxd' == provider:
313
assess_virt_type_constraints(client, test_kvm)
314
elif 'ec2' == provider:
315
assess_instance_type_constraints(client, provider)
316
assess_root_disk_constraints(client, ['16G'])
317
assess_cores_constraints(client, ['2'])
318
assess_cpu_power_constraints(client, ['30'])
321
def parse_args(argv):
322
"""Parse all arguments."""
323
parser = argparse.ArgumentParser(description="Test constraints")
324
add_basic_testing_arguments(parser)
325
return parser.parse_args(argv)
329
args = parse_args(argv)
330
configure_logging(args.verbose)
331
bs_manager = BootstrapManager.from_args(args)
332
test_kvm = '--with-virttype-kvm' in args
333
with bs_manager.booted_context(args.upload_tools):
334
assess_constraints(bs_manager.client, test_kvm)
338
if __name__ == '__main__':