launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11905
[Merge] lp:~andreserl/maas/add_ubuntu_releases_lp1013146 into lp:maas
Andres Rodriguez has proposed merging lp:~andreserl/maas/add_ubuntu_releases_lp1013146 into lp:maas.
Requested reviews:
MAAS Maintainers (maas-maintainers)
Related bugs:
Bug #1013146 in MAAS: "MAAS currently only supports Ubuntu version 12.04 to be installed on the nodes."
https://bugs.launchpad.net/maas/+bug/1013146
For more details, see:
https://code.launchpad.net/~andreserl/maas/add_ubuntu_releases_lp1013146/+merge/124092
--
https://code.launchpad.net/~andreserl/maas/add_ubuntu_releases_lp1013146/+merge/124092
Your team MAAS Maintainers is requested to review the proposed merge of lp:~andreserl/maas/add_ubuntu_releases_lp1013146 into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-09-12 14:24:49 +0000
+++ src/maasserver/api.py 2012-09-13 02:52:18 +0000
@@ -1121,8 +1121,11 @@
preseed_url = compose_preseed_url(node)
hostname = node.hostname
- # XXX JeroenVermeulen 2012-08-06 bug=1013146: Stop hard-coding this.
- release = 'precise'
+ if node is None or node.status == NODE_STATUS.COMMISSIONING:
+ release = Config.objects.get_config('default_commissioning_release')
+ else:
+ release = node.get_install_release()
+
purpose = get_boot_purpose(node)
domain = 'local.lan' # TODO: This is probably not enough!
server_address = get_maas_facing_server_address()
=== modified file 'src/maasserver/enum.py'
--- src/maasserver/enum.py 2012-09-10 16:45:38 +0000
+++ src/maasserver/enum.py 2012-09-13 02:52:18 +0000
@@ -22,9 +22,12 @@
'NODE_STATUS_CHOICES',
'NODE_STATUS_CHOICES_DICT',
'PRESEED_TYPE',
+ 'UBUNTU_RELEASE',
+ 'UBUNTU_RELEASE_CHOICES',
]
from collections import OrderedDict
+from commands import getoutput
class NODE_STATUS:
@@ -114,6 +117,32 @@
)
+class UBUNTU_RELEASE:
+ """List of supported ubuntu releases."""
+ default = ''
+
+# Obtain all supported releases and its full name
+release_codenames = getoutput("distro-info --supported").split()
+release_fullnames = getoutput(
+ "distro-info --supported --fullname").split("\n")
+releases = dict(zip(release_codenames, release_fullnames))
+# Create attributes for supported releases starting from `precise`
+for release in releases:
+ if release >= "precise":
+ setattr(UBUNTU_RELEASE, release, release)
+
+# Release name Description for UI Choices
+UBUNTU_RELEASE_CHOICES = ()
+release_names = [attr for attr in dir(UBUNTU_RELEASE())
+ if not callable(attr) and not attr.startswith("__")]
+for codename in release_names:
+ if codename != 'default':
+ UBUNTU_RELEASE_CHOICES = UBUNTU_RELEASE_CHOICES + (
+ (codename, releases[codename]),)
+ else:
+ UBUNTU_RELEASE_CHOICES += ((UBUNTU_RELEASE.default, 'Default Ubuntu release'),)
+
+
class NODE_PERMISSION:
"""Permissions relating to nodes."""
VIEW = 'view_node'
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-09-05 13:30:21 +0000
+++ src/maasserver/forms.py 2012-09-13 02:52:18 +0000
@@ -53,6 +53,8 @@
DNS_DHCP_MANAGEMENT_CHOICES,
NODE_AFTER_COMMISSIONING_ACTION,
NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
+ UBUNTU_RELEASE,
+ UBUNTU_RELEASE_CHOICES,
)
from maasserver.fields import MACAddressFormField
from maasserver.models import (
@@ -90,6 +92,9 @@
INVALID_ARCHITECTURE_MESSAGE = compose_invalid_choice_text(
'architecture', ARCHITECTURE_CHOICES)
+INVALID_UBUNTU_RELEASE_MESSAGE = compose_invalid_choice_text(
+ 'os_release', UBUNTU_RELEASE_CHOICES)
+
class NodeForm(ModelForm):
after_commissioning_action = forms.TypedChoiceField(
@@ -97,6 +102,12 @@
choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES, required=False,
empty_value=NODE_AFTER_COMMISSIONING_ACTION.DEFAULT)
+ os_release = forms.ChoiceField(
+ choices=UBUNTU_RELEASE_CHOICES, required=False,
+ initial=UBUNTU_RELEASE.default,
+ label="Release",
+ error_messages={'invalid_choice': INVALID_UBUNTU_RELEASE_MESSAGE})
+
architecture = forms.ChoiceField(
choices=ARCHITECTURE_CHOICES, required=True,
initial=ARCHITECTURE.i386,
@@ -108,6 +119,7 @@
'hostname',
'after_commissioning_action',
'architecture',
+ 'os_release',
)
@@ -147,6 +159,7 @@
'hostname',
'after_commissioning_action',
'architecture',
+ 'os_release',
'power_type',
'power_parameters',
)
=== added file 'src/maasserver/migrations/0023_node_add_os_release.py'
--- src/maasserver/migrations/0023_node_add_os_release.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0023_node_add_os_release.py 2012-09-13 02:52:18 +0000
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Node.os_release'
+ db.add_column(u'maasserver_node', 'os_release',
+ self.gf('django.db.models.fields.CharField')(default=None, max_length=10, null=True, blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Node.os_release'
+ db.delete_column(u'maasserver_node', 'os_release')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'maasserver.config': {
+ 'Meta': {'object_name': 'Config'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
+ },
+ u'maasserver.dhcplease': {
+ 'Meta': {'object_name': 'DHCPLease'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
+ },
+ u'maasserver.filestorage': {
+ 'Meta': {'object_name': 'FileStorage'},
+ 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
+ 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ u'maasserver.macaddress': {
+ 'Meta': {'object_name': 'MACAddress'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386'", 'max_length': '10'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
+ 'os_release': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-6acfd98e-fceb-11e1-8817-58946bf1d72c'", 'unique': 'True', 'max_length': '41'}),
+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.nodegroup': {
+ 'Meta': {'object_name': 'NodeGroup'},
+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}),
+ 'broadcast_ip': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'dhcp_interfaces': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_range_high': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'ip_range_low': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'router_ip': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'subnet_mask': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
+ 'worker_ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'})
+ },
+ u'maasserver.sshkey': {
+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.TextField', [], {}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ u'maasserver.userprofile': {
+ 'Meta': {'object_name': 'UserProfile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'piston.consumer': {
+ 'Meta': {'object_name': 'Consumer'},
+ 'description': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"})
+ },
+ 'piston.token': {
+ 'Meta': {'object_name': 'Token'},
+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1347462381L'}),
+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
+ }
+ }
+
+ complete_apps = ['maasserver']
=== modified file 'src/maasserver/models/config.py'
--- src/maasserver/models/config.py 2012-09-05 13:30:21 +0000
+++ src/maasserver/models/config.py 2012-09-13 02:52:18 +0000
@@ -26,6 +26,7 @@
)
from django.db.models.signals import post_save
from maasserver import DefaultMeta
+from maasserver.enum import UBUNTU_RELEASE
from maasserver.enum import (
DNS_DHCP_MANAGEMENT,
NODE_AFTER_COMMISSIONING_ACTION,
@@ -53,6 +54,8 @@
'enlistment_domain': b'local',
'dns_dhcp_management': DNS_DHCP_MANAGEMENT.NONE,
## /settings
+ 'default_install_release': UBUNTU_RELEASE.precise,
+ 'default_commissioning_release': UBUNTU_RELEASE.precise,
}
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2012-08-24 10:45:47 +0000
+++ src/maasserver/models/node.py 2012-09-13 02:52:18 +0000
@@ -38,6 +38,8 @@
from maasserver.enum import (
ARCHITECTURE,
ARCHITECTURE_CHOICES,
+ UBUNTU_RELEASE,
+ UBUNTU_RELEASE_CHOICES,
NODE_AFTER_COMMISSIONING_ACTION,
NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
NODE_PERMISSION,
@@ -353,6 +355,10 @@
choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
default=NODE_AFTER_COMMISSIONING_ACTION.DEFAULT)
+ os_release = CharField(
+ max_length=10, choices=UBUNTU_RELEASE_CHOICES, null=True,
+ blank=True, default=None)
+
architecture = CharField(
max_length=10, choices=ARCHITECTURE_CHOICES, blank=False,
default=ARCHITECTURE.i386)
@@ -550,6 +556,13 @@
else:
return None
+ def get_install_release(self):
+ """Return the release to install that node."""
+ if not self.os_release or self.os_release == UBUNTU_RELEASE.default:
+ return Config.objects.get_config('default_install_release')
+ else:
+ return self.os_release
+
def get_effective_power_parameters(self):
"""Return effective power parameters, including any defaults."""
if self.power_parameters:
=== modified file 'src/maasserver/preseed.py'
--- src/maasserver/preseed.py 2012-08-21 20:27:47 +0000
+++ src/maasserver/preseed.py 2012-09-13 02:52:18 +0000
@@ -30,6 +30,7 @@
)
from maasserver.server_address import get_maas_facing_server_host
from maasserver.utils import absolute_reverse
+from maasserver.models import Config
import tempita
@@ -54,8 +55,7 @@
return render_preseed(None, PRESEED_TYPE.ENLIST_USERDATA)
-# XXX: rvb 2012-06-21 bug=1013146: 'precise' is hardcoded here.
-def get_preseed(node, release="precise"):
+def get_preseed(node):
"""Return the preseed for a given node. Depending on the node's status
this will be a commissioning preseed (if the node is commissioning) or the
standard preseed (normal installation preseed).
@@ -69,13 +69,14 @@
"""
if node.status == NODE_STATUS.COMMISSIONING:
return render_preseed(
- node, PRESEED_TYPE.COMMISSIONING, release=release)
+ node, PRESEED_TYPE.COMMISSIONING,
+ release=Config.objects.get_config('default_commissioning_release'))
else:
- return render_preseed(node, PRESEED_TYPE.DEFAULT, release=release)
-
-
-# XXX: rvb 2012-06-14 bug=1013146: 'precise' is hardcoded here.
-def get_preseed_filenames(node, prefix='', release='precise', default=False):
+ return render_preseed(node, PRESEED_TYPE.DEFAULT,
+ release=node.get_install_release())
+
+
+def get_preseed_filenames(node, prefix='', release='', default=False):
"""List possible preseed template filenames for the given node.
:param node: The node to return template preseed filenames for.
@@ -174,8 +175,7 @@
self.name = name
-# XXX: rvb 2012-06-18 bug=1013146: 'precise' is hardcoded here.
-def load_preseed_template(node, prefix, release="precise"):
+def load_preseed_template(node, prefix, release=''):
"""Find and load a `PreseedTemplate` for the given node.
:param node: See `get_preseed_filenames`.
@@ -201,8 +201,7 @@
return get_template(prefix, None, default=True)
-# XXX: rvb 2012-06-19 bug=1013146: 'precise' is hardcoded here.
-def get_preseed_context(node, release="precise"):
+def get_preseed_context(node, release=''):
"""Return the context dictionary to be used to render preseed templates
for this node.
@@ -236,8 +235,7 @@
return context
-# XXX: rvb 2012-06-19 bug=1013146: 'precise' is hardcoded here.
-def render_preseed(node, prefix, release="precise"):
+def render_preseed(node, prefix, release=''):
"""Find and load a `PreseedTemplate` for the given node.
:param node: See `get_preseed_filenames`.
=== modified file 'src/maasserver/templates/maasserver/snippets.html'
--- src/maasserver/templates/maasserver/snippets.html 2012-06-11 12:25:37 +0000
+++ src/maasserver/templates/maasserver/snippets.html 2012-09-13 02:52:18 +0000
@@ -20,6 +20,10 @@
{{ node_form.after_commissioning_action }}
</p>
<p>
+ <label for="id_os_release">Release</label>
+ {{ node_form.os_release }}
+ </p>
+ <p>
<label for="id_architecture">Architecture</label>
{{ node_form.architecture }}
</p>
Follow ups