← Back to team overview

launchpad-reviewers team mailing list archive

[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