[Kimchi-devel] [WIP PATCH] Storage DL/UL

Aline Manera alinefm at linux.vnet.ibm.com
Mon Sep 8 18:02:54 UTC 2014


On 09/05/2014 02:48 PM, Hongliang Wang wrote:
> Volume.
>
> Known kssues:
>    after add a remote URL and click "OK" button, manual click on the storage pool
> to refresh the volumes.
>
> Signed-off-by: Hongliang Wang <hlwang at linux.vnet.ibm.com>
> ---
>   src/kimchi/model/host.py               |   2 +-
>   ui/css/theme-default/sp-add-volume.css |  36 ++++++++
>   ui/css/theme-default/storage.css       |  34 +++++++-
>   ui/js/src/kimchi.api.js                |  38 +++++++++
>   ui/js/src/kimchi.sp_add_volume_main.js | 152 +++++++++++++++++++++++++++++++++
>   ui/js/src/kimchi.storage_main.js       |  19 ++++-
>   ui/pages/i18n.json.tmpl                |   3 +
>   ui/pages/sp-add-volume.html.tmpl       |  80 +++++++++++++++++
>   ui/pages/tabs/storage.html.tmpl        |  12 ++-
>   9 files changed, 369 insertions(+), 7 deletions(-)
>   create mode 100644 ui/css/theme-default/sp-add-volume.css
>   create mode 100644 ui/js/src/kimchi.sp_add_volume_main.js
>   create mode 100644 ui/pages/sp-add-volume.html.tmpl
>
> diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
> index 933a142..2052f58 100644
> --- a/src/kimchi/model/host.py
> +++ b/src/kimchi/model/host.py
> @@ -280,7 +280,7 @@ class DevicesModel(object):
>       def __init__(self, **kargs):
>           self.conn = kargs['conn']
>           self.cap_map = \
> -            {'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST,
> +            {#'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST,

I think you used it only for your tests, right? =)

>                'net': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET,
>                'pci': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV,
>                'scsi': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI,
> diff --git a/ui/css/theme-default/sp-add-volume.css b/ui/css/theme-default/sp-add-volume.css
> new file mode 100644
> index 0000000..2c31b9a
> --- /dev/null
> +++ b/ui/css/theme-default/sp-add-volume.css
> @@ -0,0 +1,36 @@
> +/*
> + * Project Kimchi
> + *
> + * Copyright IBM, Corp. 2013-2014
> + *

The copyright refers to the year the file was created followed by the 
modifications.
So if a file was created in 2014 and never modified the copyright will 
be only 2014.
if the files was created in 2013 and modified in 2014 the copyright will 
be 2013-2014
So in this case you need to keep only 2014

> + * 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.
> + */
> +#sp-add-volume-window {
> +    height: 400px;
> +    width: 500px;
> +}
> +
> +#sp-add-volume-window .textbox-wrapper input[type="text"] {
> +    box-sizing: border-box;
> +    width: 100%;
> +}
> +
> +#sp-add-volume-window .textbox-wrapper label {
> +    vertical-align: middle;
> +}
> +
> +#sp-add-volume-window input[type="text"][disabled] {
> +    color: #bbb;
> +    background-color: #fafafa;
> +    cursor: not-allowed;
> +}
> diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
> index f635c2f..02c92e9 100644
> --- a/ui/css/theme-default/storage.css
> +++ b/ui/css/theme-default/storage.css
> @@ -336,7 +336,6 @@
>       float: left;
>       padding: 4px;
>       margin-bottom: 5px;
> -    height: 40px;
>       width: 130px;
>   }
>
> @@ -622,3 +621,36 @@
>   #iSCSITarget input {
>       width: 493px;
>   }
> +
> +/* Progress bar */
> +.volume-progress {
> +    clear: both;
> +    width: 140px;
> +}
> +
> +.volume-progress .progress-bar-outer {
> +    background: #ccc;
> +    height: 4px;
> +    overflow: hidden;
> +    width: 100%;
> +}
> +
> +.volume-progress .progress-bar-inner {
> +    background: #090;
> +    height: 100%;
> +    width: 0%;
> +}
> +
> +.volume-progress .progress-label {
> +    color: #999;
> +    font-size: 10px;
> +    line-height: 16px;
> +}
> +
> +.volume-progress .progress-status {
> +}
> +
> +.volume-progress .progress-downloaded {
> +    float: right;
> +}
> +/* End of Progress bar */
> diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
> index 5fc456d..766df6a 100644
> --- a/ui/js/src/kimchi.api.js
> +++ b/ui/js/src/kimchi.api.js
> @@ -1130,5 +1130,43 @@ var kimchi = {
>                   kimchi.message.error(data.responseJSON.reason);
>               }
>           });
> +    },
> +
> +    /**
> +     * Add a volume to a given storage pool.
> +     */
> +    uploadVolumeToSP: function(settings, suc, err) {
> +        var fd = settings['formData'];
> +        var sp = encodeURIComponent(settings['sp']);
> +        kimchi.requestJSON({
> +            url : kimchi.url + 'storagepools/' + sp + '/storagevolumes',
> +            type : 'POST',
> +            data : fd,
> +            processData : false,
> +            contentType : false,
> +            success : suc,
> +            error : err
> +        });
> +    },
> +

I am getting the following error while trying to upload a file into a 
storage pool:




After some investigation I realized it is because you set the 
contentType as false (ie, '*/*') so Kimchi server will try to return a 
HTML according to the resource.
In this case a Task resource. So it looks for a file: 
/home/alinefm/kimchi/ui/pages/Task.tmpl which does not exist and then 
the 404 error is reported.

Check src/kimchi/template.py

def can_accept_html():
     return can_accept('text/html') or \
            can_accept('application/xaml+xml') or \
            can_accept('*/*')

To solve this issue, we can change AsyncCollection 
(src/kimchi/control/base.py) to always return a JSON (see patch below)


diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py
index 6391a1a..9ab6696 100644
--- a/src/kimchi/control/base.py
+++ b/src/kimchi/control/base.py
@@ -344,7 +344,10 @@ class AsyncCollection(Collection):
          args = self.model_args + [params]
          task = create(*args)
          cherrypy.response.status = 202
-        return kimchi.template.render("Task", task)
+        cherrypy.response.headers['Content-Type'] = \
+            'application/json;charset=utf-8'
+        return json.dumps(task, indent=2, separators=(',', ':'))


After applying this patch I was able to upload a file into a storage 
pool, but the dialog is not closed.
I need to manually close the dialog and check the storage volumes inside 
the pool. (And any more errors on firebug =])

Let me know if you have any other idea to solve this content type issue. 
Otherwise, I will send the above patch for review.

> +    /**
> +     * Add a volume to a given storage pool by URL.
> +     */
> +    downloadVolumeToSP: function(settings, suc, err) {
> +        var url = settings['url'];
> +        var name = settings['name'];
> +        var sp = encodeURIComponent(settings['sp']);
> +        kimchi.requestJSON({
> +            url : kimchi.url + 'storagepools/' + sp + '/storagevolumes',
> +            type : 'POST',
> +            data : JSON.stringify({
> +                name: name,
> +                url: url
> +            }),
> +            contentType : 'application/json',
> +            dataType : 'json',
> +            success : suc,
> +            error : err
> +        });
>       }
>   };
> diff --git a/ui/js/src/kimchi.sp_add_volume_main.js b/ui/js/src/kimchi.sp_add_volume_main.js
> new file mode 100644
> index 0000000..e4f25fc
> --- /dev/null
> +++ b/ui/js/src/kimchi.sp_add_volume_main.js

Name the fie as storagepool_add_volume_main.js so we use the same 
pattern already in use.

> @@ -0,0 +1,152 @@
> +/*
> + * Project Kimchi
> + *
> + * Copyright IBM, Corp. 2013-2014
> + *
> + * 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.
> + */
> +kimchi.sp_add_volume_main = function() {
> +    // download from remote server or upload from local file
> +    var type = 'download';
> +
> +    var addButton = $('#sp-add-volume-button');
> +
> +    $('input.volume-type').change(function(e) {
> +        $('.volume-input').prop('disabled', true);
> +        $('.volume-input.' + this.value).prop('disabled', false);
> +        type = this.value;
> +    });
> +
> +    var volumeName;
> +    var taskID = -1;
> +
> +    var onAccepted = function() {
> +        if(inited[taskID]) {
> +            return;
> +        }
> +        inited[taskID] = true;
> +        kimchi.window.close();
> +
> +        kimchi.topic('kimchi/volumeDownloadStarted').publish({
> +            sp: kimchi.selectedSP
> +        });
> +    };
> +    var inited = {};
> +
> +    var onError = function(result) {
> +        if (result['message']) {
> +            var errText = result['message'];
> +        }
> +        else {
> +            var errText = result['responseJSON']['reason'];
> +        }
> +        result && kimchi.message.error(errText);
> +        var volumeName = result['target_uri'].split('/').pop();
> +        var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]');
> +        $('.progress-status', volumeBox).text(i18n['KCHPOOL6016M']);
> +    };
> +
> +    var fetchRemoteFile = function() {
> +        var volumeURL = $('#volume-remote-url').val();
> +        volumeName = volumeURL.split(/(\\|\/)/g).pop();
> +        kimchi.downloadVolumeToSP({
> +            sp: kimchi.selectedSP,
> +            name: volumeName,
> +            url: volumeURL
> +        }, function(resp) {
> +            taskID = resp['id'];
> +            onAccepted();
> +        }, onError);
> +    };
> +
> +    var onProgress = function(resp) {
> +        var sizeArray = resp['message'].split('/');
> +        var downloaded = sizeArray[0];
> +        var percent = 0;
> +        if(isNaN(downloaded)) {
> +            downloaded = 0;
> +        }
> +        else {
> +            var total = sizeArray[1];
> +            percent = downloaded / total * 100;
> +        }
> +        var formatted = kimchi.formatMeasurement(downloaded);
> +        var size = formatted['v'].toFixed(1) + formatted['s'];
> +        var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]');
> +        $('.volume-progress', volumeBox).removeClass('hidden');
> +        $('.progress-bar-inner', volumeBox).css({
> +            width: percent + '%'
> +        });
> +        $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']);
> +        $('.progress-downloaded', volumeBox).text(size);
> +    };
> +
> +    var onTaskResponse = function(result) {
> +        var taskStatus = result['status'];
> +        switch(taskStatus) {
> +        case 'running':
> +            onProgress(result);
> +            setTimeout(function() {
> +                trackTask(result['id']);
> +            }, 1500);
> +            break;
> +        case 'finished':
> +            taskID = -1;
> +            var volumeName = result['target_url'].split('/').pop();

It should be target_uri

> +            var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]');
> +            $('.progress-status', volumeBox).text(i18n['KCHPOOL6015M']);
> +            break;
> +        case 'failed':
> +            taskID = -1;
> +            onError(result);
> +            break;
> +        default:
> +            break;
> +        }
> +    };
> +
> +    var trackTask = function(task) {
> +        kimchi.getTask(task, onTaskResponse, function(resp) {});
> +    };
> +
> +    var uploadFile = function() {
> +        var blobFile = $('#volume-input-file')[0].files[0];
> +        var fileName = blobFile.name;
> +        var fd = new FormData();
> +        fd.append('name', fileName);
> +        fd.append('file', blobFile);
> +        kimchi.uploadVolumeToSP({
> +            sp: kimchi.selectedSP,
> +            formData: fd
> +        }, function(resp) {
> +        });
> +    };
> +
> +    $(addButton).on('click', function(event) {
> +        $(this).prop('disabled', true);
> +        if(type === 'download') {
> +            fetchRemoteFile();
> +        }
> +        else {
> +            uploadFile();
> +        }
> +        event.preventDefault();
> +    });
> +
> +    kimchi.topic('kimchi/volumesListed').subscribe(function() {
> +        var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]');
> +        $(volumeBox).removeClass('hidden');
> +        $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']);
> +        taskID > -1 && trackTask(taskID);
> +    });
> +};
> diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js
> index ae3f963..56252a8 100644
> --- a/ui/js/src/kimchi.storage_main.js
> +++ b/ui/js/src/kimchi.storage_main.js
> @@ -135,6 +135,12 @@ kimchi.storageBindClick = function() {
>               }
>           });
>
> +        $('.pool-add-volume').on('click', function(event) {
> +            var poolName = $(this).data('name');
> +            kimchi.selectedSP = poolName;
> +            kimchi.window.open('sp-add-volume.html');
> +        });
> +
>           $('.storage-action').on('click', function() {
>               var storage_action = $(this);
>               var deleteButton = storage_action.find('.pool-delete');
> @@ -149,10 +155,6 @@ kimchi.storageBindClick = function() {
>               $("#logicalPoolExtend").dialog("option", "poolName", $(this).data('name'));
>               $("#logicalPoolExtend").dialog("open");
>           });
> -
> -        $('#volume-doAdd').on('click', function() {
> -            kimchi.window.open('storagevolume-add.html');
> -        });
>       }
>
>       $('.storage-li').on('click', function(event) {
> @@ -195,6 +197,9 @@ kimchi.doListVolumes = function(poolObj) {
>               poolObj.removeClass('in');
>               kimchi.changeArrow(handleArrow);
>               slide.slideDown('slow');
> +            setTimeout(function() {
> +                kimchi.topic('kimchi/volumesListed').publish();
> +            }, 1500);
>           }
>       }, function(err) {
>           kimchi.message.error(err.responseJSON.reason);
> @@ -255,6 +260,12 @@ kimchi.storage_main = function() {
>       }
>       kimchi.doListStoragePools();
>       kimchi.initLogicalPoolExtend();
> +
> +    kimchi.topic('kimchi/volumeDownloadStarted').subscribe(function(data) {
> +        var sp = data['sp'];
> +        var poolNode = $('.storage-li[data-name="' + sp + '"]');
> +        kimchi.doListVolumes(poolNode);
> +    });
>   }
>
>   kimchi.changeArrow = function(obj) {
> diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
> index d920ae2..b3bca53 100644
> --- a/ui/pages/i18n.json.tmpl
> +++ b/ui/pages/i18n.json.tmpl
> @@ -169,6 +169,9 @@
>       "KCHPOOL6011M": "$_("No available partitions found.")",
>       "KCHPOOL6012M": "$_("This storage pool is not persistent. Instead of deactivate, this action will permanently delete it. Would you like to continue?")",
>       "KCHPOOL6013M": "$_("Unable to retrieve partitions information.")",
> +    "KCHPOOL6014M": "$_("Downloading...")",
> +    "KCHPOOL6015M": "$_("Done!")",
> +    "KCHPOOL6016M": "$_("Failed!")",
>
>       "KCHVMSTOR0001E": "$_("CDROM path need to be a valid local path and cannot be blank.")",
>       "KCHVMSTOR0002E": "$_("Disk pool or volume cannot be blank.")"
> diff --git a/ui/pages/sp-add-volume.html.tmpl b/ui/pages/sp-add-volume.html.tmpl
> new file mode 100644
> index 0000000..148a17b
> --- /dev/null
> +++ b/ui/pages/sp-add-volume.html.tmpl
> @@ -0,0 +1,80 @@
> +#*
> + * Project Kimchi
> + *
> + * Copyright IBM, Corp. 2014
> + *
> + * Authors:
> + *  Hongliang Wang <hlwang at linux.vnet.ibm.com>
> + *
> + * 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.
> + *#
> +#unicode UTF-8
> +#import gettext
> +#from kimchi.cachebust import href
> +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang)
> +#silent _ = t.gettext
> +#silent _t = t.gettext
> +<div id="sp-add-volume-window" class="window">
> +    <form id="form-sp-add-volume">
> +        <header class="window-header">
> +            <h1 class="title">$_("Add a Volume to Storage Pool")</h1>
> +            <div class="close">X</div>
> +        </header>
> +        <section>
> +            <div class="content">
> +                <div class="form-section">
> +                    <h2>
> +                        <input type="radio" id="volume-type-download" class="volume-type" name="volumeType" value="download" checked="checked" />
> +                        <label for="volume-type-download">
> +                            $_("Fetch from remote URL")
> +                        </label>
> +                    </h2>
> +                    <div class="field">
> +                        <p class="text-help">
> +                            $_("Enter the remote URL here.")
> +                        </p>
> +                        <div class="textbox-wrapper">
> +                            <input type="text" id="volume-remote-url" class="text volume-input download" name="volumeRemoteURL" />
> +                        </div>
> +                    </div>
> +                </div>
> +                <div class="form-section">
> +                    <h2>
> +                        <input type="radio" id="volume-type-upload" class="volume-type" name="volumeType" value="upload"/>
> +                        <label for="volume-type-upload">
> +                        $_("Upload an file")
> +                        </label>
> +                    </h2>
> +                    <div class="field">
> +                        <p class="text-help">
> +                            $_("Choose the file you want to upload.")
> +                        </p>
> +                        <div class="textbox-wrapper">
> +                            <input type="file" class="volume-input upload" id="volume-input-file" name="volumeLocalFile" disabled="disabled" />
> +                        </div>
> +                    </div>
> +                </div>
> +            </div>
> +        </section>
> +        <footer>
> +            <div class="btn-group">
> +                <button type="submit" id="sp-add-volume-button" class="btn-normal">
> +                    <span class="text">$_("OK")</span>
> +                </button>
> +            </div>
> +        </footer>
> +    </form>
> +</div>
> +<script type="text/javascript">
> +    kimchi.sp_add_volume_main();
> +</script>
> diff --git a/ui/pages/tabs/storage.html.tmpl b/ui/pages/tabs/storage.html.tmpl
> index 87205bd..d5aceef 100644
> --- a/ui/pages/tabs/storage.html.tmpl
> +++ b/ui/pages/tabs/storage.html.tmpl
> @@ -82,6 +82,7 @@
>                       <div class="popover actionsheet right-side" style="width: 250px">
>                           <button class="button-big pool-deactivate" data-stat="{state}" data-name="{name}" data-persistent="{persistent}"><span class="text">$_("Deactivate")</span></button>
>                           <button class="button-big pool-activate" data-stat="{state}" data-name="{name}"><span class="text">$_("Activate")</span></button>
> +                        <button class="button-big pool-add-volume" data-stat="{state}" data-name="{name}"><span class="text">$_("Add Volume")</span></button>
>                           <button class="button-big pool-extend {enableExt}" data-stat="{state}" data-name="{name}"><span class="text">$_("Extend")</span></button>
>                           <button class="button-big red pool-delete" data-stat="{state}" data-name="{name}"><span class="text">$_("Undefine")</span></button>
>                       </div>
> @@ -98,11 +99,20 @@
>       </li>
>   </script>
>   <script id="volumeTmpl" type="html/text">
> -        <div class="volume-box white-box">
> +        <div class="volume-box white-box" data-volume-name="{name}">
>               <div class="storage-icon volume-default icon-{format} ">
>               </div>
>               <div class="volume-title">
>                   <div class="volume-name" title="{name}">{name}</div>
> +                <div class="volume-progress hidden">
> +                    <div class="progress-bar-outer">
> +                        <div class="progress-bar-inner"></div>
> +                    </div>
> +                    <div class="progress-label">
> +                        <span class="progress-status"></span>
> +                        <span class="progress-downloaded"></span>
> +                    </div>
> +                </div>
>               </div>
>               <div class="volume-setting">
>               </div>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ovirt.org/pipermail/kimchi-devel/attachments/20140908/e7e10b8c/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: gdadcjdd.
Type: image/jpeg
Size: 52253 bytes
Desc: not available
URL: <http://lists.ovirt.org/pipermail/kimchi-devel/attachments/20140908/e7e10b8c/attachment.jpe>


More information about the Kimchi-devel mailing list