Merge lp:~julian-edwards/maas/dns-in-networks into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2922
Proposed branch: lp:~julian-edwards/maas/dns-in-networks
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 552 lines (+406/-4)
9 files modified
src/maasserver/api/networks.py (+2/-1)
src/maasserver/api/tests/test_network.py (+3/-0)
src/maasserver/api/tests/test_networks.py (+3/-1)
src/maasserver/forms.py (+1/-0)
src/maasserver/migrations/0109_networks_dns_servers.py (+377/-0)
src/maasserver/models/network.py (+5/-0)
src/maasserver/templates/maasserver/network_detail.html (+4/-0)
src/maasserver/testing/factory.py (+8/-2)
src/maasserver/views/tests/test_networks.py (+3/-0)
To merge this branch: bzr merge lp:~julian-edwards/maas/dns-in-networks
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+233464@code.launchpad.net

Commit message

Add dns_servers as a field in Networks. It's purely book-keeping for the cloud installer so nothing special is done with it here.

Description of the change

The UI changes are to put it in the Network detail view only; the listing view is already quite wide and this is not strictly a network property.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Don't be afraid of the size of the branch, most of it is the migration.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

About time to line-break that ‘fields’ tuple in networks.py properly, I think...

I'd be happy to know I was worried about nothing, but will this new field support both empty strings and nulls? It seems like a recipe for confusion, e.g. if we start doing things like “if no DNS servers have been set on this network that we manage, substitute the MAAS DNS server.” Does that go for nulls, or for blanks as well? Which do you get in API results? Probably worth being clear about.

Personally I would say “space-separated” rather than “space separated”; avoids grammatical ambiguity early on.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Thanks for reviewing!

On Friday 05 September 2014 08:06:48 you wrote:
> Review: Approve
>
> About time to line-break that ‘fields’ tuple in networks.py properly, I
> think...

I can't see anything wrong with it.

> I'd be happy to know I was worried about nothing, but will this new field
> support both empty strings and nulls? It seems like a recipe for
> confusion, e.g. if we start doing things like “if no DNS servers have been
> set on this network that we manage, substitute the MAAS DNS server.” Does
> that go for nulls, or for blanks as well? Which do you get in API results?
> Probably worth being clear about.

AFAIK it stores blanks as NULL with those field settings.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/networks.py'
--- src/maasserver/api/networks.py 2014-09-05 01:36:31 +0000
+++ src/maasserver/api/networks.py 2014-09-05 07:17:23 +0000
@@ -46,7 +46,8 @@
4646
47 model = Network47 model = Network
48 fields = (48 fields = (
49 'name', 'ip', 'netmask', 'vlan_tag', 'description', 'default_gateway')49 'name', 'ip', 'netmask', 'vlan_tag', 'description', 'default_gateway',
50 'dns_servers')
5051
51 # Creation happens on the NetworksHandler.52 # Creation happens on the NetworksHandler.
52 create = None53 create = None
5354
=== modified file 'src/maasserver/api/tests/test_network.py'
--- src/maasserver/api/tests/test_network.py 2014-09-05 06:48:30 +0000
+++ src/maasserver/api/tests/test_network.py 2014-09-05 07:17:23 +0000
@@ -56,6 +56,7 @@
56 network.vlan_tag,56 network.vlan_tag,
57 network.description,57 network.description,
58 network.default_gateway,58 network.default_gateway,
59 network.dns_servers,
59 ),60 ),
60 (61 (
61 parsed_result['name'],62 parsed_result['name'],
@@ -64,6 +65,7 @@
64 parsed_result['vlan_tag'],65 parsed_result['vlan_tag'],
65 parsed_result['description'],66 parsed_result['description'],
66 parsed_result['default_gateway'],67 parsed_result['default_gateway'],
68 parsed_result['dns_servers'],
67 ))69 ))
6870
69 def test_GET_returns_404_for_unknown_network(self):71 def test_GET_returns_404_for_unknown_network(self):
@@ -82,6 +84,7 @@
82 'vlan_tag': factory.make_vlan_tag(),84 'vlan_tag': factory.make_vlan_tag(),
83 'description': "Changed description",85 'description': "Changed description",
84 'default_gateway': factory.getRandomIPAddress(),86 'default_gateway': factory.getRandomIPAddress(),
87 'dns_servers': factory.getRandomIPAddress(),
85 }88 }
8689
87 response = self.client_put(self.get_url(network.name), new_values)90 response = self.client_put(self.get_url(network.name), new_values)
8891
=== modified file 'src/maasserver/api/tests/test_networks.py'
--- src/maasserver/api/tests/test_networks.py 2014-09-05 06:48:30 +0000
+++ src/maasserver/api/tests/test_networks.py 2014-09-05 07:17:23 +0000
@@ -38,6 +38,8 @@
38 'netmask': '%s' % net.netmask,38 'netmask': '%s' % net.netmask,
39 'vlan_tag': factory.make_vlan_tag(),39 'vlan_tag': factory.make_vlan_tag(),
40 'description': factory.make_string(),40 'description': factory.make_string(),
41 'default_gateway': factory.getRandomIPAddress(),
42 'dns_servers': factory.getRandomIPAddress(),
41 }43 }
42 response = self.client.post(reverse('networks_handler'), params)44 response = self.client.post(reverse('networks_handler'), params)
43 self.assertEqual(httplib.OK, response.status_code)45 self.assertEqual(httplib.OK, response.status_code)
@@ -63,7 +65,7 @@
63 [returned_network] = parsed_result65 [returned_network] = parsed_result
64 fields = {66 fields = {
65 'name', 'ip', 'netmask', 'vlan_tag', 'description',67 'name', 'ip', 'netmask', 'vlan_tag', 'description',
66 'default_gateway'}68 'default_gateway', 'dns_servers'}
67 self.assertEqual(69 self.assertEqual(
68 fields.union({'resource_uri'}),70 fields.union({'resource_uri'}),
69 set(returned_network.keys()))71 set(returned_network.keys()))
7072
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2014-09-05 01:32:07 +0000
+++ src/maasserver/forms.py 2014-09-05 07:17:23 +0000
@@ -1915,6 +1915,7 @@
1915 'netmask',1915 'netmask',
1916 'vlan_tag',1916 'vlan_tag',
1917 'default_gateway',1917 'default_gateway',
1918 'dns_servers',
1918 )1919 )
19191920
1920 mac_addresses = NodeMACAddressChoiceField(1921 mac_addresses = NodeMACAddressChoiceField(
19211922
=== added file 'src/maasserver/migrations/0109_networks_dns_servers.py'
--- src/maasserver/migrations/0109_networks_dns_servers.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0109_networks_dns_servers.py 2014-09-05 07:17:23 +0000
@@ -0,0 +1,377 @@
1from django.db import models
2from south.db import db
3# -*- coding: utf-8 -*-
4from south.utils import datetime_utils as datetime
5from south.v2 import SchemaMigration
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'Network.dns_servers'
12 db.add_column(u'maasserver_network', 'dns_servers',
13 self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Network.dns_servers'
19 db.delete_column(u'maasserver_network', 'dns_servers')
20
21
22 models = {
23 u'auth.group': {
24 'Meta': {'object_name': 'Group'},
25 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
27 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
28 },
29 u'auth.permission': {
30 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
31 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
32 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
35 },
36 u'auth.user': {
37 'Meta': {'object_name': 'User'},
38 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
39 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
40 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
41 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
42 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
43 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
44 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
45 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
46 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
47 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
48 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
49 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
50 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
51 },
52 u'contenttypes.contenttype': {
53 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
54 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
55 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
56 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
57 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
58 },
59 u'maasserver.bootimage': {
60 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
61 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
62 'created': ('django.db.models.fields.DateTimeField', [], {}),
63 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
64 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
65 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
66 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
67 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
68 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
69 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
70 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
71 'updated': ('django.db.models.fields.DateTimeField', [], {}),
72 'xinstall_path': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
73 'xinstall_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '30', 'null': 'True', 'blank': 'True'})
74 },
75 u'maasserver.bootresource': {
76 'Meta': {'unique_together': "((u'name', u'architecture'),)", 'object_name': 'BootResource'},
77 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
78 'created': ('django.db.models.fields.DateTimeField', [], {}),
79 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
80 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
81 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
82 'rtype': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
83 'updated': ('django.db.models.fields.DateTimeField', [], {})
84 },
85 u'maasserver.bootresourcefile': {
86 'Meta': {'unique_together': "((u'resource_set', u'filetype'),)", 'object_name': 'BootResourceFile'},
87 'created': ('django.db.models.fields.DateTimeField', [], {}),
88 'extra': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
89 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
90 'filetype': ('django.db.models.fields.CharField', [], {'default': "u'root-tgz'", 'max_length': '20'}),
91 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'largefile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.LargeFile']"}),
93 'resource_set': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'files'", 'to': u"orm['maasserver.BootResourceSet']"}),
94 'updated': ('django.db.models.fields.DateTimeField', [], {})
95 },
96 u'maasserver.bootresourceset': {
97 'Meta': {'unique_together': "((u'resource', u'version'),)", 'object_name': 'BootResourceSet'},
98 'created': ('django.db.models.fields.DateTimeField', [], {}),
99 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
101 'resource': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'sets'", 'to': u"orm['maasserver.BootResource']"}),
102 'updated': ('django.db.models.fields.DateTimeField', [], {}),
103 'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
104 },
105 u'maasserver.bootsource': {
106 'Meta': {'object_name': 'BootSource'},
107 'created': ('django.db.models.fields.DateTimeField', [], {}),
108 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
110 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
111 'updated': ('django.db.models.fields.DateTimeField', [], {}),
112 'url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'})
113 },
114 u'maasserver.bootsourceselection': {
115 'Meta': {'object_name': 'BootSourceSelection'},
116 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
117 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
118 'created': ('django.db.models.fields.DateTimeField', [], {}),
119 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
121 'os': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
122 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
123 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
124 'updated': ('django.db.models.fields.DateTimeField', [], {})
125 },
126 u'maasserver.candidatename': {
127 'Meta': {'unique_together': "((u'name', u'position'),)", 'object_name': 'CandidateName'},
128 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
129 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
130 'position': ('django.db.models.fields.IntegerField', [], {})
131 },
132 u'maasserver.componenterror': {
133 'Meta': {'object_name': 'ComponentError'},
134 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
135 'created': ('django.db.models.fields.DateTimeField', [], {}),
136 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
137 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
138 'updated': ('django.db.models.fields.DateTimeField', [], {})
139 },
140 u'maasserver.config': {
141 'Meta': {'object_name': 'Config'},
142 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
143 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
144 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
145 },
146 u'maasserver.dhcplease': {
147 'Meta': {'object_name': 'DHCPLease'},
148 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
149 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
150 'mac': ('maasserver.fields.MACAddressField', [], {}),
151 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
152 },
153 u'maasserver.downloadprogress': {
154 'Meta': {'object_name': 'DownloadProgress'},
155 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
156 'created': ('django.db.models.fields.DateTimeField', [], {}),
157 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
158 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
159 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
161 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
162 'updated': ('django.db.models.fields.DateTimeField', [], {})
163 },
164 u'maasserver.event': {
165 'Meta': {'object_name': 'Event'},
166 'created': ('django.db.models.fields.DateTimeField', [], {}),
167 'description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
168 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
170 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.EventType']"}),
171 'updated': ('django.db.models.fields.DateTimeField', [], {})
172 },
173 u'maasserver.eventtype': {
174 'Meta': {'object_name': 'EventType'},
175 'created': ('django.db.models.fields.DateTimeField', [], {}),
176 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
177 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178 'level': ('django.db.models.fields.IntegerField', [], {}),
179 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
180 'updated': ('django.db.models.fields.DateTimeField', [], {})
181 },
182 u'maasserver.filestorage': {
183 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
184 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
185 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
186 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
187 'key': ('django.db.models.fields.CharField', [], {'default': "u'35907f12-34bb-11e4-bb2b-002215205ce8'", 'unique': 'True', 'max_length': '36'}),
188 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
189 },
190 u'maasserver.largefile': {
191 'Meta': {'object_name': 'LargeFile'},
192 'content': ('maasserver.fields.LargeObjectField', [], {}),
193 'created': ('django.db.models.fields.DateTimeField', [], {}),
194 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
195 'sha256': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
196 'total_size': ('django.db.models.fields.BigIntegerField', [], {}),
197 'updated': ('django.db.models.fields.DateTimeField', [], {})
198 },
199 u'maasserver.licensekey': {
200 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
201 'created': ('django.db.models.fields.DateTimeField', [], {}),
202 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
203 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
205 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
206 'updated': ('django.db.models.fields.DateTimeField', [], {})
207 },
208 u'maasserver.macaddress': {
209 'Meta': {'ordering': "(u'created',)", 'object_name': 'MACAddress'},
210 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'blank': 'True'}),
211 'created': ('django.db.models.fields.DateTimeField', [], {}),
212 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
213 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}),
214 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
215 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
216 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
217 'updated': ('django.db.models.fields.DateTimeField', [], {})
218 },
219 u'maasserver.macstaticipaddresslink': {
220 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'},
221 'created': ('django.db.models.fields.DateTimeField', [], {}),
222 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
223 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}),
224 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}),
225 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
226 'updated': ('django.db.models.fields.DateTimeField', [], {})
227 },
228 u'maasserver.network': {
229 'Meta': {'object_name': 'Network'},
230 'default_gateway': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
231 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
232 'dns_servers': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
233 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
234 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
235 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
236 'netmask': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
237 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
238 },
239 u'maasserver.node': {
240 'Meta': {'object_name': 'Node'},
241 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
242 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
243 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}),
244 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
245 'created': ('django.db.models.fields.DateTimeField', [], {}),
246 'disable_ipv4': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
247 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
248 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
249 'error_description': ('django.db.models.fields.TextField', [], {'default': "u''", 'blank': 'True'}),
250 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
251 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
252 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
253 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
254 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
255 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
256 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
257 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
258 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
259 'power_state': ('django.db.models.fields.CharField', [], {'default': "u'unknown'", 'max_length': '10'}),
260 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
261 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
262 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
263 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
264 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-358bdb4c-34bb-11e4-bb2b-002215205ce8'", 'unique': 'True', 'max_length': '41'}),
265 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
266 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
267 'updated': ('django.db.models.fields.DateTimeField', [], {}),
268 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
269 },
270 u'maasserver.nodegroup': {
271 'Meta': {'object_name': 'NodeGroup'},
272 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
273 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
274 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
275 'created': ('django.db.models.fields.DateTimeField', [], {}),
276 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
277 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
278 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
279 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
280 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
281 'updated': ('django.db.models.fields.DateTimeField', [], {}),
282 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
283 },
284 u'maasserver.nodegroupinterface': {
285 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'},
286 'broadcast_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
287 'created': ('django.db.models.fields.DateTimeField', [], {}),
288 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
289 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
290 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
291 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
292 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
293 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
294 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
295 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
296 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
297 'router_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
298 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
299 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
300 'subnet_mask': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
301 'updated': ('django.db.models.fields.DateTimeField', [], {})
302 },
303 u'maasserver.sshkey': {
304 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
305 'created': ('django.db.models.fields.DateTimeField', [], {}),
306 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
307 'key': ('django.db.models.fields.TextField', [], {}),
308 'updated': ('django.db.models.fields.DateTimeField', [], {}),
309 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
310 },
311 u'maasserver.sslkey': {
312 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
313 'created': ('django.db.models.fields.DateTimeField', [], {}),
314 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
315 'key': ('django.db.models.fields.TextField', [], {}),
316 'updated': ('django.db.models.fields.DateTimeField', [], {}),
317 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
318 },
319 u'maasserver.staticipaddress': {
320 'Meta': {'object_name': 'StaticIPAddress'},
321 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
322 'created': ('django.db.models.fields.DateTimeField', [], {}),
323 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
324 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
325 'updated': ('django.db.models.fields.DateTimeField', [], {}),
326 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
327 },
328 u'maasserver.tag': {
329 'Meta': {'object_name': 'Tag'},
330 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
331 'created': ('django.db.models.fields.DateTimeField', [], {}),
332 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
333 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
334 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
335 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
336 'updated': ('django.db.models.fields.DateTimeField', [], {})
337 },
338 u'maasserver.userprofile': {
339 'Meta': {'object_name': 'UserProfile'},
340 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
341 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
342 },
343 u'maasserver.zone': {
344 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
345 'created': ('django.db.models.fields.DateTimeField', [], {}),
346 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
347 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
348 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
349 'updated': ('django.db.models.fields.DateTimeField', [], {})
350 },
351 u'piston.consumer': {
352 'Meta': {'object_name': 'Consumer'},
353 'description': ('django.db.models.fields.TextField', [], {}),
354 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
355 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
356 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
357 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
358 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
359 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
360 },
361 u'piston.token': {
362 'Meta': {'object_name': 'Token'},
363 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
364 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
365 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
366 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
367 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
368 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
369 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
370 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1409893937L'}),
371 'token_type': ('django.db.models.fields.IntegerField', [], {}),
372 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
373 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
374 }
375 }
376
377 complete_apps = ['maasserver']
0\ No newline at end of file378\ No newline at end of file
1379
=== modified file 'src/maasserver/models/network.py'
--- src/maasserver/models/network.py 2014-09-01 01:45:04 +0000
+++ src/maasserver/models/network.py 2014-09-05 07:17:23 +0000
@@ -248,6 +248,11 @@
248 blank=True, editable=True, null=True,248 blank=True, editable=True, null=True,
249 help_text="Default gateway for this network (e.g. 192.168.1.1).")249 help_text="Default gateway for this network (e.g. 192.168.1.1).")
250250
251 dns_servers = CharField(
252 blank=True, editable=True, null=True, max_length=255,
253 help_text="Space separated list of DNS server addresses that this "
254 "network may use.")
255
251 vlan_tag = PositiveSmallIntegerField(256 vlan_tag = PositiveSmallIntegerField(
252 editable=True, null=True, blank=True, unique=True,257 editable=True, null=True, blank=True, unique=True,
253 help_text="A 12-bit field specifying the VLAN to which the frame "258 help_text="A 12-bit field specifying the VLAN to which the frame "
254259
=== modified file 'src/maasserver/templates/maasserver/network_detail.html'
--- src/maasserver/templates/maasserver/network_detail.html 2014-09-05 01:23:06 +0000
+++ src/maasserver/templates/maasserver/network_detail.html 2014-09-05 07:17:23 +0000
@@ -44,6 +44,10 @@
44 <span>{{ network.default_gateway|default_if_none:"" }}</span>44 <span>{{ network.default_gateway|default_if_none:"" }}</span>
45 </li>45 </li>
46 <li class="block size2">46 <li class="block size2">
47 <h4>DNS servers</h4>
48 <span>{{ network.dns_servers|default_if_none:"" }}</span>
49 </li>
50 <li class="block size2">
47 <h4>Attached nodes</h4>51 <h4>Attached nodes</h4>
48 <span id="nodecount">52 <span id="nodecount">
49 <a title="View nodes attached to network {{ network.name }}"53 <a title="View nodes attached to network {{ network.name }}"
5054
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2014-09-05 06:36:56 +0000
+++ src/maasserver/testing/factory.py 2014-09-05 07:17:23 +0000
@@ -864,7 +864,8 @@
864864
865 def make_Network(self, name=None, network=None, vlan_tag=NO_VALUE,865 def make_Network(self, name=None, network=None, vlan_tag=NO_VALUE,
866 description=None, sortable_name=False,866 description=None, sortable_name=False,
867 disjoint_from=None, default_gateway=None):867 disjoint_from=None, default_gateway=None,
868 dns_servers=None):
868 """Create a `Network`.869 """Create a `Network`.
869870
870 :param network: An `IPNetwork`. If given, the `ip` and `netmask`871 :param network: An `IPNetwork`. If given, the `ip` and `netmask`
@@ -899,6 +900,10 @@
899 network = self.getRandomNetwork(disjoint_from=disjoint_from)900 network = self.getRandomNetwork(disjoint_from=disjoint_from)
900 if default_gateway is None and self.pick_bool():901 if default_gateway is None and self.pick_bool():
901 default_gateway = self.pick_ip_in_network(network)902 default_gateway = self.pick_ip_in_network(network)
903 if dns_servers is None and self.pick_bool():
904 dns_servers = " ".join(
905 self.getRandomIPAddress()
906 for _ in range(random.choice((1, 2))))
902 ip = unicode(network.ip)907 ip = unicode(network.ip)
903 netmask = unicode(network.netmask)908 netmask = unicode(network.netmask)
904 if description is None:909 if description is None:
@@ -907,7 +912,8 @@
907 vlan_tag = self.make_vlan_tag()912 vlan_tag = self.make_vlan_tag()
908 network = Network(913 network = Network(
909 name=name, ip=ip, netmask=netmask, vlan_tag=vlan_tag,914 name=name, ip=ip, netmask=netmask, vlan_tag=vlan_tag,
910 description=description, default_gateway=default_gateway)915 description=description, default_gateway=default_gateway,
916 dns_servers=dns_servers)
911 network.save()917 network.save()
912 return network918 return network
913919
914920
=== modified file 'src/maasserver/views/tests/test_networks.py'
--- src/maasserver/views/tests/test_networks.py 2014-09-05 06:48:30 +0000
+++ src/maasserver/views/tests/test_networks.py 2014-09-05 07:17:23 +0000
@@ -200,6 +200,7 @@
200 'netmask': "%s" % network.netmask,200 'netmask': "%s" % network.netmask,
201 'vlan_tag': factory.make_vlan_tag(),201 'vlan_tag': factory.make_vlan_tag(),
202 'default_gateway': factory.getRandomIPAddress(),202 'default_gateway': factory.getRandomIPAddress(),
203 'dns_servers': factory.getRandomIPAddress(),
203 }204 }
204 response = self.client.post(reverse('network-add'), definition)205 response = self.client.post(reverse('network-add'), definition)
205 self.assertEqual(httplib.FOUND, response.status_code)206 self.assertEqual(httplib.FOUND, response.status_code)
@@ -315,6 +316,7 @@
315 factory.make_mac_address()316 factory.make_mac_address()
316 for _ in range(3)]317 for _ in range(3)]
317 new_gateway = factory.getRandomIPAddress()318 new_gateway = factory.getRandomIPAddress()
319 new_dns_servers = factory.getRandomIPAddress()
318 response = self.client.post(320 response = self.client.post(
319 reverse('network-edit', args=[network.name]),321 reverse('network-edit', args=[network.name]),
320 data={322 data={
@@ -322,6 +324,7 @@
322 'description': new_description,324 'description': new_description,
323 'default_gateway': new_gateway,325 'default_gateway': new_gateway,
324 'mac_addresses': new_macs,326 'mac_addresses': new_macs,
327 'dns_servers': new_dns_servers,
325 })328 })
326 self.assertEqual(329 self.assertEqual(
327 reverse('network-list'), extract_redirect(response),330 reverse('network-list'), extract_redirect(response),