Add file foreign key to Task and Playbook

And add some tests, too.

Change-Id: Ie44dd60dd7fdee546b3d3a2a5424f8f551f08b4e
This commit is contained in:
Guillaume Vincent 2018-03-20 19:39:49 +01:00 committed by David Moreau Simard
parent 17f6c9eca5
commit 61bd81226f
No known key found for this signature in database
GPG Key ID: 33A07694CBB71ECC
7 changed files with 130 additions and 91 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.3 on 2018-03-20 13:55
# Generated by Django 2.0.3 on 2018-03-20 18:21
from django.db import migrations, models
import django.db.models.deletion
@ -81,6 +81,7 @@ class Migration(migrations.Migration):
('ansible_version', models.CharField(max_length=255)),
('completed', models.BooleanField(default=False)),
('parameters', models.BinaryField(max_length=4294967295)),
('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='playbooks', to='api.File')),
('files', models.ManyToManyField(to='api.File')),
],
options={
@ -112,6 +113,7 @@ class Migration(migrations.Migration):
('ended', models.DateTimeField(blank=True, null=True)),
('status', models.CharField(choices=[('ok', 'ok'), ('failed', 'failed'), ('skipped', 'skipped'), ('unreachable', 'unreachable'), ('changed', 'changed'), ('ignored', 'ignored'), ('unknown', 'unknown')], default='unknown', max_length=25)),
('content', models.BinaryField(max_length=4294967295)),
('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Host')),
],
options={
'db_table': 'results',
@ -130,39 +132,29 @@ class Migration(migrations.Migration):
('lineno', models.IntegerField()),
('tags', models.BinaryField(max_length=4294967295)),
('handler', models.BooleanField()),
('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.File')),
('files', models.ManyToManyField(to='api.File')),
('play', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='api.Play')),
('results', models.ManyToManyField(to='api.Result')),
],
options={
'db_table': 'tasks',
},
),
migrations.AddField(
model_name='playbook',
name='results',
field=models.ManyToManyField(to='api.Result'),
model_name='result',
name='task',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='api.Task'),
),
migrations.AddField(
model_name='play',
name='playbook',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plays', to='api.Playbook'),
),
migrations.AddField(
model_name='play',
name='results',
field=models.ManyToManyField(to='api.Result'),
),
migrations.AddField(
model_name='host',
name='play',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='hosts', to='api.Play'),
),
migrations.AddField(
model_name='host',
name='results',
field=models.ManyToManyField(to='api.Result'),
),
migrations.AddField(
model_name='file',
name='content',

View File

@ -77,42 +77,6 @@ class File(Base):
return '<File %s:%s>' % (self.id, self.path)
class Result(Duration):
"""
Data about Ansible results.
A task can have many results if the task is run on multiple hosts.
"""
class Meta:
db_table = 'results'
# Ansible statuses
OK = 'ok'
FAILED = 'failed'
SKIPPED = 'skipped'
UNREACHABLE = 'unreachable'
# ARA specific statuses (derived or assumed)
CHANGED = 'changed'
IGNORED = 'ignored'
UNKNOWN = 'unknown'
STATUS = (
(OK, 'ok'),
(FAILED, 'failed'),
(SKIPPED, 'skipped'),
(UNREACHABLE, 'unreachable'),
(CHANGED, 'changed'),
(IGNORED, 'ignored'),
(UNKNOWN, 'unknown')
)
status = models.CharField(max_length=25, choices=STATUS, default=UNKNOWN)
content = models.BinaryField(max_length=(2 ** 32) - 1)
def __str__(self):
return '<Result %s, %s>' % (self.id, self.status)
class Playbook(Duration):
"""
An entry in the 'playbooks' table represents a single execution of the
@ -126,8 +90,8 @@ class Playbook(Duration):
ansible_version = models.CharField(max_length=255)
completed = models.BooleanField(default=False)
parameters = models.BinaryField(max_length=(2 ** 32) - 1)
file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='playbooks')
files = models.ManyToManyField(File)
results = models.ManyToManyField(Result)
def __str__(self):
return '<Playbook %s>' % self.id
@ -163,7 +127,6 @@ class Play(Duration):
name = models.CharField(max_length=255, blank=True, null=True)
playbook = models.ForeignKey(Playbook, on_delete=models.CASCADE, related_name='plays')
results = models.ManyToManyField(Result)
def __str__(self):
return '<Play %s:%s>' % (self.id, self.name)
@ -182,7 +145,7 @@ class Task(Duration):
handler = models.BooleanField()
play = models.ForeignKey(Play, on_delete=models.CASCADE, related_name='tasks')
results = models.ManyToManyField(Result)
file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='tasks')
files = models.ManyToManyField(File)
def __str__(self):
@ -208,7 +171,45 @@ class Host(Base):
skipped = models.IntegerField(default=0)
unreachable = models.IntegerField(default=0)
play = models.ForeignKey(Play, on_delete=models.DO_NOTHING, related_name='hosts')
results = models.ManyToManyField(Result)
def __str__(self):
return '<Host %s:%s>' % (self.id, self.name)
class Result(Duration):
"""
Data about Ansible results.
A task can have many results if the task is run on multiple hosts.
"""
class Meta:
db_table = 'results'
# Ansible statuses
OK = 'ok'
FAILED = 'failed'
SKIPPED = 'skipped'
UNREACHABLE = 'unreachable'
# ARA specific statuses (derived or assumed)
CHANGED = 'changed'
IGNORED = 'ignored'
UNKNOWN = 'unknown'
STATUS = (
(OK, 'ok'),
(FAILED, 'failed'),
(SKIPPED, 'skipped'),
(UNREACHABLE, 'unreachable'),
(CHANGED, 'changed'),
(IGNORED, 'ignored'),
(UNKNOWN, 'unknown')
)
status = models.CharField(max_length=25, choices=STATUS, default=UNKNOWN)
# todo use a single Content table
content = models.BinaryField(max_length=(2 ** 32) - 1)
host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='results')
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='results')
def __str__(self):
return '<Result %s, %s>' % (self.id, self.status)

View File

@ -116,24 +116,19 @@ class PlaybookSerializer(DurationSerializer):
model = models.Playbook
fields = '__all__'
parameters = CompressedObjectField(
default=zlib.compress(json.dumps({}).encode('utf8')),
help_text='A JSON dictionary containing Ansible command parameters'
)
parameters = CompressedObjectField(default=zlib.compress(json.dumps({}).encode('utf8')))
file = FileSerializer()
files = FileSerializer(many=True, default=[])
results = ResultSerializer(read_only=True, many=True)
def create(self, validated_data):
file_dict = validated_data.pop('file')
validated_data['file'] = models.File.objects.create(**file_dict)
files = validated_data.pop('files')
playbook = models.Playbook.objects.create(**validated_data)
for file in files:
playbook.files.add(models.File.objects.create(**file))
return playbook
def update(self, instance, validated_data):
files = validated_data.pop('files')
return super(PlaybookSerializer, self).update(instance, validated_data)
class PlaySerializer(DurationSerializer):
class Meta:

View File

@ -3,15 +3,6 @@ import factory
from api import models
class PlaybookFactory(factory.DjangoModelFactory):
class Meta:
model = models.Playbook
ansible_version = '2.4.0'
completed = True
parameters = b'x\x9c\xabVJ\xcb\xcfW\xb2RPJJ,R\xaa\x05\x00 \x98\x04T'
class FileContentFactory(factory.DjangoModelFactory):
class Meta:
model = models.FileContent
@ -27,3 +18,13 @@ class FileFactory(factory.DjangoModelFactory):
path = '/tmp/playbook.yml'
content = factory.SubFactory(FileContentFactory)
class PlaybookFactory(factory.DjangoModelFactory):
class Meta:
model = models.Playbook
ansible_version = '2.4.0'
completed = True
parameters = b'x\x9c\xabVJ\xcb\xcfW\xb2RPJJ,R\xaa\x05\x00 \x98\x04T'
file = factory.SubFactory(FileFactory)

View File

@ -43,6 +43,15 @@ class FileTestCase(APITestCase):
self.assertEqual(2, models.File.objects.all().count())
self.assertEqual(1, models.FileContent.objects.all().count())
def test_create_file(self):
self.assertEqual(0, models.File.objects.count())
request = self.client.post('/api/v1/files/', {
'path': '/tmp/playbook.yml',
'content': '# playbook'
})
self.assertEqual(201, request.status_code)
self.assertEqual(1, models.File.objects.count())
def test_get_no_files(self):
request = self.client.get('/api/v1/files/')
self.assertEqual(0, len(request.data['results']))
@ -53,21 +62,21 @@ class FileTestCase(APITestCase):
self.assertEqual(1, len(request.data['results']))
self.assertEqual(file.path, request.data['results'][0]['path'])
def test_delete_file(self):
def test_get_file(self):
file = factories.FileFactory()
self.assertEqual(1, models.File.objects.all().count())
request = self.client.delete('/api/v1/files/%s/' % file.id)
self.assertEqual(204, request.status_code)
self.assertEqual(0, models.File.objects.all().count())
request = self.client.get('/api/v1/files/%s/' % file.id)
self.assertEqual(file.path, request.data['path'])
def test_create_file(self):
self.assertEqual(0, models.File.objects.count())
request = self.client.post('/api/v1/files/', {
'path': '/tmp/playbook.yml',
def test_update_file(self):
file = factories.FileFactory()
self.assertNotEqual('/tmp/new_playbook.yml', file.path)
request = self.client.put('/api/v1/files/%s/' % file.id, {
"path": "/tmp/new_playbook.yml",
'content': '# playbook'
})
self.assertEqual(201, request.status_code)
self.assertEqual(1, models.File.objects.count())
self.assertEqual(200, request.status_code)
file_updated = models.File.objects.get(id=file.id)
self.assertEqual('/tmp/new_playbook.yml', file_updated.path)
def test_partial_update_file(self):
file = factories.FileFactory()
@ -79,7 +88,9 @@ class FileTestCase(APITestCase):
file_updated = models.File.objects.get(id=file.id)
self.assertEqual('/tmp/new_playbook.yml', file_updated.path)
def test_get_file(self):
def test_delete_file(self):
file = factories.FileFactory()
request = self.client.get('/api/v1/files/%s/' % file.id)
self.assertEqual(file.path, request.data['path'])
self.assertEqual(1, models.File.objects.all().count())
request = self.client.delete('/api/v1/files/%s/' % file.id)
self.assertEqual(204, request.status_code)
self.assertEqual(0, models.File.objects.all().count())

View File

@ -13,7 +13,11 @@ class PlaybookTestCase(APITestCase):
def test_playbook_serializer(self):
serializer = serializers.PlaybookSerializer(data={
'ansible_version': '2.4.0'
'ansible_version': '2.4.0',
'file': {
'path': '/tmp/playbook.yml',
'content': '# playbook'
}
})
serializer.is_valid()
playbook = serializer.save()
@ -23,6 +27,10 @@ class PlaybookTestCase(APITestCase):
def test_playbook_serializer_compress_parameters(self):
serializer = serializers.PlaybookSerializer(data={
'ansible_version': '2.4.0',
'file': {
'path': '/tmp/playbook.yml',
'content': '# playbook'
},
'parameters': {'foo': 'bar'}
})
serializer.is_valid()
@ -56,14 +64,18 @@ class PlaybookTestCase(APITestCase):
self.assertEqual(0, models.Playbook.objects.count())
request = self.client.post('/api/v1/playbooks/', {
"ansible_version": "2.4.0",
'file': {
'path': '/tmp/playbook.yml',
'content': '# playbook'
}
})
self.assertEqual(201, request.status_code)
self.assertEqual(1, models.Playbook.objects.count())
def test_update_playbook(self):
def test_partial_update_playbook(self):
playbook = factories.PlaybookFactory()
self.assertNotEqual('2.3.0', playbook.ansible_version)
request = self.client.put('/api/v1/playbooks/%s/' % playbook.id, {
request = self.client.patch('/api/v1/playbooks/%s/' % playbook.id, {
"ansible_version": "2.3.0",
})
self.assertEqual(200, request.status_code)

View File

@ -12,10 +12,37 @@ class PlaybookFileTestCase(APITestCase):
self.assertEqual(0, models.File.objects.all().count())
self.client.post('/api/v1/playbooks/', {
'ansible_version': '2.4.0',
'files': [{
'file': {
'path': '/tmp/playbook.yml',
'content': '# playbook'
},
'files': [{
'path': '/tmp/host',
'content': '# host'
}],
})
self.assertEqual(1, models.Playbook.objects.all().count())
self.assertEqual(2, models.File.objects.all().count())
def test_create_file_to_a_playbook(self):
playbook = factories.PlaybookFactory()
self.assertEqual(0, models.File.objects.all().count())
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
'path': '/tmp/playbook.yml',
'content': '# playbook'
})
self.assertEqual(1, models.File.objects.all().count())
self.assertEqual(1, models.FileContent.objects.all().count())
def test_create_2_files_with_same_content(self):
playbook = factories.PlaybookFactory()
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
'path': '/tmp/1/playbook.yml',
'content': '# playbook'
})
self.client.post('/api/v1/playbooks/%s/files' % playbook.id, {
'path': '/tmp/2/playbook.yml',
'content': '# playbook'
})
self.assertEqual(2, models.File.objects.all().count())
self.assertEqual(1, models.FileContent.objects.all().count())