Merge lp:~jtv/maas/zero-vlan-tag-is-not-unique into lp:~maas-committers/maas/trunk
- zero-vlan-tag-is-not-unique
- Merge into trunk
Proposed by
Jeroen T. Vermeulen
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1892 |
Proposed branch: | lp:~jtv/maas/zero-vlan-tag-is-not-unique |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
223 lines (+57/-17) 4 files modified
src/maasserver/migrations/0066_rename_vlan_add_link_node_network.py (+9/-10) src/maasserver/models/network.py (+10/-2) src/maasserver/models/tests/test_network.py (+28/-4) src/maasserver/testing/factory.py (+10/-1) |
To merge this branch: | bzr merge lp:~jtv/maas/zero-vlan-tag-is-not-unique |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+205073@code.launchpad.net |
Commit message
Zero VLAN tag should not be unique.
Description of the change
We have a "unique" constraint on Network.vlan_tag, but this should not apply to the zero tag! Django does not support conditional unique constraints (except in model code, which can't detect paradoxes that may occur at lower database isolation levels) so there was only one thing for it. Allow the field to be null, and normalise zero to None. I thought I was able to avoid this, but forgot about the database constraint. :(
Jeroen
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote : | # |
Thanks. I've updated the migration. I was more cautious about this after the recent incident where a release unbeknownst to us hit juuuust the wrong migration number.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/migrations/0066_rename_vlan_add_link_node_network.py' | |||
2 | --- src/maasserver/migrations/0066_rename_vlan_add_link_node_network.py 2014-02-04 09:08:13 +0000 | |||
3 | +++ src/maasserver/migrations/0066_rename_vlan_add_link_node_network.py 2014-02-06 04:08:29 +0000 | |||
4 | @@ -1,9 +1,8 @@ | |||
5 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
9 | 2 | import datetime | 2 | from south.utils import datetime_utils as datetime |
7 | 3 | |||
8 | 4 | from django.db import models | ||
10 | 5 | from south.db import db | 3 | from south.db import db |
11 | 6 | from south.v2 import SchemaMigration | 4 | from south.v2 import SchemaMigration |
12 | 5 | from django.db import models | ||
13 | 7 | 6 | ||
14 | 8 | 7 | ||
15 | 9 | class Migration(SchemaMigration): | 8 | class Migration(SchemaMigration): |
16 | @@ -18,7 +17,7 @@ | |||
17 | 18 | ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), | 17 | ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), |
18 | 19 | ('ip', self.gf('django.db.models.fields.GenericIPAddressField')(unique=True, max_length=39)), | 18 | ('ip', self.gf('django.db.models.fields.GenericIPAddressField')(unique=True, max_length=39)), |
19 | 20 | ('netmask', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)), | 19 | ('netmask', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)), |
21 | 21 | ('vlan_tag', self.gf('django.db.models.fields.PositiveSmallIntegerField')(unique=True)), | 20 | ('vlan_tag', self.gf('django.db.models.fields.PositiveSmallIntegerField')(unique=True, null=True, blank=True)), |
22 | 22 | ('description', self.gf('django.db.models.fields.CharField')(default=u'', max_length=255, blank=True)), | 21 | ('description', self.gf('django.db.models.fields.CharField')(default=u'', max_length=255, blank=True)), |
23 | 23 | )) | 22 | )) |
24 | 24 | db.send_create_signal(u'maasserver', ['Network']) | 23 | db.send_create_signal(u'maasserver', ['Network']) |
25 | @@ -67,7 +66,7 @@ | |||
26 | 67 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | 66 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
27 | 68 | 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | 67 | 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
28 | 69 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | 68 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
30 | 70 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), | 69 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), |
31 | 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | 70 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
32 | 72 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | 71 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
33 | 73 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | 72 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
34 | @@ -75,7 +74,7 @@ | |||
35 | 75 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | 74 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
36 | 76 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | 75 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
37 | 77 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | 76 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
39 | 78 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), | 77 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), |
40 | 79 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) | 78 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
41 | 80 | }, | 79 | }, |
42 | 81 | u'contenttypes.contenttype': { | 80 | u'contenttypes.contenttype': { |
43 | @@ -131,7 +130,7 @@ | |||
44 | 131 | 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}), | 130 | 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}), |
45 | 132 | 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | 131 | 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
46 | 133 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | 132 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
48 | 134 | 'key': ('django.db.models.fields.CharField', [], {'default': "u'c9791474-8d7b-11e3-8453-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}), | 133 | 'key': ('django.db.models.fields.CharField', [], {'default': "u'1cef8bac-8ee3-11e3-8252-94de80b61466'", 'unique': 'True', 'max_length': '36'}), |
49 | 135 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) | 134 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
50 | 136 | }, | 135 | }, |
51 | 137 | u'maasserver.macaddress': { | 136 | u'maasserver.macaddress': { |
52 | @@ -149,7 +148,7 @@ | |||
53 | 149 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}), | 148 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
54 | 150 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), | 149 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
55 | 151 | 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | 150 | 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
57 | 152 | 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True'}) | 151 | 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) |
58 | 153 | }, | 152 | }, |
59 | 154 | u'maasserver.node': { | 153 | u'maasserver.node': { |
60 | 155 | 'Meta': {'object_name': 'Node'}, | 154 | 'Meta': {'object_name': 'Node'}, |
61 | @@ -172,7 +171,7 @@ | |||
62 | 172 | 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}), | 171 | 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}), |
63 | 173 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), | 172 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
64 | 174 | 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | 173 | 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
66 | 175 | 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-c97b5392-8d7b-11e3-8453-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}), | 174 | 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-1cf030fc-8ee3-11e3-8252-94de80b61466'", 'unique': 'True', 'max_length': '41'}), |
67 | 176 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), | 175 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
68 | 177 | 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}), | 176 | 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}), |
69 | 178 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | 177 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
70 | @@ -258,7 +257,7 @@ | |||
71 | 258 | 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | 257 | 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
72 | 259 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | 258 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
73 | 260 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | 259 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
75 | 261 | 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1391504854L'}), | 260 | 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1391659184L'}), |
76 | 262 | 'token_type': ('django.db.models.fields.IntegerField', [], {}), | 261 | 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
77 | 263 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}), | 262 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}), |
78 | 264 | 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) | 263 | 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
79 | 265 | 264 | ||
80 | === modified file 'src/maasserver/models/network.py' | |||
81 | --- src/maasserver/models/network.py 2014-02-05 14:12:38 +0000 | |||
82 | +++ src/maasserver/models/network.py 2014-02-06 04:08:29 +0000 | |||
83 | @@ -54,7 +54,7 @@ | |||
84 | 54 | help_text="Network mask (e.g. 255.255.255.0).") | 54 | help_text="Network mask (e.g. 255.255.255.0).") |
85 | 55 | 55 | ||
86 | 56 | vlan_tag = PositiveSmallIntegerField( | 56 | vlan_tag = PositiveSmallIntegerField( |
88 | 57 | editable=True, blank=False, unique=True, | 57 | editable=True, null=True, blank=True, unique=True, |
89 | 58 | help_text="A 12-bit field specifying the VLAN to which the frame " | 58 | help_text="A 12-bit field specifying the VLAN to which the frame " |
90 | 59 | "belongs. The hexadecimal values of 0x000 and 0xFFF " | 59 | "belongs. The hexadecimal values of 0x000 and 0xFFF " |
91 | 60 | "are reserved. All other values may be used as VLAN " | 60 | "are reserved. All other values may be used as VLAN " |
92 | @@ -85,13 +85,18 @@ | |||
93 | 85 | 85 | ||
94 | 86 | def __unicode__(self): | 86 | def __unicode__(self): |
95 | 87 | net = unicode(self.get_network().cidr) | 87 | net = unicode(self.get_network().cidr) |
97 | 88 | if self.vlan_tag == 0: | 88 | # A vlan_tag of zero normalises to None. But __unicode__ may be |
98 | 89 | # called while we're not in a clean state, so handle zero as well. | ||
99 | 90 | if self.vlan_tag is None or self.vlan_tag == 0: | ||
100 | 89 | return net | 91 | return net |
101 | 90 | else: | 92 | else: |
102 | 91 | return "%s(tag:%x)" % (net, self.vlan_tag) | 93 | return "%s(tag:%x)" % (net, self.vlan_tag) |
103 | 92 | 94 | ||
104 | 93 | def clean_vlan_tag(self): | 95 | def clean_vlan_tag(self): |
105 | 94 | """Validator for `vlan_tag`.""" | 96 | """Validator for `vlan_tag`.""" |
106 | 97 | if self.vlan_tag is None: | ||
107 | 98 | # Always OK. | ||
108 | 99 | return | ||
109 | 95 | if self.vlan_tag == 0xFFF: | 100 | if self.vlan_tag == 0xFFF: |
110 | 96 | raise ValidationError( | 101 | raise ValidationError( |
111 | 97 | {'tag': ["Cannot use reserved value 0xFFF."]}) | 102 | {'tag': ["Cannot use reserved value 0xFFF."]}) |
112 | @@ -133,6 +138,9 @@ | |||
113 | 133 | raise ValidationError("Invalid network address: %s" % e) | 138 | raise ValidationError("Invalid network address: %s" % e) |
114 | 134 | # Normalise self.ip. This strips off any host bits from the address. | 139 | # Normalise self.ip. This strips off any host bits from the address. |
115 | 135 | self.ip = unicode(net.cidr.ip) | 140 | self.ip = unicode(net.cidr.ip) |
116 | 141 | # Normalise self.vlan_tag. A zero value ("not a VLAN") becomes None. | ||
117 | 142 | if self.vlan_tag == 0: | ||
118 | 143 | self.vlan_tag = None | ||
119 | 136 | 144 | ||
120 | 137 | def validate_unique(self, exclude=None): | 145 | def validate_unique(self, exclude=None): |
121 | 138 | super(Network, self).validate_unique(exclude=exclude) | 146 | super(Network, self).validate_unique(exclude=exclude) |
122 | 139 | 147 | ||
123 | === modified file 'src/maasserver/models/tests/test_network.py' | |||
124 | --- src/maasserver/models/tests/test_network.py 2014-02-05 10:46:49 +0000 | |||
125 | +++ src/maasserver/models/tests/test_network.py 2014-02-06 04:08:29 +0000 | |||
126 | @@ -14,9 +14,11 @@ | |||
127 | 14 | __metaclass__ = type | 14 | __metaclass__ = type |
128 | 15 | __all__ = [] | 15 | __all__ = [] |
129 | 16 | 16 | ||
130 | 17 | from operator import attrgetter | ||
131 | 17 | from random import randint | 18 | from random import randint |
132 | 18 | 19 | ||
133 | 19 | from django.core.exceptions import ValidationError | 20 | from django.core.exceptions import ValidationError |
134 | 21 | from maasserver.models import Network | ||
135 | 20 | from maasserver.testing.factory import factory | 22 | from maasserver.testing.factory import factory |
136 | 21 | from maasserver.testing.testcase import MAASServerTestCase | 23 | from maasserver.testing.testcase import MAASServerTestCase |
137 | 22 | from netaddr import IPNetwork | 24 | from netaddr import IPNetwork |
138 | @@ -52,11 +54,10 @@ | |||
139 | 52 | self.assertEqual('10.9.0.0', network.ip) | 54 | self.assertEqual('10.9.0.0', network.ip) |
140 | 53 | 55 | ||
141 | 54 | def test_vlan_tag_can_be_zero_through_hex_ffe(self): | 56 | def test_vlan_tag_can_be_zero_through_hex_ffe(self): |
143 | 55 | min_tag = 0 | 57 | self.assertIsNone(factory.make_network(vlan_tag=0).vlan_tag) |
144 | 58 | self.assertEqual(1, factory.make_network(vlan_tag=1).vlan_tag) | ||
145 | 56 | max_tag = 0xfff - 1 | 59 | max_tag = 0xfff - 1 |
146 | 57 | self.assertEqual( | 60 | self.assertEqual( |
147 | 58 | min_tag, factory.make_network(vlan_tag=min_tag).vlan_tag) | ||
148 | 59 | self.assertEqual( | ||
149 | 60 | max_tag, factory.make_network(vlan_tag=max_tag).vlan_tag) | 61 | max_tag, factory.make_network(vlan_tag=max_tag).vlan_tag) |
150 | 61 | 62 | ||
151 | 62 | def test_reserved_vlan_tag_does_not_validate(self): | 63 | def test_reserved_vlan_tag_does_not_validate(self): |
152 | @@ -69,6 +70,20 @@ | |||
153 | 69 | self.assertRaises( | 70 | self.assertRaises( |
154 | 70 | ValidationError, factory.make_network, vlan_tag=-1) | 71 | ValidationError, factory.make_network, vlan_tag=-1) |
155 | 71 | 72 | ||
156 | 73 | def test_vlan_tag_normalises_zero_to_None(self): | ||
157 | 74 | self.assertIsNone(factory.make_network(vlan_tag=0).vlan_tag) | ||
158 | 75 | |||
159 | 76 | def test_nonzero_vlan_tag_is_unique(self): | ||
160 | 77 | tag = randint(1, 0xffe) | ||
161 | 78 | factory.make_network(vlan_tag=tag) | ||
162 | 79 | self.assertRaises(ValidationError, factory.make_network, vlan_tag=tag) | ||
163 | 80 | |||
164 | 81 | def test_zero_vlan_tag_is_not_unique(self): | ||
165 | 82 | networks = [factory.make_network(vlan_tag=0) for _ in range(3)] | ||
166 | 83 | self.assertEqual( | ||
167 | 84 | sorted(networks, key=attrgetter('id')), | ||
168 | 85 | list(Network.objects.filter(vlan_tag=None).order_by('id'))) | ||
169 | 86 | |||
170 | 72 | def test_get_network_returns_network(self): | 87 | def test_get_network_returns_network(self): |
171 | 73 | net = factory.getRandomNetwork() | 88 | net = factory.getRandomNetwork() |
172 | 74 | self.assertEqual(net, factory.make_network(network=net).get_network()) | 89 | self.assertEqual(net, factory.make_network(network=net).get_network()) |
173 | @@ -112,11 +127,20 @@ | |||
174 | 112 | network = factory.make_network(network=IPNetwork(cidr), vlan_tag=0) | 127 | network = factory.make_network(network=IPNetwork(cidr), vlan_tag=0) |
175 | 113 | self.assertEqual(cidr, unicode(network)) | 128 | self.assertEqual(cidr, unicode(network)) |
176 | 114 | 129 | ||
178 | 115 | def test_unicode_includes_tag_if_nonzero(self): | 130 | def test_unicode_includes_tag_if_set(self): |
179 | 116 | cidr = '10.9.0.0/16' | 131 | cidr = '10.9.0.0/16' |
180 | 117 | network = factory.make_network(network=IPNetwork(cidr), vlan_tag=0xabc) | 132 | network = factory.make_network(network=IPNetwork(cidr), vlan_tag=0xabc) |
181 | 118 | self.assertEqual("%s(tag:abc)" % cidr, unicode(network)) | 133 | self.assertEqual("%s(tag:abc)" % cidr, unicode(network)) |
182 | 119 | 134 | ||
183 | 135 | def test_unicode_treats_unclean_zero_tag_as_unset(self): | ||
184 | 136 | net = IPNetwork('10.1.1.0/24') | ||
185 | 137 | network = factory.make_network(network=net) | ||
186 | 138 | network.vlan_tag = None | ||
187 | 139 | proper_unicode = unicode(network) | ||
188 | 140 | network.vlan_tag = 0 | ||
189 | 141 | unclean_unicode = unicode(network) | ||
190 | 142 | self.assertEqual(proper_unicode, unclean_unicode) | ||
191 | 143 | |||
192 | 120 | def test_disallows_identical_networks_with_same_netmask(self): | 144 | def test_disallows_identical_networks_with_same_netmask(self): |
193 | 121 | existing_network = factory.make_network() | 145 | existing_network = factory.make_network() |
194 | 122 | self.assertRaises( | 146 | self.assertRaises( |
195 | 123 | 147 | ||
196 | === modified file 'src/maasserver/testing/factory.py' | |||
197 | --- src/maasserver/testing/factory.py 2014-02-04 17:28:30 +0000 | |||
198 | +++ src/maasserver/testing/factory.py 2014-02-06 04:08:29 +0000 | |||
199 | @@ -543,6 +543,9 @@ | |||
200 | 543 | 543 | ||
201 | 544 | :param network: An `IPNetwork`. If given, the `ip` and `netmask` | 544 | :param network: An `IPNetwork`. If given, the `ip` and `netmask` |
202 | 545 | fields will be taken from this. | 545 | fields will be taken from this. |
203 | 546 | :param vlan_tag: A number between 1 and 0xffe inclusive to create a | ||
204 | 547 | VLAN, or 0 to create a non-VLAN network, or None to make a random | ||
205 | 548 | choice. | ||
206 | 546 | """ | 549 | """ |
207 | 547 | if name is None: | 550 | if name is None: |
208 | 548 | name = factory.make_name() | 551 | name = factory.make_name() |
209 | @@ -553,7 +556,13 @@ | |||
210 | 553 | if description is None: | 556 | if description is None: |
211 | 554 | description = self.getRandomString() | 557 | description = self.getRandomString() |
212 | 555 | if vlan_tag is None: | 558 | if vlan_tag is None: |
214 | 556 | vlan_tag = random.randint(0, 0xFFE) | 559 | # Caller wants it random. To avoid hiding once-in-4094 bugs when |
215 | 560 | # it comes out zero, skew the odds in favour of zero: fifty-fifty | ||
216 | 561 | # choice between a zero and a nonzero tag. | ||
217 | 562 | if random.randint(0, 1) == 1: | ||
218 | 563 | vlan_tag = random.randint(1, 0xFFE) | ||
219 | 564 | else: | ||
220 | 565 | vlan_tag = 0 | ||
221 | 557 | network = Network( | 566 | network = Network( |
222 | 558 | name=name, ip=ip, netmask=netmask, vlan_tag=vlan_tag, | 567 | name=name, ip=ip, netmask=netmask, vlan_tag=vlan_tag, |
223 | 559 | description=description) | 568 | description=description) |
Looks great, I especially like the thought put into the random tag in the factory func.
Can I persuade you to go and edit the old migration though please, having multiple migrations for the same new model is ugly, embarrassing and inefficient. It's not been released to trusty yet so it's fine.