Skip to content

Commit b11c9f0

Browse files
author
Gabriel Monroy
committed
move cloud infrastructure fields from formation to layer
1 parent f78a533 commit b11c9f0

13 files changed

Lines changed: 132 additions & 116 deletions

File tree

api/models.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,8 @@ class Formation(UuidAuditedModel):
241241

242242
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
243243
id = models.SlugField(max_length=64)
244-
flavor = models.ForeignKey('Flavor')
245244
layers = fields.JSONField(default='{}', blank=True)
246245
containers = fields.JSONField(default='{}', blank=True)
247-
248-
environment = models.CharField(max_length=64, default='_default')
249-
250-
ssh_username = models.CharField(max_length=64, default='ubuntu')
251-
ssh_private_key = models.TextField()
252-
ssh_public_key = models.TextField()
253246

254247
class Meta:
255248
unique_together = (('owner', 'id'),)
@@ -381,12 +374,6 @@ def _balance_containers(self, **kwargs):
381374

382375
def __str__(self):
383376
return self.id
384-
385-
def prepare_provider(self, *args, **kwargs):
386-
tasks = import_tasks(self.flavor.provider.type)
387-
args = (self.id, self.flavor.provider.creds.copy(),
388-
self.flavor.params.copy())
389-
return tasks.prepare_formation.subtask(args)
390377

391378
def calculate(self):
392379
"Return a Chef data bag item for this formation"
@@ -435,19 +422,14 @@ def converge(self, databag):
435422
return databag
436423

437424
def destroy(self):
438-
tasks = import_tasks(self.flavor.provider.type)
439425
subtasks = []
440426
# call a celery task to update the formation data bag
441427
if settings.CHEF_ENABLED:
442428
subtasks.extend([controller.destroy_formation.s(self.id)]) # @UndefinedVariable
443429
# create subtasks to terminate all nodes in parallel
444-
subtasks.extend([ node.terminate() for node in self.node_set.all() ])
430+
subtasks.extend([ layer.destroy() for layer in self.layer_set.all() ])
445431
job = group(*subtasks)
446432
job.apply_async().join() # block for termination
447-
# purge other hosting provider infrastructure
448-
tasks.cleanup_formation.delay(self.id,
449-
self.flavor.provider.creds.copy(),
450-
self.flavor.params.copy()).wait()
451433

452434

453435
@python_2_unicode_compatible
@@ -465,13 +447,36 @@ class Layer(UuidAuditedModel):
465447
formation = models.ForeignKey('Formation')
466448
flavor = models.ForeignKey('Flavor')
467449
nodes = models.PositiveSmallIntegerField(default=0)
468-
469-
run_list = models.CharField(max_length=512)
450+
# chef settings
451+
run_list = models.CharField(max_length=512)
470452
initial_attributes = fields.JSONField(default='{}', blank=True)
471-
453+
environment = models.CharField(max_length=64, default='_default')
454+
# ssh settings
455+
ssh_username = models.CharField(max_length=64, default='ubuntu')
456+
ssh_private_key = models.TextField()
457+
ssh_public_key = models.TextField()
458+
472459
def __str__(self):
473460
return self.id
474461

462+
def build(self, *args, **kwargs):
463+
tasks = import_tasks(self.flavor.provider.type)
464+
args = (self.id, self.flavor.provider.creds.copy(),
465+
self.flavor.params.copy())
466+
return tasks.build_layer.subtask(args)
467+
468+
def destroy(self):
469+
tasks = import_tasks(self.flavor.provider.type)
470+
subtasks = []
471+
# create subtasks to terminate all nodes in parallel
472+
subtasks.extend([ node.terminate() for node in self.node_set.all() ])
473+
job = group(*subtasks)
474+
job.apply_async().join() # block for termination
475+
# purge other hosting provider infrastructure
476+
tasks.destroy_layer.delay(self.id,
477+
self.flavor.provider.creds.copy(),
478+
self.flavor.params.copy()).wait() # block
479+
475480

476481
@python_2_unicode_compatible
477482
class NodeManager(models.Manager):
@@ -518,16 +523,16 @@ def __str__(self):
518523
return self.id
519524

520525
def launch(self, *args, **kwargs):
521-
tasks = import_tasks(self.formation.flavor.provider.type)
526+
tasks = import_tasks(self.layer.flavor.provider.type)
522527
args = self._prepare_launch_args()
523528
return tasks.launch_node.subtask(args)
524529

525530
def _prepare_launch_args(self):
526-
creds = self.formation.flavor.provider.creds.copy()
527-
params = self.formation.flavor.params.copy()
531+
creds = self.layer.flavor.provider.creds.copy()
532+
params = self.layer.flavor.params.copy()
528533
params['formation'] = self.formation.id
529534
params['id'] = self.id
530-
init = self.formation.flavor.init.copy()
535+
init = self.layer.flavor.init.copy()
531536
if settings.CHEF_ENABLED:
532537
chef = init['chef'] = {}
533538
chef['ruby_version'] = settings.CHEF_RUBY_VERSION
@@ -543,36 +548,36 @@ def _prepare_launch_args(self):
543548
chef['initial_attributes'] = self.layer.initial_attributes
544549
# add the formation's ssh pubkey
545550
init.setdefault('ssh_authorized_keys', []).append(
546-
self.formation.ssh_public_key)
551+
self.layer.ssh_public_key)
547552
# add all of the owner's SSH keys
548553
init['ssh_authorized_keys'].extend([k.public for k in self.formation.owner.key_set.all() ])
549-
ssh_username = self.formation.ssh_username
550-
ssh_private_key = self.formation.ssh_private_key
554+
ssh_username = self.layer.ssh_username
555+
ssh_private_key = self.layer.ssh_private_key
551556
args = (self.uuid, creds, params, init, ssh_username, ssh_private_key)
552557
return args
553558

554559
def converge(self, *args, **kwargs):
555-
tasks = import_tasks(self.formation.flavor.provider.type)
560+
tasks = import_tasks(self.layer.flavor.provider.type)
556561
args = self._prepare_converge_args()
557562
# TODO: figure out how to store task return values in model
558563
return tasks.converge_node.subtask(args)
559564

560565
def _prepare_converge_args(self):
561-
ssh_username = self.formation.ssh_username
566+
ssh_username = self.layer.ssh_username
562567
fqdn = self.fqdn
563-
ssh_private_key = self.formation.ssh_private_key
568+
ssh_private_key = self.layer.ssh_private_key
564569
args = (self.uuid, ssh_username, fqdn, ssh_private_key)
565570
return args
566571

567572
def terminate(self, *args, **kwargs):
568-
tasks = import_tasks(self.formation.flavor.provider.type)
573+
tasks = import_tasks(self.layer.flavor.provider.type)
569574
args = self._prepare_terminate_args()
570575
# TODO: figure out how to store task return values in model
571576
return tasks.terminate_node.subtask(args)
572577

573578
def _prepare_terminate_args(self):
574-
creds = self.formation.flavor.provider.creds.copy()
575-
params = self.formation.flavor.params.copy()
579+
creds = self.layer.flavor.provider.creds.copy()
580+
params = self.layer.flavor.params.copy()
576581
args = (self.uuid, creds, params, self.provider_id)
577582
return args
578583

api/serializers.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,24 +119,14 @@ class FormationSerializer(serializers.ModelSerializer):
119119

120120
owner = serializers.Field(source='owner.username')
121121
id = serializers.SlugField(default=utils.generate_app_name)
122-
flavor = serializers.SlugRelatedField(slug_field='id')
123-
containers = serializers.ModelField(
124-
model_field=models.Formation()._meta.get_field('containers'), required=False)
122+
#containers = serializers.ModelField(
123+
# model_field=models.Formation()._meta.get_field('containers'), required=False)
125124

126125
class Meta:
127126
"""Metadata options for a FormationSerializer."""
128127
model = models.Formation
129128
read_only_fields = ('created', 'updated')
130129

131-
@property
132-
def data(self):
133-
"Custom data property that removes secure fields"
134-
d = super(FormationSerializer, self).data
135-
for f in ('ssh_private_key',):
136-
if f in d:
137-
del d[f]
138-
return d
139-
140130

141131
class LayerSerializer(serializers.ModelSerializer):
142132

@@ -151,6 +141,15 @@ class Meta:
151141
model = models.Layer
152142
read_only_fields = ('created', 'updated')
153143

144+
@property
145+
def data(self):
146+
"Custom data property that removes secure fields"
147+
d = super(LayerSerializer, self).data
148+
for f in ('ssh_private_key',):
149+
if f in d:
150+
del d[f]
151+
return d
152+
154153

155154
class NodeSerializer(serializers.ModelSerializer):
156155

api/tests/build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test_build(self):
4040
can post new builds to a formation
4141
"""
4242
url = '/api/formations'
43-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest'}
43+
body = {'id': 'autotest'}
4444
response = self.client.post(url, json.dumps(body), content_type='application/json')
4545
self.assertEqual(response.status_code, 201)
4646
formation_id = response.data['id']

api/tests/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_config(self):
3737
new versions can be created using a PATCH
3838
"""
3939
url = '/api/formations'
40-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest'}
40+
body = {'id': 'autotest'}
4141
response = self.client.post(url, json.dumps(body), content_type='application/json')
4242
self.assertEqual(response.status_code, 201)
4343
formation_id = response.data['id']

api/tests/container.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ def setUp(self):
3636

3737
def test_container_scale(self):
3838
url = '/api/formations'
39-
body = {'id': 'autotest', 'flavor': 'autotest'}
39+
body = {'id': 'autotest'}
4040
response = self.client.post(url, json.dumps(body), content_type='application/json')
4141
self.assertEqual(response.status_code, 201)
4242
formation_id = response.data['id']
4343
url = '/api/formations/{formation_id}/layers'.format(**locals())
44-
body = {'id': 'runtime', 'run_list': 'recipe[deis::runtime]'}
44+
body = {'id': 'runtime', 'flavor': 'autotest', 'run_list': 'recipe[deis::runtime]'}
4545
response = self.client.post(url, json.dumps(body), content_type='application/json')
4646
self.assertEqual(response.status_code, 201)
4747
# scale runtime layer up
@@ -87,12 +87,12 @@ def test_container_scale(self):
8787

8888
def test_container_balance(self):
8989
url = '/api/formations'
90-
body = {'id': 'autotest', 'flavor': 'autotest'}
90+
body = {'id': 'autotest'}
9191
response = self.client.post(url, json.dumps(body), content_type='application/json')
9292
self.assertEqual(response.status_code, 201)
9393
formation_id = response.data['id']
9494
url = '/api/formations/{formation_id}/layers'.format(**locals())
95-
body = {'id': 'runtime', 'run_list': 'recipe[deis::runtime]'}
95+
body = {'id': 'runtime', 'flavor': 'autotest', 'run_list': 'recipe[deis::runtime]'}
9696
response = self.client.post(url, json.dumps(body), content_type='application/json')
9797
self.assertEqual(response.status_code, 201)
9898
# scale layer

api/tests/formation.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import json
1010

1111
from django.test import TestCase
12-
from Crypto.PublicKey import RSA
1312

1413

1514
class FormationTest(TestCase):
@@ -37,11 +36,10 @@ def test_formation(self):
3736
Test that a user can create, read, update and delete a node formation
3837
"""
3938
url = '/api/formations'
40-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest'}
39+
body = {'id': 'autotest'}
4140
response = self.client.post(url, json.dumps(body), content_type='application/json')
4241
self.assertEqual(response.status_code, 201)
4342
formation_id = response.data['id']
44-
self.assertIn('flavor', response.data)
4543
self.assertIn('layers', response.data)
4644
self.assertIn('containers', response.data)
4745
response = self.client.get('/api/formations')
@@ -57,39 +55,26 @@ def test_formation(self):
5755
self.assertEqual(response.status_code, 204)
5856

5957
def test_formation_auto_id(self):
60-
body = {'flavor': 'autotest', 'image': 'deis/autotest'}
58+
body = {'id': 'autotest'}
6159
response = self.client.post('/api/formations', json.dumps(body), content_type='application/json')
6260
self.assertEqual(response.status_code, 201)
6361
self.assertTrue(response.data['id'])
6462
return response
65-
66-
def test_formation_ssh_override(self):
67-
key = RSA.generate(2048)
68-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest',
69-
'ssh_private_key': key.exportKey('PEM'),
70-
'ssh_public_key': key.exportKey('OpenSSH')}
71-
url = '/api/formations'
72-
response = self.client.post(url, json.dumps(body), content_type='application/json')
73-
self.assertEqual(response.status_code, 201)
74-
self.assertIn('ssh_public_key', response.data)
75-
self.assertEquals(response.data['ssh_public_key'], body['ssh_public_key'])
76-
# ssh private key should be hidden
77-
self.assertNotIn('ssh_private_key', response.data)
7863

7964
def test_formation_errors(self):
8065
# test duplicate id
81-
body = {'flavor': 'autotest', 'image': 'deis/autotest'}
66+
body = {}
8267
response = self.client.post('/api/formations', json.dumps(body), content_type='application/json')
8368
self.assertEqual(response.status_code, 201)
8469
self.assertTrue(response.data['id'])
85-
body = {'id': response.data['id'], 'flavor': 'autotest', 'image': 'deis/autotest'}
70+
body = {'id': response.data['id']}
8671
response = self.client.post('/api/formations', json.dumps(body), content_type='application/json')
8772
self.assertEqual(response.status_code, 400)
8873
self.assertEqual(json.loads(response.content), 'Formation with this Id already exists.')
8974

9075
def test_formation_scale_errors(self):
9176
url = '/api/formations'
92-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest'}
77+
body = {'id': 'autotest'}
9378
response = self.client.post(url, json.dumps(body), content_type='application/json')
9479
self.assertEqual(response.status_code, 201)
9580
formation_id = response.data['id']
@@ -101,7 +86,7 @@ def test_formation_scale_errors(self):
10186
self.assertEqual(json.loads(response.content), 'Must create a "runtime" layer to host containers')
10287
# scaling containers without any runtime nodes should throw an error
10388
url = '/api/formations/{formation_id}/layers'.format(**locals())
104-
body = {'id': 'runtime', 'run_list': 'recipe[deis::runtime]'}
89+
body = {'id': 'runtime', 'flavor': 'autotest', 'run_list': 'recipe[deis::runtime]'}
10590
response = self.client.post(url, json.dumps(body), content_type='application/json')
10691
self.assertEqual(response.status_code, 201)
10792
url = '/api/formations/{formation_id}/scale/containers'.format(**locals())
@@ -112,7 +97,7 @@ def test_formation_scale_errors(self):
11297

11398
def test_formation_actions(self):
11499
url = '/api/formations'
115-
body = {'id': 'autotest', 'flavor': 'autotest', 'image': 'deis/autotest'}
100+
body = {'id': 'autotest'}
116101
response = self.client.post(url, json.dumps(body), content_type='application/json')
117102
self.assertEqual(response.status_code, 201)
118103
formation_id = response.data['id']

0 commit comments

Comments
 (0)