Remove Horizon plugin(moved to a different repo https://github.com/keedya/shovel-horizon-plugin
@ -1,7 +0,0 @@
|
|||||||
# RackHD plugin for OpenStack Horizon dashboard
|
|
||||||
|
|
||||||
- [Configure Openstack to Boot Baremetal nodes](https://github.com/keedya/Shovel-horizon/blob/master/Horizon/setup_openstack.md)
|
|
||||||
- git clone https://github.com/keedya/Shovel-horizon.git
|
|
||||||
- cd Shovel-horizon/Horizon
|
|
||||||
- sudo ./install.sh --url 'Shovel Url' --location 'Horizon Path'
|
|
||||||
- sudo service apache2 restart
|
|
@ -1,11 +0,0 @@
|
|||||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
|
||||||
PANEL = 'rackhd'
|
|
||||||
# The slug of the dashboard the PANEL associated with. Required.
|
|
||||||
PANEL_DASHBOARD = 'admin'
|
|
||||||
|
|
||||||
# The slug of the panel group the PANEL is associated with.
|
|
||||||
PANEL_GROUP = 'admin'
|
|
||||||
|
|
||||||
# Python panel class of the PANEL to be added.
|
|
||||||
ADD_PANEL = ('openstack_dashboard.dashboards.admin.'
|
|
||||||
'rackhd.panel.Rackhd')
|
|
@ -1,33 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
TEMP=`getopt -o u:l: --long url: --long location: -- "$@"`
|
|
||||||
|
|
||||||
if [ $? != 0 ] ; then echo "Exit" ; exit 1 ; fi
|
|
||||||
|
|
||||||
eval set -- "$TEMP"
|
|
||||||
SHOVEL_URL=${SHOVEL_URL-}
|
|
||||||
FILE_LOC=${FILE_LOC-}
|
|
||||||
|
|
||||||
while true ; do
|
|
||||||
case "$1" in
|
|
||||||
-u | --url) SHOVEL_URL=$2 ;shift 2 ;;
|
|
||||||
-l | --location) FILE_LOC=$2;shift 2 ;;
|
|
||||||
--) shift; break ;;
|
|
||||||
*) echo "Internal error!" ; exit 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
echo "get shovel url: " $SHOVEL_URL
|
|
||||||
echo "get file location: " $FILE_LOC
|
|
||||||
if [ -z "$SHOVEL_URL" -o -z "$FILE_LOC" ]
|
|
||||||
then
|
|
||||||
echo "You must specify Shovel service URL(http://<ipaddr>)using --url <shovel-url>"
|
|
||||||
echo "and horizon location using --location <horizon path>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#replace in shovel.py SHOVEL_URL with the new url value
|
|
||||||
sed -i "s|.*URI = .*|URI = \"$SHOVEL_URL\" + SHOVEL_BASE_API|g" rackhd/shovel.py
|
|
||||||
#copy rackhd to horizon admin dashboard
|
|
||||||
cp -r rackhd $FILE_LOC/openstack_dashboard/dashboards/admin
|
|
||||||
#copy _50_admin_rackhd_panels.py to dashboard enabled
|
|
||||||
cp _50_admin_rackhd_panels.py $FILE_LOC/openstack_dashboard/enabled
|
|
@ -1,152 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd import shovel
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class RegisterForm(forms.SelfHandlingForm):
|
|
||||||
uuid = forms.Field(label=_('Node ID'), widget=forms.TextInput(attrs={'readonly':'readonly'}))
|
|
||||||
name = forms.CharField(max_length=255, label=_('Name'), required=True)
|
|
||||||
driver = forms.ChoiceField(label=_('Driver'), required=True,
|
|
||||||
widget=forms.Select(attrs={'class': 'switchable','data-slug': 'driver'}))
|
|
||||||
kernel = forms.ChoiceField(label=_('Deploy Kernel'), required=True,
|
|
||||||
widget=forms.Select(attrs={'class': 'switchable'}))
|
|
||||||
ramdisk = forms.ChoiceField(label=_('Deploy RAM Disk'), required=True,
|
|
||||||
widget=forms.Select(attrs={'class': 'switchable'}))
|
|
||||||
port = forms.ChoiceField(label=_('Mac address'), required=True,
|
|
||||||
widget=forms.Select(attrs={'class': 'switchable'}))
|
|
||||||
ipmihost = forms.CharField(required=False,
|
|
||||||
widget=forms.TextInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ipmitool': _('IPMI Host Address')}))
|
|
||||||
ipmiuser = forms.CharField(required=False,
|
|
||||||
widget=forms.TextInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ipmitool': _('IPMI Username')}))
|
|
||||||
ipmipass = forms.CharField(required=False,
|
|
||||||
widget=forms.PasswordInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ipmitool': _('IPMI Password')}))
|
|
||||||
|
|
||||||
sshhost = forms.CharField(required=False,
|
|
||||||
widget=forms.TextInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ssh': _('SSH Host Address')}))
|
|
||||||
sshuser = forms.CharField(required=False,
|
|
||||||
widget=forms.TextInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ssh': _('SSH Username')}))
|
|
||||||
sshpass = forms.CharField(required=False,
|
|
||||||
widget=forms.PasswordInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ssh': _('SSH Password')}))
|
|
||||||
sshport = forms.CharField(required=False,
|
|
||||||
widget=forms.TextInput(attrs={'class': 'switched','data-switch-on': 'driver','data-driver-pxe_ssh': _('SSH Port')}))
|
|
||||||
|
|
||||||
failovernode = forms.ChoiceField(label=_("Failover Node"), required=False)
|
|
||||||
enfailover = forms.BooleanField(label=_("Enable Failover"), initial=False, required=False)
|
|
||||||
eventre = forms.CharField(max_length=255, label=_('Event Trigger (regex)'), required=False, initial='')
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(RegisterForm, self).__init__(request, *args, **kwargs)
|
|
||||||
self._node = kwargs['initial'].get('node', None)
|
|
||||||
if self._node is not None:
|
|
||||||
self._drivers = kwargs['initial']['drivers']
|
|
||||||
self._ramdisk = kwargs['initial']['images']
|
|
||||||
self._macaddress = kwargs['initial']['ports']
|
|
||||||
self.fields['name'].initial = shovel.get_catalog_data_by_source(self._node['id'],'dmi')['System Information']['Product Name']
|
|
||||||
self.fields['uuid'].initial = self._node['id']
|
|
||||||
self.fields['driver'].choices = [ (elem,_(elem)) for elem in self._drivers ]
|
|
||||||
self.fields['kernel'].choices = [ (elem,_(elem)) for elem in self._ramdisk ]
|
|
||||||
self.fields['ramdisk'].choices = [ (elem,_(elem)) for elem in self._ramdisk ]
|
|
||||||
self.fields['port'].choices = [ (elem,_(elem)) for elem in self._macaddress]
|
|
||||||
# BMC information initials
|
|
||||||
bmc = shovel.get_catalog_data_by_source(self._node['id'], 'bmc')
|
|
||||||
bmcuser = shovel.get_catalog_data_by_source(self._node['id'], 'ipmi-user-list-1')
|
|
||||||
self.fields['ipmihost'].initial = bmc['IP Address']
|
|
||||||
self.fields['ipmiuser'].initial = bmcuser['2']['']
|
|
||||||
|
|
||||||
# Host network initials
|
|
||||||
host = shovel.get_catalog_data_by_source(self._node['id'], 'ohai')
|
|
||||||
self.fields['sshuser'].initial = host['current_user']
|
|
||||||
self.fields['sshhost'].initial = host['ipaddress']
|
|
||||||
self.fields['sshport'].initial = '22'
|
|
||||||
|
|
||||||
# Failover node initials
|
|
||||||
nodes = shovel.request_nodes_get()
|
|
||||||
self.fields['failovernode'].choices = [ (n['id'],_(n['id'])) for n in nodes if n['id'] != self._node['id'] ]
|
|
||||||
else:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
msg = 'Invalid node ID specified'
|
|
||||||
messages.error(request, _(msg))
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def _add_new_node(self, request, data):
|
|
||||||
try:
|
|
||||||
# create node with shovel
|
|
||||||
#replace kernal and ramdisk with image id
|
|
||||||
list_images = shovel.get_images_list()['images']
|
|
||||||
for elem in list_images:
|
|
||||||
if data['ramdisk'] == elem['name']:
|
|
||||||
data['ramdisk'] = elem['id']
|
|
||||||
if data['kernel'] == elem['name']:
|
|
||||||
data['kernel'] = elem['id']
|
|
||||||
result = shovel.register_node_post(data)
|
|
||||||
if 'error_message' in result:
|
|
||||||
raise Exception(result)
|
|
||||||
else:
|
|
||||||
msg = _('Registered node {0} ({1})'.format(data['uuid'], data['name']))
|
|
||||||
messages.success(request, msg)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
msg = _('Failed to register baremetal node: {0} ({1})'.format(data['uuid'], data['name']))
|
|
||||||
messages.error(request, msg)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _check_failover(self, data):
|
|
||||||
if not data['enfailover']:
|
|
||||||
data.pop('failovernode', None)
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
self._check_failover(data)
|
|
||||||
self._add_new_node(request, data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class UnregisterForm(forms.SelfHandlingForm):
|
|
||||||
uuid = forms.CharField(max_length=255, label=_("Unregister Node"))
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(UnregisterForm, self).__init__(request, *args, **kwargs)
|
|
||||||
self._node = kwargs['initial']['node']
|
|
||||||
self.fields['uuid'].initial = self._node['id']
|
|
||||||
|
|
||||||
def _remove_current_node(self, request, data):
|
|
||||||
try:
|
|
||||||
# unregister a node from ironic using shovel
|
|
||||||
result = shovel.unregister_node_del(data['uuid'])
|
|
||||||
if 'result' in result:
|
|
||||||
msg = _('Unregistered node {0}'.format(data['uuid']))
|
|
||||||
messages.success(request, msg)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise Exception(result)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
msg = _('Failed to unregister baremetal node: {0}'.format(data['uuid']))
|
|
||||||
messages.error(request, msg)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
self._remove_current_node(request, data)
|
|
||||||
return True
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
JSON 2 HTML convertor
|
|
||||||
=====================
|
|
||||||
(c) Varun Malhotra 2013
|
|
||||||
Source Code: https://github.com/softvar/json2html
|
|
||||||
Contributors:
|
|
||||||
-------------
|
|
||||||
1. Michel Müller(@muellermichel), patch #2 - https://github.com/softvar/json2html/pull/2
|
|
||||||
LICENSE: MIT
|
|
||||||
--------
|
|
||||||
'''
|
|
||||||
|
|
||||||
import json
|
|
||||||
import ordereddict
|
|
||||||
|
|
||||||
class JSON:
|
|
||||||
|
|
||||||
def convert(self, **args):
|
|
||||||
'''
|
|
||||||
convert json Object to HTML Table format
|
|
||||||
'''
|
|
||||||
|
|
||||||
# table attributes such as class
|
|
||||||
# eg: table_attributes = "class = 'sortable table table-condensed table-bordered table-hover'
|
|
||||||
global table_attributes
|
|
||||||
table_attributes = ''
|
|
||||||
|
|
||||||
if 'table_attributes' in args:
|
|
||||||
table_attributes = args['table_attributes']
|
|
||||||
else:
|
|
||||||
# by default HTML table border
|
|
||||||
table_attributes = 'border="1"'
|
|
||||||
|
|
||||||
if 'json' in args:
|
|
||||||
self.json_input = args['json']
|
|
||||||
try:
|
|
||||||
json.loads(self.json_input)
|
|
||||||
except:
|
|
||||||
self.json_input = json.dumps(self.json_input)
|
|
||||||
else:
|
|
||||||
raise Exception('Can\'t convert NULL!')
|
|
||||||
|
|
||||||
|
|
||||||
ordered_json = json.loads(self.json_input, object_pairs_hook=ordereddict.OrderedDict)
|
|
||||||
|
|
||||||
return self.iterJson(ordered_json)
|
|
||||||
|
|
||||||
|
|
||||||
def columnHeadersFromListOfDicts(self, ordered_json):
|
|
||||||
'''
|
|
||||||
If suppose some key has array of objects and all the keys are same,
|
|
||||||
instead of creating a new row for each such entry, club those values,
|
|
||||||
thus it makes more sense and more readable code.
|
|
||||||
@example:
|
|
||||||
jsonObject = {"sampleData": [ {"a":1, "b":2, "c":3}, {"a":5, "b":6, "c":7} ] }
|
|
||||||
OUTPUT:
|
|
||||||
<table border="1"><tr><th>1</th><td><table border="1"><tr><th>a</th><th>c</th><th>b</th></tr><tr><td>1</td><td>3</td><td>2</td></tr><tr><td>5</td><td>7</td><td>6</td></tr></table></td></tr></table>
|
|
||||||
@contributed by: @muellermichel
|
|
||||||
'''
|
|
||||||
|
|
||||||
if len(ordered_json) < 2:
|
|
||||||
return None
|
|
||||||
if not isinstance(ordered_json[0],dict):
|
|
||||||
return None
|
|
||||||
|
|
||||||
column_headers = ordered_json[0].keys()
|
|
||||||
|
|
||||||
for entry in ordered_json:
|
|
||||||
if not isinstance(entry,dict):
|
|
||||||
return None
|
|
||||||
if len(entry.keys()) != len(column_headers):
|
|
||||||
return None
|
|
||||||
for header in column_headers:
|
|
||||||
if not header in entry:
|
|
||||||
return None
|
|
||||||
return column_headers
|
|
||||||
|
|
||||||
|
|
||||||
def iterJson(self, ordered_json):
|
|
||||||
'''
|
|
||||||
Iterate over the JSON and process it to generate the super awesome HTML Table format
|
|
||||||
'''
|
|
||||||
|
|
||||||
def markup(entry, parent_is_list = False):
|
|
||||||
'''
|
|
||||||
Check for each value corresponding to its key and return accordingly
|
|
||||||
'''
|
|
||||||
if(isinstance(entry,unicode)):
|
|
||||||
return unicode(entry)
|
|
||||||
if(isinstance(entry,int) or isinstance(entry,float)):
|
|
||||||
return str(entry)
|
|
||||||
if(parent_is_list and isinstance(entry,list)==True):
|
|
||||||
#list of lists are not accepted
|
|
||||||
return ''
|
|
||||||
if(isinstance(entry,list)==True) and len(entry) == 0:
|
|
||||||
return ''
|
|
||||||
if(isinstance(entry,list)==True):
|
|
||||||
return '<ul><li>' + '</li><li>'.join([markup(child, parent_is_list=True) for child in entry]) + '</li></ul>'
|
|
||||||
if(isinstance(entry,dict)==True):
|
|
||||||
return self.iterJson(entry)
|
|
||||||
|
|
||||||
#safety: don't do recursion over anything that we don't know about - iteritems() will most probably fail
|
|
||||||
return ''
|
|
||||||
|
|
||||||
convertedOutput = ''
|
|
||||||
|
|
||||||
global table_attributes
|
|
||||||
table_init_markup = "<table %s>" %(table_attributes)
|
|
||||||
convertedOutput = convertedOutput + table_init_markup
|
|
||||||
|
|
||||||
for k,v in ordered_json.iteritems():
|
|
||||||
convertedOutput = convertedOutput + '<tr>'
|
|
||||||
convertedOutput = convertedOutput + '<th>'+ str(k) +'</th>'
|
|
||||||
|
|
||||||
if (v == None):
|
|
||||||
v = unicode("")
|
|
||||||
if(isinstance(v,list)):
|
|
||||||
column_headers = self.columnHeadersFromListOfDicts(v)
|
|
||||||
if column_headers != None:
|
|
||||||
convertedOutput = convertedOutput + '<td>'
|
|
||||||
convertedOutput = convertedOutput + table_init_markup
|
|
||||||
convertedOutput = convertedOutput + '<tr><th>' + '</th><th>'.join(column_headers) + '</th></tr>'
|
|
||||||
for list_entry in v:
|
|
||||||
convertedOutput = convertedOutput + '<tr><td>' + '</td><td>'.join([markup(list_entry[column_header]) for column_header in column_headers]) + '</td></tr>'
|
|
||||||
convertedOutput = convertedOutput + '</table>'
|
|
||||||
convertedOutput = convertedOutput + '</td>'
|
|
||||||
convertedOutput = convertedOutput + '</tr>'
|
|
||||||
continue
|
|
||||||
convertedOutput = convertedOutput + '<td>' + markup(v) + '</td>'
|
|
||||||
convertedOutput = convertedOutput + '</tr>'
|
|
||||||
convertedOutput = convertedOutput + '</table>'
|
|
||||||
return convertedOutput
|
|
||||||
|
|
||||||
json2html = JSON()
|
|
@ -1,19 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
class Rackhd(horizon.Panel):
|
|
||||||
name = _("RackHD")
|
|
||||||
slug = "rackhd"
|
|
||||||
permissions = ('openstack.roles.admin',)
|
|
@ -1,79 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
SHOVEL_BASE_API = '/api/1.1'
|
|
||||||
URI = SHOVEL_URL + SHOVEL_BASE_API
|
|
||||||
|
|
||||||
def get_driver_list():
|
|
||||||
r = requests.get(URI + '/ironic/drivers')
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_images_list():
|
|
||||||
r = requests.get(URI + '/glance/images')
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_ironic_nodes():
|
|
||||||
r = requests.get(URI + '/ironic/nodes')
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_ironic_node(id):
|
|
||||||
r = requests.get(URI + '/ironic/nodes/' + id)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def request_node_get(id):
|
|
||||||
r = requests.get(URI + '/nodes/' + id)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def request_nodes_get():
|
|
||||||
r = requests.get(URI + '/nodes')
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def register_node_post(data):
|
|
||||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
|
||||||
r = requests.post(URI + '/register',
|
|
||||||
data=json.dumps(data), headers=headers)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def unregister_node_del(name):
|
|
||||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
|
||||||
data = {}
|
|
||||||
r = requests.delete(URI + '/unregister/' + name,
|
|
||||||
data=json.dumps(data), headers=headers)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def request_catalog_get(uuid):
|
|
||||||
r = requests.get(URI+ '/catalogs/' + uuid)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_catalog_data_by_source(id,source):
|
|
||||||
r = requests.get(URI+ '/catalogs/' + id + '/' + source)
|
|
||||||
return r.json()['data']
|
|
||||||
|
|
||||||
def get_current_sel_data(id):
|
|
||||||
r = requests.get(URI+ '/nodes/' + id + '/sel')
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def node_patch(uuid, data):
|
|
||||||
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
|
||||||
r = requests.patch(URI + '/ironic/node/' + uuid,
|
|
||||||
data=json.dumps(data), headers=headers)
|
|
||||||
return r.json()
|
|
@ -1,127 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.template import defaultfilters as filters
|
|
||||||
from django.utils.translation import pgettext_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
|
|
||||||
from horizon import tables
|
|
||||||
from horizon.utils import filters as utils_filters
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard import policy
|
|
||||||
|
|
||||||
class RegisterSelectedNodes(tables.LinkAction):
|
|
||||||
name = "register_selected"
|
|
||||||
verbose_name = _("Register Selected")
|
|
||||||
icon = "plus"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
url = "horizon:admin:rackhd:register"
|
|
||||||
def get_link_url(self, datum=None, *args, **kwargs):
|
|
||||||
return reverse(self.url)
|
|
||||||
|
|
||||||
|
|
||||||
class UnregisterSelectedNodes(tables.LinkAction):
|
|
||||||
name = "unregister_selected"
|
|
||||||
verbose_name = _("Unegister Selected")
|
|
||||||
icon = "minus"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
url = "horizon:admin:rackhd:unregister"
|
|
||||||
def get_link_url(self, datum=None, *args, **kwargs):
|
|
||||||
return reverse(self.url)
|
|
||||||
|
|
||||||
|
|
||||||
class RegisterNode(tables.LinkAction):
|
|
||||||
name = "register"
|
|
||||||
verbose_name = _("Register")
|
|
||||||
icon = "plus"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
url = "horizon:admin:rackhd:register"
|
|
||||||
|
|
||||||
|
|
||||||
class Failover(tables.LinkAction):
|
|
||||||
name = "failover"
|
|
||||||
verbose_name = _("Failover")
|
|
||||||
icon = "minus"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
url = "horizon:admin:rackhd:failover"
|
|
||||||
|
|
||||||
|
|
||||||
class UnregisterNode(tables.LinkAction):
|
|
||||||
name = "unregister"
|
|
||||||
verbose_name = _("Unregister")
|
|
||||||
icon = "minus"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
url = "horizon:admin:rackhd:unregister"
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalFilterAction(tables.FilterAction):
|
|
||||||
def filter(self, table, services, filter_string):
|
|
||||||
q = filter_string.lower()
|
|
||||||
return filter(lambda service: q in service.host.lower(), services)
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalDetailsTable(tables.DataTable):
|
|
||||||
catalog = tables.Column("catalog", verbose_name=_("Node Catalog"), filters=[filters.safe])
|
|
||||||
class Meta(object):
|
|
||||||
name = "node_catalog"
|
|
||||||
verbose_name = _("Catalog")
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalLastEventTable(tables.DataTable):
|
|
||||||
date = tables.Column('date',verbose_name=_("Date"))
|
|
||||||
event = tables.Column('event',verbose_name=_("Event"))
|
|
||||||
logid = tables.Column('logid',verbose_name=_("Log ID"))
|
|
||||||
sensor_num = tables.Column('sensor_num',verbose_name=_("Sensor Number"))
|
|
||||||
sensor_type = tables.Column('sensor_type',verbose_name=_("Sensor Type"))
|
|
||||||
time = tables.Column('time',verbose_name=_("Time"))
|
|
||||||
value = tables.Column('value',verbose_name=_("Value"))
|
|
||||||
class Meta(object):
|
|
||||||
name = "lastevent"
|
|
||||||
hidden_title=False
|
|
||||||
verbose_name = _("Last Triggered")
|
|
||||||
row_actions = (Failover,UnregisterNode,)
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalAllEventsTable(tables.DataTable):
|
|
||||||
if False:
|
|
||||||
date = tables.Column('date',verbose_name=_("Date"))
|
|
||||||
event = tables.Column('event',verbose_name=_("Event"))
|
|
||||||
logid = tables.Column('logid',verbose_name=_("Log ID"))
|
|
||||||
sensor_num = tables.Column('sensor_num',verbose_name=_("Sensor Number"))
|
|
||||||
sensor_type = tables.Column('sensor_type',verbose_name=_("Sensor Type"))
|
|
||||||
time = tables.Column('time',verbose_name=_("Time"))
|
|
||||||
value = tables.Column('value',verbose_name=_("Value"))
|
|
||||||
else:
|
|
||||||
html = tables.Column('html',verbose_name=_("System Events"), filters=[filters.safe])
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "allevents"
|
|
||||||
hidden_title=False
|
|
||||||
verbose_name = _("All Events")
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalTable(tables.DataTable):
|
|
||||||
name = tables.Column('name', verbose_name=_('Name'), link="horizon:admin:rackhd:detail", )
|
|
||||||
uuid = tables.Column('uuid', verbose_name=_('Node ID') )
|
|
||||||
hwaddr = tables.Column('hwaddr', verbose_name=_('MAC Address') )
|
|
||||||
events = tables.Column('events', verbose_name=_('Events'), link="horizon:admin:rackhd:events" )
|
|
||||||
state = tables.Column('state', verbose_name=_('State'))
|
|
||||||
class Meta(object):
|
|
||||||
name = "baremetal"
|
|
||||||
verbose_name = _("Baremetal Compute Nodes")
|
|
||||||
table_actions = (BareMetalFilterAction,)
|
|
||||||
multi_select = False
|
|
||||||
row_actions = (RegisterNode, UnregisterNode,)
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load url from future %}
|
|
||||||
|
|
||||||
{% block form_id %}register_form{% endblock %}
|
|
||||||
{% block form_action %}{% url 'horizon:admin:rackhd:register' baremetal %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-header %}{% trans "Register Node" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="left">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Register bare metal node." %}</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Register Node" %}" />
|
|
||||||
<a href="{% url 'horizon:admin:hypervisors:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
|
||||||
{% endblock %}
|
|
@ -1,25 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load url from future %}
|
|
||||||
|
|
||||||
{% block form_id %}unregister_form{% endblock %}
|
|
||||||
{% block form_action %}{% url 'horizon:admin:rackhd:unregister' baremetal %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-header %}{% trans "Unregister Node" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="left">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Unegister bare metal node." %}</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Unregister Node" %}" />
|
|
||||||
<a href="{% url 'horizon:admin:hypervisors:index' %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
|
||||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Node Details" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{ table.render }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Node Events" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<div id="last-event">
|
|
||||||
{{ lastevent_table.render }}
|
|
||||||
</div>
|
|
||||||
<div id="all-events">
|
|
||||||
{{ allevents_table.render }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Bare Metal" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ table.render }}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Register" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'admin/rackhd/_register.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Unregister" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'admin/rackhd/_unregister.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from horizon.test import helpers as test
|
|
||||||
|
|
||||||
|
|
||||||
class RackhdTests(test.TestCase):
|
|
||||||
# Unit tests for rackhd.
|
|
||||||
def test_me(self):
|
|
||||||
self.assertTrue(1 + 1 == 2)
|
|
@ -1,28 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.conf.urls import patterns
|
|
||||||
from django.conf.urls import url
|
|
||||||
from django.conf.urls import include
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd import views
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
|
||||||
'openstack_dashboard.dashboards.admin.rackhd.views',
|
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
|
||||||
url(r'^(?P<baremetal>[^/]+)/register$', views.RegisterView.as_view(), name='register'),
|
|
||||||
url(r'^(?P<baremetal>[^/]+)/unregister$', views.UnregisterView.as_view(), name='unregister'),
|
|
||||||
url(r'^(?P<baremetal>[^/]+)/detail$', views.BareMetalDetailView.as_view(), name='detail'),
|
|
||||||
url(r'^(?P<baremetal>[^/]+)/events$', views.BareMetalEventView.as_view(), name='events'),
|
|
||||||
url(r'^(?P<baremetal>[^/]+)/failover$', views.FailoverView.as_view(), name='failover'),
|
|
||||||
)
|
|
@ -1,276 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import tables
|
|
||||||
from horizon import messages
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd \
|
|
||||||
import forms as baremetal_forms
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd \
|
|
||||||
import tables as baremetal_tables
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd \
|
|
||||||
import json2html as j2h
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.rackhd import shovel
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class IndexView(tables.DataTableView):
|
|
||||||
# A very simple class-based view...
|
|
||||||
table_class = baremetal_tables.BareMetalTable
|
|
||||||
template_name = "admin/rackhd/index.html"
|
|
||||||
page_title = _("Baremetal")
|
|
||||||
|
|
||||||
class NodeData:
|
|
||||||
def __init__(self, uuid, name, hwaddr, events, state):
|
|
||||||
self.id = uuid
|
|
||||||
self.name = name
|
|
||||||
self.uuid = uuid
|
|
||||||
self.hwaddr = hwaddr
|
|
||||||
self.events = events
|
|
||||||
self.state = state
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
data = []
|
|
||||||
try:
|
|
||||||
nodes = shovel.request_nodes_get()
|
|
||||||
i = 0
|
|
||||||
for n in nodes:
|
|
||||||
dmi = shovel.get_catalog_data_by_source(n['id'],'dmi')
|
|
||||||
name = dmi['System Information']['Product Name']
|
|
||||||
hwaddr = n['name']
|
|
||||||
id = n['id']
|
|
||||||
events = '0'
|
|
||||||
n = self._find_ironic_node(id)
|
|
||||||
if n is not None:
|
|
||||||
events = n['extra'].get('eventcnt','0')
|
|
||||||
state = 'Registered'
|
|
||||||
else:
|
|
||||||
state = 'Unregistered'
|
|
||||||
i += i +1
|
|
||||||
data.append(self.NodeData(id, name, hwaddr, events, state))
|
|
||||||
return data
|
|
||||||
except Exception, e:
|
|
||||||
print
|
|
||||||
LOG.error("Excepton in get_baremetal_data(): {0}".format(e))
|
|
||||||
return data
|
|
||||||
def _find_ironic_node(self, id):
|
|
||||||
# ISSUE: iterating all nodes because query by name (onrack id) isn't working in ironic?
|
|
||||||
nodes = shovel.get_ironic_nodes()
|
|
||||||
for n in nodes['nodes']:
|
|
||||||
if n['extra'].get('nodeid', None) == id:
|
|
||||||
return n
|
|
||||||
return None
|
|
||||||
|
|
||||||
class RegisterView(forms.ModalFormView):
|
|
||||||
context_object_name = 'baremetal'
|
|
||||||
template_name = 'admin/rackhd/register.html'
|
|
||||||
form_class = baremetal_forms.RegisterForm
|
|
||||||
success_url = reverse_lazy('horizon:admin:rackhd:index')
|
|
||||||
page_title = _("Register Node")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(RegisterView, self).get_context_data(**kwargs)
|
|
||||||
context["baremetal"] = self.kwargs['baremetal']
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
id = self.kwargs['baremetal']
|
|
||||||
node = shovel.request_node_get(id)
|
|
||||||
list_drivers = shovel.get_driver_list()['drivers']
|
|
||||||
drivers = [ elem['name'] for elem in list_drivers ]
|
|
||||||
ports = str(node['name']).split(',')
|
|
||||||
|
|
||||||
list_images = shovel.get_images_list()['images']
|
|
||||||
images = [ elem['name'] for elem in list_images ]
|
|
||||||
initial = super(RegisterView, self).get_initial()
|
|
||||||
initial.update({'nodeid': self.kwargs['baremetal'], 'node': node, 'drivers': drivers,'images':images,'ports': ports})
|
|
||||||
return initial
|
|
||||||
|
|
||||||
|
|
||||||
class UnregisterView(forms.ModalFormView):
|
|
||||||
context_object_name = 'baremetal'
|
|
||||||
template_name = 'admin/rackhd/unregister.html'
|
|
||||||
form_class = baremetal_forms.UnregisterForm
|
|
||||||
success_url = reverse_lazy('horizon:admin:rackhd:index')
|
|
||||||
page_title = _("Unegister Node")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(UnregisterView, self).get_context_data(**kwargs)
|
|
||||||
context["baremetal"] = self.kwargs['baremetal']
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
id = self.kwargs['baremetal']
|
|
||||||
node = shovel.request_node_get(id)
|
|
||||||
initial = super(UnregisterView, self).get_initial()
|
|
||||||
initial.update({'nodeid': self.kwargs['baremetal'], 'node': node})
|
|
||||||
return initial
|
|
||||||
|
|
||||||
|
|
||||||
class FailoverView(forms.ModalFormView):
|
|
||||||
context_object_name = 'baremetal'
|
|
||||||
template_name = 'admin/rackhd/register.html'
|
|
||||||
form_class = baremetal_forms.RegisterForm
|
|
||||||
success_url = reverse_lazy('horizon:admin:rackhd:index')
|
|
||||||
page_title = _("Failover")
|
|
||||||
|
|
||||||
def _find_ironic_node(self, id):
|
|
||||||
nodes = shovel.get_ironic_nodes()
|
|
||||||
for n in nodes['nodes']:
|
|
||||||
if n['extra'].get('nodeid', None) == id:
|
|
||||||
return n
|
|
||||||
|
|
||||||
def _remove_node(self,id):
|
|
||||||
try:
|
|
||||||
result = shovel.unregister_node_del(id)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(FailoverView, self).get_context_data(**kwargs)
|
|
||||||
context["baremetal"] = self.kwargs['baremetal']
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = super(FailoverView, self).get_initial()
|
|
||||||
current_id = self.kwargs['baremetal']
|
|
||||||
inode = self._find_ironic_node(current_id)
|
|
||||||
try:
|
|
||||||
if inode is not None:
|
|
||||||
failover = inode['extra'].get('failover', None)
|
|
||||||
if failover is not None:
|
|
||||||
node = shovel.request_node_get(failover)
|
|
||||||
list_drivers = shovel.get_driver_list()['drivers']
|
|
||||||
drivers = [ elem['name'] for elem in list_drivers ]
|
|
||||||
initial.update({'nodeid': self.kwargs['baremetal'], 'node': node, 'drivers': drivers})
|
|
||||||
else:
|
|
||||||
raise ValueError('Failover node not set')
|
|
||||||
else:
|
|
||||||
raise ValueError('Registered node not found')
|
|
||||||
except ValueError as e:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
messages.error(self.request, _(e.message))
|
|
||||||
raise Exception(e.message)
|
|
||||||
self._remove_node(current_id)
|
|
||||||
messages.success(self.request, _('Removed node {0}'.format(current_id)))
|
|
||||||
return initial
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalDetailView(tables.DataTableView):
|
|
||||||
table_class = baremetal_tables.BareMetalDetailsTable
|
|
||||||
template_name = 'admin/rackhd/detail.html'
|
|
||||||
page_title = _('Details')
|
|
||||||
|
|
||||||
class CatalogData:
|
|
||||||
def __init__(self, id, catalog):
|
|
||||||
self.id = id
|
|
||||||
self.catalog = catalog
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
uuid = self.kwargs['baremetal']
|
|
||||||
dmi = shovel.get_catalog_data_by_source(id = uuid, source = 'dmi')
|
|
||||||
scsi = shovel.get_catalog_data_by_source(id = uuid, source = 'lsscsi')
|
|
||||||
del dmi['Processor Information'] # don't feel like rendering this now
|
|
||||||
dmi.update({'Storage Information' : scsi})
|
|
||||||
|
|
||||||
catalog = json.dumps(dmi, sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
data = [ self.CatalogData(id, j2h.json2html.convert( json = catalog, table_attributes="class=\"table-bordered table\"" )) ]
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class BareMetalEventView(tables.MultiTableView):
|
|
||||||
table_classes = (baremetal_tables.BareMetalLastEventTable,
|
|
||||||
baremetal_tables.BareMetalAllEventsTable,)
|
|
||||||
template_name = 'admin/rackhd/events.html'
|
|
||||||
page_title = _('Events')
|
|
||||||
name = _("Events")
|
|
||||||
slug = "events"
|
|
||||||
|
|
||||||
class HTMLData:
|
|
||||||
def __init__(self, id, html):
|
|
||||||
self.id = id
|
|
||||||
self.html = html
|
|
||||||
|
|
||||||
class SELEventData:
|
|
||||||
def __init__(self, id, type, value, logid, number, time, date, event):
|
|
||||||
self.id = id
|
|
||||||
self.date = date
|
|
||||||
self.event = event
|
|
||||||
self.logid = logid
|
|
||||||
self.sensor_num = number
|
|
||||||
self.sensor_type = type
|
|
||||||
self.time = time
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def _find_ironic_node(self, id):
|
|
||||||
nodes = shovel.get_ironic_nodes()
|
|
||||||
for n in nodes['nodes']:
|
|
||||||
if n['extra'].get('nodeid', None) == id:
|
|
||||||
return n
|
|
||||||
|
|
||||||
def get_lastevent_data(self):
|
|
||||||
id = self.kwargs['baremetal']
|
|
||||||
try:
|
|
||||||
node = self._find_ironic_node(id)
|
|
||||||
if node is not None:
|
|
||||||
entry = node['extra']['events']
|
|
||||||
return [ self.SELEventData(id,
|
|
||||||
entry['sensorType'],
|
|
||||||
entry['value'],
|
|
||||||
str(int(entry['logId'], 16)),
|
|
||||||
entry['sensorNumber'],
|
|
||||||
entry['time'],
|
|
||||||
entry['date'],
|
|
||||||
entry['event']) ]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_allevents_data(self):
|
|
||||||
id = self.kwargs['baremetal']
|
|
||||||
try:
|
|
||||||
sel = shovel.get_current_sel_data(id)[0].get('sel', [])
|
|
||||||
except KeyError as e:
|
|
||||||
redirect = reverse('horizon:admin:rackhd:index')
|
|
||||||
messages.error(self.request, _('No SEL data available, check node {0} poller task'.format(id)))
|
|
||||||
raise KeyError(e.message)
|
|
||||||
data = []
|
|
||||||
if False: # TODO: enable this, view is not iterating the data list correctly
|
|
||||||
for entry in sel:
|
|
||||||
data.append(self.SELEventData(id,
|
|
||||||
entry['sensorType'],
|
|
||||||
entry['value'],
|
|
||||||
str(int(entry['logId'], 16)),
|
|
||||||
entry['sensorNumber'],
|
|
||||||
entry['time'],
|
|
||||||
entry['date'],
|
|
||||||
entry['event'] ))
|
|
||||||
else: # build html
|
|
||||||
rsel = list(reversed(sel))
|
|
||||||
j = json.dumps({"":rsel}, sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
return [ self.HTMLData(id, j2h.json2html.convert(json=j, table_attributes="class=\"table\"")) ]
|
|
||||||
return data
|
|
@ -1,115 +0,0 @@
|
|||||||
# Configure Openstack to Boot Baremetal nodes Using Devstack
|
|
||||||
|
|
||||||
## Download and install OpenStack using DevStack
|
|
||||||
- git clone https://github.com/openstack-dev/devstack.git devstack
|
|
||||||
- sudo ./devstack/tools/create-stack-user.sh
|
|
||||||
- sudo su stack
|
|
||||||
- cd ~
|
|
||||||
- git clone https://github.com/openstack-dev/devstack.git devstack
|
|
||||||
- cd Devstack
|
|
||||||
- in Devstack, Create local.conf :
|
|
||||||
```python
|
|
||||||
|
|
||||||
[[local|localrc]]
|
|
||||||
#Enable Ironic API and Ironic Conductor
|
|
||||||
enable_service ironic
|
|
||||||
enable_service ir-api
|
|
||||||
enable_service ir-cond
|
|
||||||
#Enable Neutron which is required by Ironic and disable nova-network.
|
|
||||||
disable_service n-net
|
|
||||||
disable_service n-novnc
|
|
||||||
enable_service q-dhcp
|
|
||||||
enable_service q-svc
|
|
||||||
enable_service q-agt
|
|
||||||
enable_service q-l3
|
|
||||||
enable_service q-meta
|
|
||||||
enable_service neutron
|
|
||||||
#Optional, to enable tempest configuration as part of devstack
|
|
||||||
disable_service tempest
|
|
||||||
disable_service heat h-api h-api-cfn h-api-cw h-eng
|
|
||||||
disable_service cinder c-sch c-api c-vol
|
|
||||||
ADMIN_PASSWORD=root
|
|
||||||
DATABASE_PASSWORD=$ADMIN_PASSWORD
|
|
||||||
RABBIT_PASSWORD=$ADMIN_PASSWORD
|
|
||||||
SERVICE_PASSWORD=$ADMIN_PASSWORD
|
|
||||||
SERVICE_TOKEN=$ADMIN_PASSWORD
|
|
||||||
HOST_IP=172.31.128.7
|
|
||||||
#Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
|
||||||
IRONIC_VM_COUNT=3
|
|
||||||
IRONIC_VM_SSH_PORT=22
|
|
||||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
|
||||||
#The parameters below represent the minimum possible values to create
|
|
||||||
#functional nodes.
|
|
||||||
IRONIC_VM_SPECS_RAM=1024
|
|
||||||
IRONIC_VM_SPECS_DISK=10
|
|
||||||
#Size of the ephemeral partition in GB. Use 0 for no ephemeral partition.
|
|
||||||
IRONIC_VM_EPHEMERAL_DISK=0
|
|
||||||
VIRT_DRIVER=ironic
|
|
||||||
#By default, DevStack creates a 10.0.0.0/24 network for instances.
|
|
||||||
#If this overlaps with the hosts network, you may adjust with the
|
|
||||||
#following.
|
|
||||||
NETWORK_GATEWAY=10.1.0.1
|
|
||||||
FIXED_RANGE=10.1.0.0/24
|
|
||||||
FIXED_NETWORK_SIZE=256
|
|
||||||
#Neutron OVS (flat)
|
|
||||||
Q_PLUGIN=ml2
|
|
||||||
Q_AGENT_EXTRA_OVS_OPTS=(network_vlan_ranges=physnet1)
|
|
||||||
OVS_VLAN_RANGE=physnet1
|
|
||||||
PHYSICAL_NETWORK=physnet1
|
|
||||||
OVS_PHYSICAL_BRIDGE=br-eth2
|
|
||||||
#Log all output to files
|
|
||||||
LOGFILE=$HOME/devstack.log
|
|
||||||
SCREEN_LOGDIR=$HOME/logs
|
|
||||||
IRONIC_VM_LOG_DIR=$HOME/ironic-bm-logs
|
|
||||||
```
|
|
||||||
- Configure network Interface (assuming port eth2 is used to connect openstack to rackHD)
|
|
||||||
|
|
||||||
![alt text](https://github.com/keedya/Shovel-horizon/blob/master/Shovel/snapshot/dev_config.PNG)
|
|
||||||
|
|
||||||
- cat>>/etc/network/interfaces
|
|
||||||
```python
|
|
||||||
|
|
||||||
auto eth2
|
|
||||||
iface eth2 inet static
|
|
||||||
address 172.31.128.7
|
|
||||||
netmask 255.255.255.0
|
|
||||||
```
|
|
||||||
- Restart network service
|
|
||||||
- sudo ifdown eth2
|
|
||||||
- sudo ifup eth2
|
|
||||||
- Run ./stack.sh
|
|
||||||
|
|
||||||
## Configure Neutron
|
|
||||||
|
|
||||||
Once the installation is completed, an external bridge can be setup for Neutron physical network
|
|
||||||
|
|
||||||
- Bind eth2 to the external bridge:
|
|
||||||
- ovs-vsctl add-port br-eth2 eth2
|
|
||||||
- Enable external network access under nested Open vSwitch
|
|
||||||
- ifconfig br-eth2 promisc up
|
|
||||||
- Update external bridge configuration cat>>/etc/network/interfaces
|
|
||||||
```python
|
|
||||||
|
|
||||||
auto eth2
|
|
||||||
iface eth2 inet manual
|
|
||||||
auto br-eth2
|
|
||||||
iface br-eth2 inet static
|
|
||||||
address 172.31.128.7
|
|
||||||
netmask 255.255.255.0
|
|
||||||
```
|
|
||||||
- Restart network service
|
|
||||||
- sudo ifdown br-eth2
|
|
||||||
- sudo ifup br-eth2
|
|
||||||
|
|
||||||
- Create Flat netwok:
|
|
||||||
- Source ~/devstack/openrc admin admin
|
|
||||||
- neutron net-create flat-provider-network --shared --provider:network_type flat -- provider:physical_network physnet1
|
|
||||||
- neutron subnet-create --name flat-provider-subnet --gateway 172.31.128.7 --dns-nameserver 172.31.128.254 --allocation-pool start=172.31.128.100,end=172.31.128.150 flat-provider-network 172.31.128.0/24
|
|
||||||
|
|
||||||
## Spawn an instance using nova service
|
|
||||||
- Login the horizon interface (user:admin,password:root)
|
|
||||||
- Use horizon to create new instances
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
0
Shovel/scripts/post-install.sh → scripts/post-install.sh
Executable file → Normal file
0
Shovel/scripts/shovel.conf → scripts/shovel.conf
Executable file → Normal file
@ -1,7 +1,7 @@
|
|||||||
# RackHD/OpenStack Coordinator (shovel)
|
# RackHD/OpenStack Coordinator (shovel)
|
||||||
|
|
||||||
- git clone https://github.com/keedya/Shovel-horizon.git
|
- git clone https://github.com/keedya/Shovel.git
|
||||||
- cd Shovel-horizon
|
- cd Shovel
|
||||||
- sudo mv Shovel /var/
|
- sudo mv Shovel /var/
|
||||||
- cd /var/Shovel
|
- cd /var/Shovel
|
||||||
- sudo npm install --unsafe-perm
|
- sudo npm install --unsafe-perm
|
||||||
@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
## Shovel-Ironic Set info Example:
|
## Shovel-Ironic Set info Example:
|
||||||
|
|
||||||
![alt text](https://github.com/keedya/Shovel-horizon/blob/master/Shovel/snapshot/ironic_info.png)
|
![alt text](https://github.com/keedya/shovel/blob/master/snapshot/ironic_info.png)
|
||||||
|
|
||||||
## Shovel Set Port Example:
|
## Shovel Set Port Example:
|
||||||
|
|
||||||
![alt text](https://github.com/keedya/Shovel-horizon/blob/master/Shovel/snapshot/shovel_settings.png)
|
![alt text](https://github.com/keedya/shovel/blob/master/snapshot/shovel_settings.png)
|
||||||
|
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |