summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad P. Crochet <brad@redhat.com>2015-02-23 10:03:30 -0500
committerBrad P. Crochet <brad@redhat.com>2015-03-03 06:45:10 -0500
commita09d5e54b9377b2f6a5481e188f332f9bc504214 (patch)
tree37f48b796909e214212ab838ae0e2101d8ae0b65
parent10ec89b16e3012f50ab6af3db4209c6f4dfe7052 (diff)
downloadtuskar-ui-a09d5e54b9377b2f6a5481e188f332f9bc504214.tar.gz
Remove kernel/image from flavor and put it on node
Starting with Juno, setting the kernel and image id on a flavor is deprecated. This moves that setting from the flavor, and puts it on the ironic node. Change-Id: I1f6072213d01adbc95e9d70d4faf95b376b5a566
-rw-r--r--tuskar_ui/api/node.py14
-rw-r--r--tuskar_ui/infrastructure/flavors/tables.py63
-rw-r--r--tuskar_ui/infrastructure/flavors/templates/flavors/details.html9
-rw-r--r--tuskar_ui/infrastructure/flavors/tests.py44
-rw-r--r--tuskar_ui/infrastructure/flavors/utils.py5
-rw-r--r--tuskar_ui/infrastructure/flavors/views.py8
-rw-r--r--tuskar_ui/infrastructure/flavors/workflows.py36
-rw-r--r--tuskar_ui/infrastructure/nodes/forms.py33
-rw-r--r--tuskar_ui/infrastructure/nodes/tabs.py9
-rw-r--r--tuskar_ui/infrastructure/nodes/tests.py73
-rw-r--r--tuskar_ui/infrastructure/nodes/views.py45
-rw-r--r--tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html8
-rw-r--r--tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html5
-rw-r--r--tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html2
-rw-r--r--tuskar_ui/test/test_data/heat_data.py20
-rw-r--r--tuskar_ui/test/test_data/node_data.py36
16 files changed, 234 insertions, 176 deletions
diff --git a/tuskar_ui/api/node.py b/tuskar_ui/api/node.py
index 9ff88846..53374075 100644
--- a/tuskar_ui/api/node.py
+++ b/tuskar_ui/api/node.py
@@ -100,7 +100,9 @@ class IronicNode(base.APIResourceWrapper):
def create(cls, request, ipmi_address=None, cpu_arch=None, cpus=None,
memory_mb=None, local_gb=None, mac_addresses=[],
ipmi_username=None, ipmi_password=None, ssh_address=None,
- ssh_username=None, ssh_key_contents=None, driver=None):
+ ssh_username=None, ssh_key_contents=None,
+ deployment_kernel=None, deployment_ramdisk=None,
+ driver=None):
"""Create a Node in Ironic."""
if driver == 'pxe_ssh':
driver_info = {
@@ -115,6 +117,10 @@ class IronicNode(base.APIResourceWrapper):
'ipmi_username': ipmi_username,
'ipmi_password': ipmi_password
}
+ driver_info.update(
+ pxe_deploy_kernel=deployment_kernel,
+ pxe_deploy_ramdisk=deployment_ramdisk
+ )
properties = {}
if cpus:
@@ -342,6 +348,7 @@ class BareMetalNode(base.APIResourceWrapper):
def create(cls, request, ipmi_address, cpu_arch, cpus, memory_mb,
local_gb, mac_addresses, ipmi_username=None, ipmi_password=None,
ssh_address=None, ssh_username=None, ssh_key_contents=None,
+ deployment_kernel=None, deployment_ramdisk=None,
driver=None):
"""Create a Nova BareMetalNode."""
node = baremetalclient(request).create(
@@ -567,12 +574,15 @@ class Node(base.APIResourceWrapper):
def create(cls, request, ipmi_address=None, cpu_arch=None, cpus=None,
memory_mb=None, local_gb=None, mac_addresses=[],
ipmi_username=None, ipmi_password=None, ssh_address=None,
- ssh_username=None, ssh_key_contents=None, driver=None):
+ ssh_username=None, ssh_key_contents=None,
+ deployment_kernel=None, deployment_ramdisk=None, driver=None):
return cls(NodeClient(request).node_class.create(
request, ipmi_address, cpu_arch, cpus, memory_mb, local_gb,
mac_addresses, ipmi_username=ipmi_username,
ipmi_password=ipmi_password, ssh_address=ssh_address,
ssh_username=ssh_username, ssh_key_contents=ssh_key_contents,
+ deployment_kernel=deployment_kernel,
+ deployment_ramdisk=deployment_ramdisk,
driver=driver))
@classmethod
diff --git a/tuskar_ui/infrastructure/flavors/tables.py b/tuskar_ui/infrastructure/flavors/tables.py
index 10d74910..68431a5b 100644
--- a/tuskar_ui/infrastructure/flavors/tables.py
+++ b/tuskar_ui/infrastructure/flavors/tables.py
@@ -16,41 +16,11 @@ from django.utils.translation import ugettext_lazy as _
import horizon.exceptions
import horizon.messages
import horizon.tables
-from openstack_dashboard.api import glance
from openstack_dashboard.dashboards.admin.flavors import (
tables as flavor_tables)
from tuskar_ui import api
from tuskar_ui.infrastructure.flavors import utils
-import tuskar_ui.utils.utils
-
-DEFAULT_KERNEL_IMAGE_NAME = 'discovery-kernel'
-DEFAULT_RAMDISK_IMAGE_NAME = 'discovery-ramdisk'
-
-
-def _guess_default_image_ids(request):
- try:
- images = glance.image_list_detailed(request)[0]
- except Exception:
- horizon.exceptions.handle(request,
- _('Unable to retrieve images list.'))
- kernel_images = []
- ramdisk_images = []
- else:
- has_type = tuskar_ui.utils.utils.check_image_type
- kernel_images = [
- image for image in images
- if has_type(image, 'discovery kernel')
- and image.name == DEFAULT_KERNEL_IMAGE_NAME
- ]
- ramdisk_images = [
- image for image in images
- if has_type(image, 'discovery ramdisk')
- and image.name == DEFAULT_RAMDISK_IMAGE_NAME
- ]
- if not kernel_images or not ramdisk_images:
- raise ValueError("No default images")
- return kernel_images[0].id, ramdisk_images[0].id
class CreateFlavor(flavor_tables.CreateFlavor):
@@ -65,33 +35,20 @@ class CreateSuggestedFlavor(horizon.tables.Action):
method = 'POST'
icon = 'plus'
- def create_flavor(self, request, node_id, images):
+ def create_flavor(self, request, node_id):
node = api.node.Node.get(request, node_id)
suggestion = utils.FlavorSuggestion.from_node(node)
- return suggestion.create_flavor(request, *images)
+ return suggestion.create_flavor(request)
def handle(self, data_table, request, node_ids):
- try:
- images = _guess_default_image_ids(request)
- except ValueError:
- horizon.messages.error(request, _(
- "No default images available. "
- "Create images called \"{0}\" and \"{1}\" or "
- "use the \"{2}\" action to choose different image names."
- ).format(
- DEFAULT_KERNEL_IMAGE_NAME,
- DEFAULT_RAMDISK_IMAGE_NAME,
- EditAndCreateSuggestedFlavor.verbose_name,
- ))
- else:
- for node_id in node_ids:
- try:
- self.create_flavor(request, node_id, images)
- except Exception:
- horizon.exceptions.handle(
- request,
- _(u"Unable to create flavor for node %r") % node_id,
- )
+ for node_id in node_ids:
+ try:
+ self.create_flavor(request, node_id)
+ except Exception:
+ horizon.exceptions.handle(
+ request,
+ _(u"Unable to create flavor for node %r") % node_id,
+ )
return django.shortcuts.redirect(request.get_full_path())
diff --git a/tuskar_ui/infrastructure/flavors/templates/flavors/details.html b/tuskar_ui/infrastructure/flavors/templates/flavors/details.html
index 72c39c5d..41d59603 100644
--- a/tuskar_ui/infrastructure/flavors/templates/flavors/details.html
+++ b/tuskar_ui/infrastructure/flavors/templates/flavors/details.html
@@ -21,15 +21,6 @@
<dd>{{ flavor.disk_bytes|filesizeformat|default:"&mdash;" }}</dd>
</dl>
</div>
- <div class="col-md-4">
- <h4>{% trans "Deploy Images" %}</h4>
- <dl class="dl-horizontal dl-horizontal-left">
- <dt>{% trans "Kernel" %}</dt>
- <dd>{{ kernel_image.name|default:"&mdash;" }}</dd>
- <dt>{% trans "Ramdisk" %}</dt>
- <dd>{{ ramdisk_image.name|default:"&mdash;" }}</dd>
- </dl>
- </div>
</div>
<div class="row">
<div class="col-xs-12">
diff --git a/tuskar_ui/infrastructure/flavors/tests.py b/tuskar_ui/infrastructure/flavors/tests.py
index 266e0926..002c7656 100644
--- a/tuskar_ui/infrastructure/flavors/tests.py
+++ b/tuskar_ui/infrastructure/flavors/tests.py
@@ -43,19 +43,14 @@ DETAILS_VIEW = 'horizon:infrastructure:flavors:details'
def _prepare_create():
flavor = TEST_DATA.novaclient_flavors.first()
all_flavors = TEST_DATA.novaclient_flavors.list()
- images = TEST_DATA.glanceclient_images.list()
data = {'name': 'foobar',
'vcpus': 3,
'memory_mb': 1024,
'disk_gb': 40,
- 'arch': 'amd64',
- 'kernel_image_id': images[5].id,
- 'ramdisk_image_id': images[4].id}
+ 'arch': 'amd64'}
with contextlib.nested(
patch('tuskar_ui.api.flavor.Flavor.create',
return_value=flavor),
- patch('openstack_dashboard.api.glance.image_list_detailed',
- return_value=(TEST_DATA.glanceclient_images.list(), False)),
# Inherited code calls this directly
patch('openstack_dashboard.api.nova.flavor_list',
return_value=all_flavors),
@@ -108,26 +103,10 @@ class FlavorsTest(test.BaseAdminViewTests):
self.assertMessageCount(response=res, error=2, warning=0)
def test_create_get(self):
- with patch('openstack_dashboard.api.glance.image_list_detailed',
- return_value=([], False)) as mock:
- res = self.client.get(CREATE_URL)
- self.assertEqual(mock.call_count, 2)
+ res = self.client.get(CREATE_URL)
self.assertTemplateUsed(res, 'infrastructure/flavors/create.html')
- def test_create_get_recoverable_failure(self):
- with patch('openstack_dashboard.api.glance.image_list_detailed',
- side_effect=_raise_nova_client_exception):
- res = self.client.get(CREATE_URL)
- self.assertEqual(
- [(m.message, m.tags) for m in res.context['messages']],
- [
- (u'Unable to retrieve images list.', u'error'),
- ],
- )
- self.assertMessageCount(response=res, error=1, warning=0)
-
def test_create_post_ok(self):
- images = TEST_DATA.glanceclient_images.list()
with _prepare_create() as (create_mock, data):
res = self.client.post(CREATE_URL, data)
self.assertNoFormErrors(res)
@@ -135,8 +114,7 @@ class FlavorsTest(test.BaseAdminViewTests):
request = create_mock.call_args_list[0][0][0]
self.assertListEqual(create_mock.call_args_list, [
call(request, name=u'foobar', memory=1024, vcpus=3, disk=40,
- cpu_arch='amd64', kernel_image_id=images[5].id,
- ramdisk_image_id=images[4].id)
+ cpu_arch='amd64')
])
def test_create_post_name_exists(self):
@@ -158,8 +136,6 @@ class FlavorsTest(test.BaseAdminViewTests):
return_value=[]),
patch('tuskar_ui.api.tuskar.Plan.list',
return_value=[]),
- patch('openstack_dashboard.api.glance.image_list_detailed',
- return_value=([], False)),
patch('openstack_dashboard.api.nova.flavor_list',
return_value=TEST_DATA.novaclient_flavors.list())
):
@@ -187,8 +163,6 @@ class FlavorsTest(test.BaseAdminViewTests):
return_value=[]),
patch('tuskar_ui.api.tuskar.Plan.list',
return_value=[]),
- patch('openstack_dashboard.api.glance.image_list_detailed',
- return_value=([], False)),
patch('openstack_dashboard.api.nova.flavor_list',
return_value=TEST_DATA.novaclient_flavors.list()),
patch('tuskar_ui.api.node.Node.list',
@@ -201,24 +175,20 @@ class FlavorsTest(test.BaseAdminViewTests):
def test_details_no_overcloud(self):
flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first())
- images = TEST_DATA.glanceclient_images.list()[:2]
plan = api.tuskar.Plan(TEST_DATA.tuskarclient_plans.first())
roles = [api.tuskar.Role(role)
for role in self.tuskarclient_roles.list()]
with contextlib.nested(
- patch('openstack_dashboard.api.glance.image_get',
- side_effect=images),
patch('tuskar_ui.api.flavor.Flavor.get',
return_value=flavor),
patch('tuskar_ui.api.tuskar.Plan.get_the_plan',
return_value=plan),
patch('tuskar_ui.api.tuskar.Role.list', return_value=roles),
patch('tuskar_ui.api.tuskar.Role.flavor', return_value=flavor),
- ) as (image_mock, get_mock, plan_mock, roles_mock, role_flavor_mock):
+ ) as (get_mock, plan_mock, roles_mock, role_flavor_mock):
res = self.client.get(urlresolvers.reverse(DETAILS_VIEW,
args=(flavor.id,)))
- self.assertEqual(image_mock.call_count, 1)
self.assertEqual(get_mock.call_count, 1)
self.assertEqual(plan_mock.call_count, 2)
self.assertEqual(roles_mock.call_count, 1)
@@ -227,15 +197,12 @@ class FlavorsTest(test.BaseAdminViewTests):
def test_details(self):
flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first())
- images = TEST_DATA.glanceclient_images.list()[:2]
plan = api.tuskar.Plan(TEST_DATA.tuskarclient_plans.first())
roles = [api.tuskar.Role(role)
for role in self.tuskarclient_roles.list()]
stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first())
with contextlib.nested(
- patch('openstack_dashboard.api.glance.image_get',
- side_effect=images),
patch('tuskar_ui.api.flavor.Flavor.get',
return_value=flavor),
patch('tuskar_ui.api.tuskar.Plan.get_the_plan',
@@ -247,11 +214,10 @@ class FlavorsTest(test.BaseAdminViewTests):
# __name__ is required for horizon.tables
patch('tuskar_ui.api.heat.Stack.resources_count',
return_value=42, __name__='')
- ) as (image_mock, flavor_mock, plan_mock, roles_mock, role_flavor_mock,
+ ) as (flavor_mock, plan_mock, roles_mock, role_flavor_mock,
stack_mock, count_mock):
res = self.client.get(urlresolvers.reverse(DETAILS_VIEW,
args=(flavor.id,)))
- self.assertEqual(image_mock.call_count, 1)
self.assertEqual(flavor_mock.call_count, 1)
self.assertEqual(plan_mock.call_count, 2)
self.assertEqual(roles_mock.call_count, 1)
diff --git a/tuskar_ui/infrastructure/flavors/utils.py b/tuskar_ui/infrastructure/flavors/utils.py
index e84831b2..cf2d63dd 100644
--- a/tuskar_ui/infrastructure/flavors/utils.py
+++ b/tuskar_ui/infrastructure/flavors/utils.py
@@ -110,8 +110,7 @@ class FlavorSuggestion(object):
)
)
- def create_flavor(self, request,
- kernel_image_id=None, ramdisk_image_id=None):
+ def create_flavor(self, request):
return api.flavor.Flavor.create(
request,
name=self.name,
@@ -119,6 +118,4 @@ class FlavorSuggestion(object):
vcpus=self.vcpus,
disk=self.disk,
cpu_arch=self.cpu_arch,
- kernel_image_id=kernel_image_id,
- ramdisk_image_id=ramdisk_image_id,
)
diff --git a/tuskar_ui/infrastructure/flavors/views.py b/tuskar_ui/infrastructure/flavors/views.py
index be31f9b2..b66d4aca 100644
--- a/tuskar_ui/infrastructure/flavors/views.py
+++ b/tuskar_ui/infrastructure/flavors/views.py
@@ -92,14 +92,6 @@ class DetailView(horizon.tables.DataTableView):
kwargs.get('flavor_id'),
_error_redirect=self.error_redirect
)
- context['kernel_image'] = api.node.image_get(
- self.request,
- context['flavor'].kernel_image_id
- )
- context['ramdisk_image'] = api.node.image_get(
- self.request,
- context['flavor'].ramdisk_image_id
- )
return context
def get_data(self):
diff --git a/tuskar_ui/infrastructure/flavors/workflows.py b/tuskar_ui/infrastructure/flavors/workflows.py
index bfdacbb0..6f1e1dee 100644
--- a/tuskar_ui/infrastructure/flavors/workflows.py
+++ b/tuskar_ui/infrastructure/flavors/workflows.py
@@ -16,47 +16,19 @@ from django.forms import fields
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import workflows
-from openstack_dashboard.api import glance
from openstack_dashboard.dashboards.admin.flavors import (
workflows as flavor_workflows)
from tuskar_ui import api
-from tuskar_ui.utils import utils
class CreateFlavorAction(flavor_workflows.CreateFlavorInfoAction):
arch = fields.ChoiceField(choices=(('i386', 'i386'), ('amd64', 'amd64'),
('x86_64', 'x86_64')),
label=_("Architecture"))
- kernel_image_id = fields.ChoiceField(choices=(),
- label=_("Deploy Kernel Image"))
- ramdisk_image_id = fields.ChoiceField(choices=(),
- label=_("Deploy Ramdisk Image"))
def __init__(self, *args, **kwrds):
super(CreateFlavorAction, self).__init__(*args, **kwrds)
- try:
- kernel_images = glance.image_list_detailed(
- self.request,
- )[0]
- kernel_images = [image for image in kernel_images
- if utils.check_image_type(image,
- 'discovery kernel')]
- ramdisk_images = glance.image_list_detailed(
- self.request,
- )[0]
- ramdisk_images = [image for image in ramdisk_images
- if utils.check_image_type(image,
- 'discovery ramdisk')]
- except Exception:
- exceptions.handle(self.request,
- _('Unable to retrieve images list.'))
- kernel_images = []
- ramdisk_images = []
- self.fields['kernel_image_id'].choices = [(img.id, img.name)
- for img in kernel_images]
- self.fields['ramdisk_image_id'].choices = [(img.id, img.name)
- for img in ramdisk_images]
# Delete what is not applicable to hardware
del self.fields['eph_gb']
del self.fields['swap_mb']
@@ -79,9 +51,7 @@ class CreateFlavorStep(workflows.Step):
"vcpus",
"memory_mb",
"disk_gb",
- "arch",
- "kernel_image_id",
- "ramdisk_image_id")
+ "arch")
class CreateFlavor(flavor_workflows.CreateFlavor):
@@ -101,9 +71,7 @@ class CreateFlavor(flavor_workflows.CreateFlavor):
memory=data['memory_mb'],
vcpus=data['vcpus'],
disk=data['disk_gb'],
- cpu_arch=data['arch'],
- kernel_image_id=data['kernel_image_id'],
- ramdisk_image_id=data['ramdisk_image_id']
+ cpu_arch=data['arch']
)
except Exception:
exceptions.handle(request, _("Unable to create flavor"))
diff --git a/tuskar_ui/infrastructure/nodes/forms.py b/tuskar_ui/infrastructure/nodes/forms.py
index c4f721ef..12d365cd 100644
--- a/tuskar_ui/infrastructure/nodes/forms.py
+++ b/tuskar_ui/infrastructure/nodes/forms.py
@@ -23,6 +23,9 @@ from tuskar_ui import api
import tuskar_ui.forms
+DEFAULT_KERNEL_IMAGE_NAME = 'bm-deploy-kernel'
+DEFAULT_RAMDISK_IMAGE_NAME = 'bm-deploy-ramdisk'
+
CPU_ARCH_CHOICES = [
('', _("unspecified")),
('amd64', _("amd64")),
@@ -37,7 +40,10 @@ DRIVER_CHOICES = [
def get_driver_info_dict(data):
driver = data['driver']
- driver_dict = {'driver': driver}
+ driver_dict = {'driver': driver,
+ 'deployment_kernel': data['deployment_kernel'],
+ 'deployment_ramdisk': data['deployment_ramdisk'],
+ }
if driver == 'pxe_ipmitool':
driver_dict.update(
ipmi_address=data['ipmi_address'],
@@ -203,6 +209,18 @@ class NodeForm(django.forms.Form):
widget=tuskar_ui.forms.NumberInput(
attrs={'placeholder': _('unspecified')}),
)
+ deployment_kernel = django.forms.ChoiceField(
+ label=_("Kernel"),
+ required=False,
+ choices=[],
+ widget=django.forms.Select(),
+ )
+ deployment_ramdisk = django.forms.ChoiceField(
+ label=_("Ramdisk"),
+ required=False,
+ choices=[],
+ widget=django.forms.Select(),
+ )
def get_name(self):
try:
@@ -244,6 +262,19 @@ class NodeForm(django.forms.Form):
class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset):
+ def __init__(self, *args, **kwargs):
+ self.kernel_images = kwargs.pop('kernel_images')
+ self.ramdisk_images = kwargs.pop('ramdisk_images')
+ super(BaseNodeFormset, self).__init__(*args, **kwargs)
+
+ def add_fields(self, form, index):
+ deployment_kernel_choices = [(kernel.id, kernel.name)
+ for kernel in self.kernel_images]
+ deployment_ramdisk_choices = [(ramdisk.id, ramdisk.name)
+ for ramdisk in self.ramdisk_images]
+ form.fields['deployment_kernel'].choices = deployment_kernel_choices
+ form.fields['deployment_ramdisk'].choices = deployment_ramdisk_choices
+
def clean(self):
all_macs = api.node.Node.get_all_mac_addresses(self.request)
bad_macs = set()
diff --git a/tuskar_ui/infrastructure/nodes/tabs.py b/tuskar_ui/infrastructure/nodes/tabs.py
index 1931751d..aa085ce6 100644
--- a/tuskar_ui/infrastructure/nodes/tabs.py
+++ b/tuskar_ui/infrastructure/nodes/tabs.py
@@ -307,6 +307,15 @@ class DetailOverviewTab(tabs.Tab):
context['role'] = resource.role
context['stack'] = resource.stack
+ context['kernel_image'] = api.node.image_get(
+ request,
+ node.driver_info['pxe_deploy_kernel']
+ )
+ context['ramdisk_image'] = api.node.image_get(
+ request,
+ node.driver_info['pxe_deploy_ramdisk']
+ )
+
if node.instance_uuid:
if api_base.is_service_enabled(self.request, 'metering'):
# Meter configuration in the following format:
diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py
index b8bed858..ee3a9dbb 100644
--- a/tuskar_ui/infrastructure/nodes/tests.py
+++ b/tuskar_ui/infrastructure/nodes/tests.py
@@ -19,6 +19,7 @@ from ceilometerclient.v2 import client as ceilometer_client
from django.core import urlresolvers
from horizon import exceptions as horizon_exceptions
from mock import patch, call, ANY # noqa
+from novaclient import exceptions as nova_exceptions
from openstack_dashboard.test.test_data import utils
from tuskar_ui import api
@@ -40,6 +41,10 @@ heat_data.data(TEST_DATA)
tuskar_data.data(TEST_DATA)
+def _raise_nova_client_exception(*args, **kwargs):
+ raise nova_exceptions.ClientException("Boom!")
+
+
class NodesTests(test.BaseAdminViewTests):
@handle_errors("Error!", [])
def _raise_tuskar_exception(self, request, *args, **kwargs):
@@ -127,13 +132,17 @@ class NodesTests(test.BaseAdminViewTests):
self._test_index_tab_list_exception('maintenance')
def test_register_get(self):
- res = self.client.get(REGISTER_URL)
+ with patch('openstack_dashboard.api.glance.image_list_detailed',
+ return_value=([], False)) as mock:
+ res = self.client.get(REGISTER_URL)
+ self.assertEqual(mock.call_count, 2)
self.assertTemplateUsed(
res, 'infrastructure/nodes/register.html')
def test_register_post(self):
node = TEST_DATA.ironicclient_nodes.first
nodes = self._all_mocked_nodes()
+ images = self.glanceclient_images.list()
data = {
'register_nodes-TOTAL_FORMS': 2,
'register_nodes-INITIAL_FORMS': 1,
@@ -148,6 +157,8 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-0-cpus': '1',
'register_nodes-0-memory_mb': '2',
'register_nodes-0-local_gb': '3',
+ 'register_nodes-0-deployment_kernel': images[6].id,
+ 'register_nodes-0-deployment_ramdisk': images[7].id,
'register_nodes-1-driver': 'pxe_ipmitool',
'register_nodes-1-ipmi_address': '127.0.0.2',
@@ -156,12 +167,18 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-1-cpus': '4',
'register_nodes-1-memory_mb': '5',
'register_nodes-1-local_gb': '6',
+ 'register_nodes-1-deployment_kernel': images[6].id,
+ 'register_nodes-1-deployment_ramdisk': images[7].id,
}
- with patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['create', 'get_all_mac_addresses'],
- 'create.return_value': node,
- 'get_all_mac_addresses.return_value': set(nodes),
- }) as Node:
+ with contextlib.nested(
+ patch('tuskar_ui.api.node.Node', **{
+ 'spec_set': ['create', 'get_all_mac_addresses'],
+ 'create.return_value': node,
+ 'get_all_mac_addresses.return_value': set(nodes),
+ }),
+ patch('openstack_dashboard.api.glance.image_list_detailed',
+ return_value=[images, False, False]),
+ ) as (Node, mock_glance_images):
res = self.client.post(REGISTER_URL, data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@@ -177,6 +194,8 @@ class NodesTests(test.BaseAdminViewTests):
ipmi_username=u'username',
ipmi_password=u'password',
driver='pxe_ipmitool',
+ deployment_kernel=images[6].id,
+ deployment_ramdisk=images[7].id,
),
call(
ANY,
@@ -189,11 +208,14 @@ class NodesTests(test.BaseAdminViewTests):
ipmi_username=None,
ipmi_password=None,
driver='pxe_ipmitool',
+ deployment_kernel=images[6].id,
+ deployment_ramdisk=images[7].id,
),
])
def test_register_post_exception(self):
nodes = self._all_mocked_nodes()
+ images = self.glanceclient_images.list()
data = {
'register_nodes-TOTAL_FORMS': 2,
'register_nodes-INITIAL_FORMS': 1,
@@ -208,6 +230,8 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-0-cpus': '1',
'register_nodes-0-memory_mb': '2',
'register_nodes-0-local_gb': '3',
+ 'register_nodes-0-deployment_kernel': images[6].id,
+ 'register_nodes-0-deployment_ramdisk': images[7].id,
'register_nodes-1-driver': 'pxe_ipmitool',
'register_nodes-1-ipmi_address': '127.0.0.2',
@@ -216,12 +240,18 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-1-cpus': '4',
'register_nodes-1-memory_mb': '5',
'register_nodes-1-local_gb': '6',
+ 'register_nodes-1-deployment_kernel': images[6].id,
+ 'register_nodes-1-deployment_ramdisk': images[7].id,
}
- with patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['create', 'get_all_mac_addresses'],
- 'create.side_effect': self.exceptions.tuskar,
- 'get_all_mac_addresses.return_value': set(nodes),
- }) as Node:
+ with contextlib.nested(
+ patch('tuskar_ui.api.node.Node', **{
+ 'spec_set': ['create', 'get_all_mac_addresses'],
+ 'create.side_effect': self.exceptions.tuskar,
+ 'get_all_mac_addresses.return_value': set(nodes),
+ }),
+ patch('openstack_dashboard.api.glance.image_list_detailed',
+ return_value=[images, False, False]),
+ ) as (Node, mock_glance_images):
res = self.client.post(REGISTER_URL, data)
self.assertEqual(res.status_code, 200)
self.assertListEqual(Node.create.call_args_list, [
@@ -236,6 +266,8 @@ class NodesTests(test.BaseAdminViewTests):
ipmi_username=u'username',
ipmi_password=u'password',
driver='pxe_ipmitool',
+ deployment_kernel=images[6].id,
+ deployment_ramdisk=images[7].id,
),
call(
ANY,
@@ -248,6 +280,8 @@ class NodesTests(test.BaseAdminViewTests):
ipmi_username=None,
ipmi_password=None,
driver='pxe_ipmitool',
+ deployment_kernel=images[6].id,
+ deployment_ramdisk=images[7].id,
),
])
self.assertTemplateUsed(
@@ -255,6 +289,7 @@ class NodesTests(test.BaseAdminViewTests):
def test_node_detail(self):
node = api.node.Node(self.ironicclient_nodes.list()[0])
+ image = self.glanceclient_images.first()
with contextlib.nested(
patch('tuskar_ui.api.node.Node', **{
@@ -266,7 +301,9 @@ class NodesTests(test.BaseAdminViewTests):
'get_by_node.side_effect': lambda *args, **kwargs: {}[None],
# Raises LookupError
}),
- ) as (mock_node, mock_heat):
+ patch('openstack_dashboard.api.glance.image_get',
+ return_value=image),
+ ) as (mock_node, mock_heat, mock_glance):
res = self.client.get(
urlresolvers.reverse(DETAIL_VIEW, args=(node.uuid,))
)
@@ -421,6 +458,8 @@ class NodesTests(test.BaseAdminViewTests):
'ipmi_address': '127.0.0.1',
'ipmi_username': 'root',
'ipmi_password': 'P@55W0rd',
+ 'deployment_kernel': '7',
+ 'deployment_ramdisk': '8',
}
ret = forms.get_driver_info_dict(data)
self.assertEqual(ret, {
@@ -428,12 +467,16 @@ class NodesTests(test.BaseAdminViewTests):
'ipmi_address': '127.0.0.1',
'ipmi_username': 'root',
'ipmi_password': 'P@55W0rd',
+ 'deployment_kernel': '7',
+ 'deployment_ramdisk': '8',
})
data = {
'driver': 'pxe_ssh',
'ssh_address': '127.0.0.1',
'ssh_username': 'root',
'ssh_key_contents': 'P@55W0rd',
+ 'deployment_kernel': '7',
+ 'deployment_ramdisk': '8',
}
ret = forms.get_driver_info_dict(data)
self.assertEqual(ret, {
@@ -441,6 +484,8 @@ class NodesTests(test.BaseAdminViewTests):
'ssh_address': '127.0.0.1',
'ssh_username': 'root',
'ssh_key_contents': 'P@55W0rd',
+ 'deployment_kernel': '7',
+ 'deployment_ramdisk': '8',
})
def test_create_node(self):
@@ -454,6 +499,8 @@ class NodesTests(test.BaseAdminViewTests):
'ipmi_username': 'username',
'ipmi_password': 'password',
'driver': 'pxe_ipmitool',
+ 'deployment_kernel': '7',
+ 'deployment_ramdisk': '8',
}
with patch('tuskar_ui.api.node.Node', **{
'spec_set': ['create', 'set_maintenance', 'discover'],
@@ -472,5 +519,7 @@ class NodesTests(test.BaseAdminViewTests):
ipmi_username=u'username',
ipmi_password=u'password',
driver='pxe_ipmitool',
+ deployment_kernel='7',
+ deployment_ramdisk='8',
),
])
diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py
index c962ed73..48221ac2 100644
--- a/tuskar_ui/infrastructure/nodes/views.py
+++ b/tuskar_ui/infrastructure/nodes/views.py
@@ -19,9 +19,11 @@ import django.forms
import django.http
from django.utils.translation import ugettext_lazy as _
from django.views.generic import base
+from horizon import exceptions
from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs
from horizon.utils import memoized
+from openstack_dashboard.api import glance
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import forms
@@ -29,6 +31,36 @@ from tuskar_ui.infrastructure.nodes import tables
from tuskar_ui.infrastructure.nodes import tabs
import tuskar_ui.infrastructure.views as infrastructure_views
from tuskar_ui.utils import metering as metering_utils
+from tuskar_ui.utils import utils
+
+
+def get_kernel_images(request):
+ try:
+ kernel_images = glance.image_list_detailed(
+ request,
+ )[0]
+ kernel_images = [image for image in kernel_images
+ if utils.check_image_type(image, 'deploy kernel')]
+ except Exception:
+ exceptions.handle(request,
+ _('Unable to retrieve kernel image list.'))
+ kernel_images = []
+ return kernel_images
+
+
+def get_ramdisk_images(request):
+ try:
+ ramdisk_images = glance.image_list_detailed(
+ request,
+ )[0]
+ ramdisk_images = [image for image in ramdisk_images
+ if utils.check_image_type(
+ image, 'deploy ramdisk')]
+ except Exception:
+ exceptions.handle(request,
+ _('Unable to retrieve ramdisk image list.'))
+ ramdisk_images = []
+ return ramdisk_images
class IndexView(infrastructure_views.ItemCountMixin,
@@ -78,7 +110,9 @@ class RegisterView(horizon_forms.ModalFormView):
formset = forms.RegisterNodeFormset(
self.request.POST,
prefix=self.form_prefix,
- request=self.request
+ request=self.request,
+ kernel_images=get_kernel_images(self.request),
+ ramdisk_images=get_ramdisk_images(self.request)
)
if formset.is_valid():
initial += formset.cleaned_data
@@ -86,7 +120,9 @@ class RegisterView(horizon_forms.ModalFormView):
None,
initial=initial,
prefix=self.form_prefix,
- request=self.request
+ request=self.request,
+ kernel_images=get_kernel_images(self.request),
+ ramdisk_images=get_ramdisk_images(self.request)
)
formset.extra = 0
return formset
@@ -94,7 +130,9 @@ class RegisterView(horizon_forms.ModalFormView):
self.request.POST or None,
initial=initial,
prefix=self.form_prefix,
- request=self.request
+ request=self.request,
+ kernel_images=get_kernel_images(self.request),
+ ramdisk_images=get_ramdisk_images(self.request)
)
def get_context_data(self, **kwargs):
@@ -109,6 +147,7 @@ class DetailView(horizon_tabs.TabView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
+
node = self.get_data()
if node.maintenance:
diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html
index 34a0e1e9..e000da05 100644
--- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html
+++ b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html
@@ -85,6 +85,14 @@
<dd>{{ node.instance_uuid|default:"&mdash;" }}</dd>
</dl>
+ <h3>{% trans "Deployment Images" %}</h3>
+ <dl class="dl-horizontal dl-horizontal-left">
+ <dt>{% trans "Kernel" %}</dt>
+ <dd>{{ kernel_image.name|default:"&mdash;" }}</dd>
+ <dt>{% trans "Ramdisk" %}</dt>
+ <dd>{{ ramdisk_image.name|default:"&mdash;" }}</dd>
+ </dl>
+
</div>
<div class="col-lg-6 col-xs-12">
<h3>{% trans "Performance & Metrics" %}</h3>
diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html
index 270b4136..c828125d 100644
--- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html
+++ b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html
@@ -27,6 +27,11 @@
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory_mb extra_text=_('MB') %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.local_gb extra_text=_('GB') %}
</div>
+ <div class="param-section">
+ <h5>{% trans "Deployment Images" %}</h5>
+ {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.deployment_kernel %}
+ {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.deployment_ramdisk %}
+ </div>
</fieldset></div>
</div>
diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html
index e83d3e7a..3e9cb1f5 100644
--- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html
+++ b/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends 'infrastructure/base.html' %}
{% load i18n %}
{% block title %}{% trans "Register Nodes" %}{% endblock %}
diff --git a/tuskar_ui/test/test_data/heat_data.py b/tuskar_ui/test/test_data/heat_data.py
index a8aa7135..6af939b0 100644
--- a/tuskar_ui/test/test_data/heat_data.py
+++ b/tuskar_ui/test/test_data/heat_data.py
@@ -254,5 +254,23 @@ def data(TEST):
'properties': {
'type': 'discovery kernel'
}})
+ image_7 = images.Image(
+ images.ImageManager(None),
+ {'id': '7',
+ 'name': 'Baremetal Deployment Kernel',
+ 'is_public': True,
+ 'protected': False,
+ 'properties': {
+ 'type': 'deploy kernel'
+ }})
+ image_8 = images.Image(
+ images.ImageManager(None),
+ {'id': '8',
+ 'name': 'Baremetal Deployment Ramdisk',
+ 'is_public': True,
+ 'protected': False,
+ 'properties': {
+ 'type': 'deploy ramdisk'
+ }})
TEST.glanceclient_images.add(image_1, image_2, image_3, image_4,
- image_5, image_6)
+ image_5, image_6, image_7, image_8)
diff --git a/tuskar_ui/test/test_data/node_data.py b/tuskar_ui/test/test_data/node_data.py
index e3f6fb51..dcbdec17 100644
--- a/tuskar_ui/test/test_data/node_data.py
+++ b/tuskar_ui/test/test_data/node_data.py
@@ -124,7 +124,9 @@ def data(TEST):
'ipmi_address': '1.1.1.1',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.2'
+ 'ip_address': '1.2.2.2',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -149,7 +151,9 @@ def data(TEST):
'ipmi_address': '2.2.2.2',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.3'
+ 'ip_address': '1.2.2.3',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '16',
@@ -174,7 +178,9 @@ def data(TEST):
'ipmi_address': '3.3.3.3',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.4'
+ 'ip_address': '1.2.2.4',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '32',
@@ -199,7 +205,9 @@ def data(TEST):
'ipmi_address': '4.4.4.4',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.5'
+ 'ip_address': '1.2.2.5',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -224,7 +232,9 @@ def data(TEST):
'ipmi_address': '5.5.5.5',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.6'
+ 'ip_address': '1.2.2.6',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -249,7 +259,9 @@ def data(TEST):
'ipmi_address': '5.5.5.5',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.6'
+ 'ip_address': '1.2.2.6',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -274,7 +286,9 @@ def data(TEST):
'ipmi_address': '7.7.7.7',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.7'
+ 'ip_address': '1.2.2.7',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -299,7 +313,9 @@ def data(TEST):
'ipmi_address': '8.8.8.8',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.8'
+ 'ip_address': '1.2.2.8',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '8',
@@ -324,7 +340,9 @@ def data(TEST):
'ipmi_address': '9.9.9.9',
'ipmi_username': 'admin',
'ipmi_password': 'password',
- 'ip_address': '1.2.2.9'
+ 'ip_address': '1.2.2.9',
+ 'pxe_deploy_kernel': 'deploy-kernel-uuid',
+ 'pxe_deploy_ramdisk': 'deploy-ramdisk-uuid',
},
'properties': {
'cpus': '16',