diff --git a/nailgun/nailgun/api/forms.py b/nailgun/nailgun/api/forms.py index c1e38f88f..2eef2dfd7 100644 --- a/nailgun/nailgun/api/forms.py +++ b/nailgun/nailgun/api/forms.py @@ -1,4 +1,5 @@ import re +import ipaddr import simplejson as json from django.core.exceptions import ValidationError @@ -7,7 +8,7 @@ from django.forms.fields import Field, IntegerField, CharField, ChoiceField, \ BooleanField from django.core.validators import RegexValidator -from nailgun.models import Cluster, Node, Recipe, Role, Release +from nailgun.models import Cluster, Node, Recipe, Role, Release, Network class RecipeForm(forms.ModelForm): @@ -139,6 +140,7 @@ def validate_networks_metadata(data): if not isinstance(data, list): raise ValidationError("There should be a list of network names") + class ReleaseCreationForm(forms.ModelForm): roles = Field(validators=[validate_release_node_roles]) networks_metadata = Field(validators=[validate_networks_metadata]) @@ -151,8 +153,37 @@ class ReleaseCreationForm(forms.ModelForm): def validate_network(data): - pass + try: + a = ipaddr.IPv4Network(data) + except: + raise ValidationError("Invalid network format!") -class NetworkCreationForm(forms.Form): - network = Field(validators=[validate_network]) +def validate_ip(data): + try: + a = ipaddr.IPv4Address(data) + except: + raise ValidationError("Invalid IP address format!") + + +class NetworkCreationForm(forms.ModelForm): + release = CharField() + network = CharField(validators=[validate_network]) + range_l = CharField(validators=[validate_ip]) + range_h = CharField(validators=[validate_ip]) + gateway = CharField(validators=[validate_ip]) + + class Meta: + model = Network + + def clean_release(self): + release_id = self.cleaned_data["release"] + if not release_id: + raise ValidationError("Release id not specified!") + try: + r = Release.objects.get(id=release_id) + except Release.DoesNotExist: + raise ValidationError("Invalid release id!") + + #self.instance.release = r + return r diff --git a/nailgun/nailgun/api/handlers.py b/nailgun/nailgun/api/handlers.py index ed08a181e..3ec1e9e72 100644 --- a/nailgun/nailgun/api/handlers.py +++ b/nailgun/nailgun/api/handlers.py @@ -11,7 +11,7 @@ from nailgun.models import Cluster, Node, Recipe, Role, Release, Network from nailgun.api.validators import validate_json from nailgun.api.forms import ClusterForm, ClusterCreationForm, RecipeForm, \ RoleForm, RoleFilterForm, NodeCreationForm, NodeFilterForm, NodeForm, \ - ReleaseCreationForm + ReleaseCreationForm, NetworkCreationForm from nailgun import tasks @@ -472,7 +472,8 @@ class NetworkHandler(JSONHandler): allowed_methods = ('GET',) model = Network - fields = ('id', 'network') + fields = ('id', 'network', 'name', 'access', + 'vlan_id', 'range_l', 'range_h', 'gateway') special_fields = ('release', 'nodes') @classmethod @@ -495,8 +496,41 @@ class NetworkHandler(JSONHandler): except Network.DoesNotExist: return rc.NOT_FOUND + class NetworkCollectionHandler(BaseHandler): allowed_methods = ('GET', 'POST') model = Network + @validate_json(NetworkCreationForm) + def create(self, request): + data = request.form.cleaned_data + + try: + release = Network.objects.get( + name=data['name'], + network=data['network'] + ) + return rc.DUPLICATE_ENTRY + except Network.DoesNotExist: + pass + + nw = Network( + name=data['name'], + network=data['network'], + release=data['release'], + access=data['access'], + range_l=data['range_l'], + range_h=data['range_h'], + gateway=data['gateway'], + vlan_id=data['vlan_id'] + ) + nw.save() + + return NetworkHandler.render(nw) + + def read(self, request): + return map( + NetworkHandler.render, + Network.objects.all() + ) diff --git a/nailgun/nailgun/fixtures/default_cluster.json b/nailgun/nailgun/fixtures/default_cluster.json index 3d048202d..c8291cbe1 100644 --- a/nailgun/nailgun/fixtures/default_cluster.json +++ b/nailgun/nailgun/fixtures/default_cluster.json @@ -3,7 +3,12 @@ "model": "nailgun.release", "pk": 1, "fields": { - "name": "Default Release" + "name": "Default Release", + "networks_metadata": [ + {"name": "floating", "access": "public"}, + {"name": "fixed", "access": "private"}, + {"name": "management", "access": "private"} + ] } }, { diff --git a/nailgun/nailgun/models.py b/nailgun/nailgun/models.py index b955ef8e7..d09f3df40 100644 --- a/nailgun/nailgun/models.py +++ b/nailgun/nailgun/models.py @@ -71,7 +71,7 @@ class Network(models.Model): range_l = models.CharField(max_length=25) range_h = models.CharField(max_length=25) gateway = models.CharField(max_length=25) - nodes = models.ManyToManyField(Node, through=IPAddr) + nodes = models.ManyToManyField(Node, through=IPAddr, null=True, blank=True) @property def netmask(self): diff --git a/nailgun/nailgun/tests/test_handlers.py b/nailgun/nailgun/tests/test_handlers.py index edfef3191..d9fa1f829 100644 --- a/nailgun/nailgun/tests/test_handlers.py +++ b/nailgun/nailgun/tests/test_handlers.py @@ -33,10 +33,6 @@ class TestHandlers(TestCase): 'cpu': 'asf', 'memory': 'sd' } - self.another_cluster = Cluster(id=2, - release_id=1, - name='Another cluster') - self.another_cluster.save() self.node_name = "test.server.com" @@ -86,6 +82,12 @@ class TestHandlers(TestCase): 'cpu': 'u', 'memory': 'a' } + + self.another_cluster = Cluster(id=2, + release_id=1, + name='Another cluster') + self.another_cluster.save() + self.meta_json = json.dumps(self.new_meta) def tearDown(self): @@ -106,6 +108,7 @@ class TestHandlers(TestCase): 'cluster_handler': {'cluster_id': 1}, 'node_handler': {'node_id': 'A' * 12}, #'task_handler': {'task_id': 'a' * 36}, + 'network_handler': {'network_id': 1}, 'release_handler': {'release_id': 1}, 'role_handler': {'role_id': 1}, 'node_role_available': {'node_id': 'A' * 12, 'role_id': 1}, @@ -147,6 +150,8 @@ class TestHandlers(TestCase): self.assertEquals(len(clusters_from_db), 1) self.assertEquals(clusters_from_db[0].nodes.all()[0].id, self.another_node.id) + cluster = clusters_from_db[0] + self.assertEquals(len(cluster.release.networks.all()), 3) def test_cluster_update(self): updated_name = 'Updated cluster' @@ -416,7 +421,12 @@ class TestHandlers(TestCase): 'name': release_name, 'version': release_version, 'description': release_description, - 'roles': release_roles + 'roles': release_roles, + 'networks_metadata': [ + {"name": "floating", "access": "public"}, + {"name": "fixed", "access": "private"}, + {"name": "storage", "access": "private"} + ] }), "application/json" ) @@ -429,7 +439,10 @@ class TestHandlers(TestCase): 'name': release_name, 'version': release_version, 'description': release_description, - 'roles': release_roles + 'roles': release_roles, + 'networks_metadata': [ + {"name": "fixed", "access": "private"} + ] }), "application/json" ) @@ -450,3 +463,34 @@ class TestHandlers(TestCase): }) for a, b in zip(sorted(roles), sorted(release_roles)): self.assertEquals(a, b) + + def test_network_create(self): + network_data = { + "name": "test_network", + "network": "10.0.0.0/24", + "range_l": "10.0.0.5", + "range_h": "10.0.0.10", + "gateway": "10.0.0.1", + "vlan_id": 100, + "release": 1, + "access": "public" + } + resp = self.client.post( + reverse('network_collection_handler'), + json.dumps(network_data), + "application/json" + ) + self.assertEquals(resp.status_code, 200) + resp = self.client.post( + reverse('network_collection_handler'), + json.dumps(network_data), + "application/json" + ) + self.assertEquals(resp.status_code, 409) + network_data["network"] = "test_fail" + resp = self.client.post( + reverse('network_collection_handler'), + json.dumps(network_data), + "application/json" + ) + self.assertEqual(resp.status_code, 400) diff --git a/scripts/ci/sample-release.json b/scripts/ci/sample-release.json index abde29f61..07c4a6d48 100644 --- a/scripts/ci/sample-release.json +++ b/scripts/ci/sample-release.json @@ -4,9 +4,7 @@ "description": "Description for Release", "networks_metadata": [ {"name": "floating", "access": "public"}, - {"name": "fixed", "access": "private"}, {"name": "management", "access": "private"}, - {"name": "public", "access": "public"}, {"name": "storage", "access": "private"} ], "roles": [{