Skip to content

Commit

Permalink
Disable selection of undersized flavors for image
Browse files Browse the repository at this point in the history
Where an image has minimum requirements for RAM or disk, we should
not allow users to select a flavor that does not meet those
requirements only to have the form fail when submitted.

Change-Id: I00667f4ca30a43771eaf3257fd055d3387687658
Fixes: bug 1116122
  • Loading branch information
Matt Wagner committed Nov 25, 2013
1 parent ed7d1db commit e07e93a
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 0 deletions.
131 changes: 131 additions & 0 deletions horizon/static/horizon/js/horizon.quota.js
Expand Up @@ -75,6 +75,132 @@ horizon.Quota = {
this._attachInputHandlers();
},

/*
Confirm that the specified attribute 'actual' meets
or exceeds the specified value 'minimum'.
*/
belowMinimum: function(minimum, actual) {
return parseInt(minimum, 10) > parseInt(actual, 10);
},

/*
Determines if the selected image meets the requirements of
the selected flavor.
*/
imageFitsFlavor: function(image, flavor) {
if (image == undefined) {
/*
If we don't actually have an image, we don't need to
limit our flavors, so we return true in this case.
*/
return true;
} else {
overDisk = horizon.Quota.belowMinimum(image.min_disk, flavor.disk);
overRAM = horizon.Quota.belowMinimum(image.min_ram, flavor.ram);
return !(overDisk || overRAM);
}
},

/*
Note to the user that some flavors have been disabled.
*/
noteDisabledFlavors: function(allDisabled) {
if ($('#some_flavors_disabled').length == 0) {
message = allDisabled ? horizon.Quota.allFlavorsDisabledMessage :
horizon.Quota.disabledFlavorMessage
$('#id_flavor').parent().append("<span id='some_flavors_disabled'>" +
message + '</span>');
}
},

/*
Re-enables all flavors that may have been disabled, and
clear the message displayed about them being disabled.
*/
resetFlavors: function() {
if ($('#some_flavors_disabled')) {
$('#some_flavors_disabled').remove();
$('#id_flavor option').each(function() {
$(this).attr('disabled', false);
})
}
},

/*
A convenience method to find an image object by its id.
*/
findImageById: function(id) {
_image = undefined;
$.each(horizon.Quota.images, function(i, image){
if(image.id == id) {
_image = image;
}
});
return _image;
},

/*
Return an image Object based on which image ID is selected
*/
getSelectedImage: function() {
selected = $('#id_image_id option:selected').val();
return horizon.Quota.findImageById(selected);
},

/*
Disable any flavors for a given image that do not meet
its minimum RAM or disk requirements.
*/
disableFlavorsForImage: function(image) {
image = horizon.Quota.getSelectedImage();
to_disable = []; // an array of flavor names to disable

horizon.Quota.resetFlavors(); // clear any previous messages

$.each(horizon.Quota.flavors, function(i, flavor) {
if (!horizon.Quota.imageFitsFlavor(image, flavor)) {
to_disable.push(flavor.name);
}
});

flavors = $('#id_flavor option');
// Now, disable anything from above:
$.each(to_disable, function(i, flavor_name) {
flavors.each(function(){
if ($(this).text() == flavor_name) {
$(this).attr('disabled', 'disabled');
}
});
});

// And then, finally, clean up:
if (to_disable.length > 0) {
selected = ($('#id_flavor option').filter(':selected'))[0];
if (to_disable.length < flavors.length && selected.disabled) {
// we need to find a new flavor to select
flavors.each(function(index, element) {
if (!element.disabled) {
$('#id_flavor').val(element.value);
$('#id_flavor').change(); // force elements to update
return false; // break
}
});
}
horizon.Quota.noteDisabledFlavors(to_disable.length == flavors.length);
}
},

/*
Store an array of image objects
*/
initWithImages: function(images, disabledMessage, allDisabledMessage) {
this.images = images;
this.disabledFlavorMessage = disabledMessage;
this.allFlavorsDisabledMessage = allDisabledMessage;
// Check if the image is pre-selected
horizon.Quota.disableFlavorsForImage();
},

/*
Sets up the quota to be used with flavor form selectors, which requires
some different handling of the forms. Also calls init() so that all of the
Expand Down Expand Up @@ -265,8 +391,13 @@ horizon.Quota = {
scope.updateFlavorUsage();
};

var imageChangeCallback = function(event) {
scope.disableFlavorsForImage();
};

$('#id_flavor').on('change', eventCallback);
$('#id_count').on('keyup', eventCallback);
$('#id_image_id').on('change', imageChangeCallback);
}

$(this.user_value_form_inputs).each(function(index, element) {
Expand Down
Expand Up @@ -40,11 +40,16 @@ <h4>{% trans "Project Limits" %}</h4>
</div>

<script type="text/javascript" charset="utf-8">
some_disabled_msg = '{{_("Some flavors not meeting minimum image requirements have been disabled.")}}';
all_disabled_msg = '{{_("No flavors meet minimum criteria for selected image.")}})';

if(typeof horizon.Quota !== 'undefined') {
horizon.Quota.initWithFlavors({{ flavors|safe|default:"{}" }});
horizon.Quota.initWithImages({{ images|safe|default:"{}"}}, some_disabled_msg, all_disabled_msg);
} else {
addHorizonLoadEvent(function() {
horizon.Quota.initWithFlavors({{ flavors|safe|default:"{}" }});
horizon.Quota.initWithImages({{ images|safe|default:"{}"}}, some_disabled_msg, all_disabled_msg);
});
}
</script>
Expand Up @@ -258,6 +258,16 @@ def get_help_text(self):
flavors = json.dumps([f._info for f in
api.nova.flavor_list(self.request)])
extra['flavors'] = flavors
images = utils.get_available_images(self.request,
self.initial['project_id'],
self._images_cache)
if images is not None:
attrs = [{'id': i.id,
'min_disk': getattr(i, 'min_disk', 0),
'min_ram': getattr(i, 'min_ram', 0)}
for i in images]
extra['images'] = json.dumps(attrs)

except Exception:
exceptions.handle(self.request,
_("Unable to retrieve quota information."))
Expand Down

0 comments on commit e07e93a

Please sign in to comment.