summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-04-29 14:38:05 +0000
committerGerrit Code Review <review@openstack.org>2015-04-29 14:38:05 +0000
commita35158bf428cc4f65ca241d8d4a29e3e69900f50 (patch)
tree73036a7af2ee231b4652b42a5f6ac458034a8465
parente704c4bba51120bac5baa60df17436574d396c5e (diff)
parentbc3b4e08af09096395559fec03b6f15bc292b029 (diff)
downloadtuskar-ui-a35158bf428cc4f65ca241d8d4a29e3e69900f50.tar.gz
Merge "Allow to upload all data from nodes.csv file"
-rw-r--r--doc/source/index.rst1
-rw-r--r--doc/source/user_guide.rst16
-rw-r--r--tuskar_ui/infrastructure/nodes/forms.py40
-rw-r--r--tuskar_ui/utils/tests.py87
-rw-r--r--tuskar_ui/utils/utils.py46
5 files changed, 157 insertions, 33 deletions
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 809c3dfe..d6176cb0 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -8,6 +8,7 @@ Contents:
README
install
+ user_guide
HACKING
Indices and tables
diff --git a/doc/source/user_guide.rst b/doc/source/user_guide.rst
new file mode 100644
index 00000000..782c9e05
--- /dev/null
+++ b/doc/source/user_guide.rst
@@ -0,0 +1,16 @@
+==========
+User Guide
+==========
+
+Nodes List File
+---------------
+
+To allow users to load a bunch of nodes at once, there is possibility to
+upload CSV file with given list of nodes. This file should be formatted as
+
+::
+
+ driver,address,username,password/ssh key,mac addresses,cpu architecture,number of CPUs,available memory,available storage
+
+Even if there is no all data available, we assume empty values for missing
+keys and try to parse everything, what is possible.
diff --git a/tuskar_ui/infrastructure/nodes/forms.py b/tuskar_ui/infrastructure/nodes/forms.py
index 12d365cd..6ec6b6e8 100644
--- a/tuskar_ui/infrastructure/nodes/forms.py
+++ b/tuskar_ui/infrastructure/nodes/forms.py
@@ -11,8 +11,6 @@
# 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 csv
-
import django.forms
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
@@ -21,6 +19,7 @@ from horizon import messages
from tuskar_ui import api
import tuskar_ui.forms
+from tuskar_ui.utils import utils
DEFAULT_KERNEL_IMAGE_NAME = 'bm-deploy-kernel'
@@ -307,38 +306,13 @@ class UploadNodeForm(forms.SelfHandlingForm):
return True
def get_data(self):
- data = []
+ try:
+ output = utils.parse_csv_file(self.cleaned_data['csv_file'])
+ except ValueError as e:
+ messages.error(self.request, e.message)
+ output = []
- for row in csv.reader(self.cleaned_data['csv_file']):
- try:
- driver = row[0].strip()
- except IndexError:
- messages.error(self.request,
- _("Unable to parse the CSV file."))
- return []
-
- if driver == 'pxe_ssh':
- node = dict(
- ssh_address=row[1],
- ssh_username=row[2],
- ssh_key_contents=row[3],
- mac_addresses=row[4],
- driver=driver,
- )
- data.append(node)
- elif driver == 'pxe_ipmitool':
- node = dict(
- ipmi_address=row[1],
- ipmi_username=row[2],
- ipmi_password=row[3],
- driver=driver,
- )
- data.append(node)
- else:
- messages.error(self.request,
- _("Unknown driver: %s.") % driver)
- return []
- return data
+ return output
RegisterNodeFormset = django.forms.formsets.formset_factory(
diff --git a/tuskar_ui/utils/tests.py b/tuskar_ui/utils/tests.py
index 60729669..408f916c 100644
--- a/tuskar_ui/utils/tests.py
+++ b/tuskar_ui/utils/tests.py
@@ -15,6 +15,7 @@
import collections
import datetime
+from django.utils.translation import ugettext_lazy as _
import mock
from tuskar_ui.test import helpers
@@ -77,6 +78,92 @@ class UtilsTests(helpers.TestCase):
ret = utils.safe_int_cast(object())
self.assertEqual(ret, 0)
+ def test_parse_correct_csv_file(self):
+ correct_file = [
+ 'pxe_ipmitool,ipmi_address,ipmi_username,ipmi_password,'
+ 'mac_addresses,cpu_arch,cpus,memory_mb,local_gb',
+ 'pxe_ipmitool,,,,MAC_ADDRESS,,CPUS,,LOCAL_GB',
+ 'pxe_ssh,ssh_address,ssh_username,ssh_key_contents,mac_addresses'
+ ',cpu_arch,cpus,memory_mb,local_gb',
+ 'pxe_ssh,SSH,USER,KEY',
+ 'pxe_ssh,SSH,USER,,,CPU_ARCH',
+ ]
+
+ correct_data = utils.parse_csv_file(correct_file)
+
+ self.assertSequenceEqual(
+ correct_data, [
+ {
+ 'driver': 'pxe_ipmitool',
+ 'ipmi_address': 'ipmi_address',
+ 'ipmi_username': 'ipmi_username',
+ 'ipmi_password': 'ipmi_password',
+ 'mac_addresses': 'mac_addresses',
+ 'cpu_arch': 'cpu_arch',
+ 'cpus': 'cpus',
+ 'memory_mb': 'memory_mb',
+ 'local_gb': 'local_gb',
+ }, {
+ 'driver': 'pxe_ipmitool',
+ 'ipmi_address': '',
+ 'ipmi_username': '',
+ 'ipmi_password': '',
+ 'mac_addresses': 'MAC_ADDRESS',
+ 'cpu_arch': '',
+ 'cpus': 'CPUS',
+ 'memory_mb': '',
+ 'local_gb': 'LOCAL_GB',
+ }, {
+ 'driver': 'pxe_ssh',
+ 'ssh_address': 'ssh_address',
+ 'ssh_username': 'ssh_username',
+ 'ssh_key_contents': 'ssh_key_contents',
+ 'mac_addresses': 'mac_addresses',
+ 'cpu_arch': 'cpu_arch',
+ 'cpus': 'cpus',
+ 'memory_mb': 'memory_mb',
+ 'local_gb': 'local_gb',
+ },
+ {
+ 'driver': 'pxe_ssh',
+ 'ssh_address': 'SSH',
+ 'ssh_username': 'USER',
+ 'ssh_key_contents': 'KEY',
+ },
+ {
+ 'driver': 'pxe_ssh',
+ 'ssh_address': 'SSH',
+ 'ssh_username': 'USER',
+ 'ssh_key_contents': '',
+ 'mac_addresses': '',
+ 'cpu_arch': 'CPU_ARCH',
+ },
+ ]
+ )
+
+ def test_parse_csv_file_wrong(self):
+ no_csv_file = [
+ '',
+ 'File with first empty line -- it\'s not a CSV file.',
+ ]
+
+ with self.assertRaises(ValueError) as raised:
+ utils.parse_csv_file(no_csv_file)
+
+ self.assertEqual(unicode(raised.exception.message),
+ unicode(_("Unable to parse the CSV file.")))
+
+ def test_parse_wrong_driver_file(self):
+ wrong_driver_file = [
+ 'wrong_driver,ssh_address,ssh_user',
+ ]
+
+ with self.assertRaises(ValueError) as raised:
+ utils.parse_csv_file(wrong_driver_file)
+
+ self.assertEqual(unicode(raised.exception.message),
+ unicode(_("Unknown driver: %s.") % 'wrong_driver'))
+
class MeteringTests(helpers.TestCase):
def test_query_data(self):
diff --git a/tuskar_ui/utils/utils.py b/tuskar_ui/utils/utils.py
index a75eb24c..f99d9fda 100644
--- a/tuskar_ui/utils/utils.py
+++ b/tuskar_ui/utils/utils.py
@@ -11,8 +11,12 @@
# 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 csv
+from itertools import izip
import re
+from django.utils.translation import ugettext_lazy as _
+
CAMEL_RE = re.compile(r'([A-Z][a-z]+|[A-Z]+(?=[A-Z\s]|$))')
@@ -92,3 +96,45 @@ def safe_int_cast(value):
return int(value)
except (TypeError, ValueError):
return 0
+
+
+def parse_csv_file(csv_file):
+ """Parses given CSV file.
+
+ If there is no error, it returns list of dicts. When something went wrong,
+ list is empty, but warning contains appropriate information about
+ possible problems.
+ """
+
+ parsed_data = []
+
+ for row in csv.reader(csv_file):
+ try:
+ driver = row[0].strip()
+ except IndexError:
+ raise ValueError(_("Unable to parse the CSV file."))
+
+ if driver in ('pxe_ssh', 'pxe_ipmitool'):
+ node_keys = (
+ 'mac_addresses', 'cpu_arch', 'cpus', 'memory_mb', 'local_gb')
+
+ if driver == 'pxe_ssh':
+ driver_keys = (
+ 'driver', 'ssh_address', 'ssh_username',
+ 'ssh_key_contents'
+ )
+
+ elif driver == 'pxe_ipmitool':
+ driver_keys = (
+ 'driver', 'ipmi_address', 'ipmi_username',
+ 'ipmi_password'
+ )
+
+ node = dict(izip(driver_keys+node_keys, row))
+
+ parsed_data.append(node)
+
+ else:
+ raise ValueError(_("Unknown driver: %s.") % driver)
+
+ return parsed_data