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
This commit is contained in:
Brad P. Crochet 2015-02-23 10:03:30 -05:00
parent 10ec89b16e
commit a09d5e54b9
16 changed files with 234 additions and 176 deletions

View File

@ -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

View File

@ -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())

View File

@ -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">

View File

@ -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)

View File

@ -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,
)

View File

@ -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):

View File

@ -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"))

View File

@ -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()

View File

@ -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:

View File

@ -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',
),
])

View File

@ -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:

View File

@ -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>

View File

@ -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>

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends 'infrastructure/base.html' %}
{% load i18n %}
{% block title %}{% trans "Register Nodes" %}{% endblock %}

View File

@ -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)

View File

@ -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',