diff --git a/controllers/action.py b/controllers/action.py index 7a6dcd9..36929ec 100644 --- a/controllers/action.py +++ b/controllers/action.py @@ -88,6 +88,7 @@ def __pause(vm): def __reboot(vm): vm.reboot() + def __delete(vm): vm.delete() @@ -118,16 +119,28 @@ def __power_off(vm): def __restore_snapshot(vmid): + auth = b64encode(dumps(dict(u=session.username, p=session.password))) snapshot_id = request.vars.snapshot_id pvars = dict(auth=auth, vmid=vmid, snapshot_id=snapshot_id) scheduler.queue_task(task_restore_snapshot, timeout=600, pvars=pvars) +def __delete_snapshot(vmid): + auth = b64encode(dumps(dict(u=session.username, p=session.password))) + snapshot_id = request.vars.snapshot_id + pvars = dict(auth=auth, vmid=vmid, snapshot_id=snapshot_id) + logger.debug(pvars) + scheduler.queue_task(task_delete_snapshot, timeout=600, pvars=pvars) + + def __get_console_url(vm): console_type = config.get('misc', 'console_type') consoleurl = vm.get_console_url(console_type=console_type) - return '{0}'.format(consoleurl) + if request.vars.urlonly: + return consoleurl + else: + return '{0}'.format(consoleurl) def __migrate(vmid): @@ -167,6 +180,8 @@ def index(): message = __power_off(vm) elif action == 'restore-snapshot': message = __restore_snapshot(vmid) + elif action == 'delete-snapshot': + message = __delete_snapshot(vmid) elif action == 'migrate': message = __migrate(vmid) elif action == 'add-virtual-disk': @@ -184,7 +199,6 @@ def __add_virtual_disk(vmid, size): try: db.virtual_disk_requests.insert(user=session.username, vmid=vmid, disk_size=int(size), status=0) - return jsonify() except Exception as e: logger.exception(e.message or str(e.__class__)) return jsonify(status='fail', message=e.message or str(e.__class__)) @@ -451,13 +465,21 @@ def __attach_vm_public_ip(): @auth.requires(user_is_project_admin) def handle_clone_request(): try: - conn = Baadal.Connection(_authurl, _tenant, session.username, - session.password) - row = db(db.clone_requests.id == request.vars.id).select()[0] - vm = conn.find_baadal_vm(id=row.vm_id) - vm.clone() - row.update_record(status=1) - db.commit() + if request.vars.action == 'approve': + row = db(db.clone_requests.id == request.vars.id).select()[0] + row.update_record(status=REQUEST_STATUS_PROCESSING) + auth = b64encode(dumps(dict(u=session.username, + p=session.password))) + scheduler.queue_task(task_clone_vm, timeout=600, + pvars={'reqid': row.id, 'auth': auth}) + db.commit() + return jsonify(message='Request successfully approved') + elif request.vars.action == 'reject': + db(db.clone_requests.id == request.vars.id).delete() + db.commit() + return jsonify(message='Request successfully deleted') + else: + raise HTTP(400) except Exception as e: message = e.message or str(e.__class__) logger.error(message) diff --git a/controllers/admin.py b/controllers/admin.py index b65769e..44fd439 100644 --- a/controllers/admin.py +++ b/controllers/admin.py @@ -124,11 +124,14 @@ def all_vms(): vm_properties = vm.properties() image_id = vm_properties['image']['id'] if not images.has_key(image_id): - image = conn.find_image(id=image_id) - meta = image.metadata - images[image_id] = ' '.join([meta['os_name'], - meta['os_version'], meta['os_arch'], - meta['os_edition'], meta['disk_size']]) + try: + image = conn.find_image(id=image_id) + meta = image.metadata + images[image_id] = ' '.join([meta['os_name'], + meta['os_version'], meta['os_arch'], + meta['os_edition'], meta['disk_size']]) + except NotFound: + images[image_id] = 'Image not found' vm_properties['image']['info'] = images[image_id] # snapshots = vm.properties()['snapshots'] # STR = 'created' @@ -281,6 +284,7 @@ def disk_requests(): elif request.extension == 'json': response = [] spurious_requests = [] + cache = {} try: rows = db(db.virtual_disk_requests.status == 0).select() conn = Baadal.Connection(_authurl, _tenant, session.username, @@ -288,7 +292,10 @@ def disk_requests(): for row in rows: try: cr = {} - vm = conn.find_baadal_vm(id=row.vmid) + if not cache.has_key(row.vmid): + vm = conn.find_baadal_vm(id=row.vmid) + cache[row.vmid] = vm.name + cr['vm_name'] = cache[row.vmid] cr['id'] = row.id cr['request_time'] = str(row.request_time) cr['vm_name'] = vm.name diff --git a/controllers/post_request.py b/controllers/post_request.py index 58cb621..ccadc44 100644 --- a/controllers/post_request.py +++ b/controllers/post_request.py @@ -30,12 +30,13 @@ def new_vm(): request_time=int(time.time()), state=vm_state ) - user_email = ldap.fetch_user_info(session.username)['user_email'] context = Storage() - context.username = session.username + user_info = ldap.fetch_user_info(session.username) + context.username = user_info['user_name'] + user_email = user_info['user_email'] context.user_email = user_email context.vm_name = request.vars.vm_name - context.support_email = mail_support + context.mail_support = mail_support if mailer.send(mailer.MailTypes.VMRequest, user_email, context): db.commit() diff --git a/controllers/user.py b/controllers/user.py index e2f4883..e56dddd 100644 --- a/controllers/user.py +++ b/controllers/user.py @@ -1,3 +1,6 @@ +from novaclient.exceptions import NotFound + + @auth.requires_login() def index(): return dict() @@ -25,11 +28,14 @@ def my_vms(): vm_properties = vm.properties() image_id = vm_properties['image']['id'] if not images.has_key(image_id): - image = conn.find_image(id=image_id) - meta = image.metadata - images[image_id] = ' '.join([meta['os_name'], - meta['os_version'], meta['os_arch'], - meta['os_edition'], meta['disk_size']]) + try: + image = conn.find_image(id=image_id) + meta = image.metadata + images[image_id] = ' '.join([meta['os_name'], + meta['os_version'], meta['os_arch'], + meta['os_edition'], meta['disk_size']]) + except NotFound: + images[image_id] = 'Image not found' vm_properties['image']['info'] = images[image_id] response.append(vm_properties) # snapshots = vm.properties()['snapshots'] diff --git a/models/2_db.py b/models/2_db.py index 4411da3..8c8490f 100644 --- a/models/2_db.py +++ b/models/2_db.py @@ -77,8 +77,7 @@ Field('id', 'integer'), Field('user', 'string', required=True), Field('vmid', 'string', required=True), - Field('request_time', 'integer', - requires=IS_INT_IN_RANGE(0, 1)), + Field('request_time', 'datetime'), Field('status', 'integer') ) diff --git a/models/action/tasks.py b/models/action/tasks.py index f3a5f76..62b0320 100644 --- a/models/action/tasks.py +++ b/models/action/tasks.py @@ -29,13 +29,13 @@ def task_create_vm(reqid, auth): try: req.update_record(state=REQUEST_STATUS_APPROVED) context = Storage() - context.username = auth.u - context.vm_name = name - context.mail_support = mail_support user_info = ldap.fetch_user_info(auth.u) + context.username = user_info['user_name'] context.user_email = user_info['user_email'] + context.vm_name = name + context.mail_support = mail_support context.gateway_server = gateway_server - context.req_time = seconds_to_localtime(req.request_time) + context.request_time = seconds_to_localtime(req.request_time) logger.info('sending mail') mailer.send(mailer.MailTypes.VMCreated, context.user_email, context) @@ -98,15 +98,18 @@ def task_restore_snapshot(auth, vmid, snapshot_id): logger.exception(e) -def task_clone_vm(auth, vmid): +def task_clone_vm(auth, reqid): auth = Storage(loads(b64decode(auth))) + req = db(db.clone_requests.id == reqid).select()[0] try: conn = Baadal.Connection(_authurl, _tenant, auth.u, auth.p) - vm = conn.find_baadal_vm(id=vmid) + vm = conn.find_baadal_vm(id=req.vm_id) clone = vm.clone() - logger.info('VM Cloned: VMID %s, clone_id %s' % (vmid, clone)) + logger.info('VM Cloned: VMID %s, clone_id %s' % (vm_id, clone)) + req.update_record(status=REQUEST_STATUS_APPROVED) except Exception as e: logger.exception(e) + req.update_record(status=REQUEST_STATUS_POSTED) def task_resize_vm(auth, reqid): auth = Storage(loads(b64decode(auth))) @@ -128,4 +131,13 @@ def task_resize_vm(auth, reqid): conn.close() - +def task_delete_snapshot(auth, vmid, snapshot_id): + auth = Storage(loads(b64decode(auth))) + try: + conn = Baadal.Connection(_authurl, _tenant, auth.u, auth.p) + image = conn.find_image(id=snapshot_id) + status = image.delete() + logger.info('Snapshot deleted: VMID %s, snapshot_id %s' % \ + (vmid, snapshot_id)) + except Exception as e: + logger.exception(e) diff --git a/modules/Baadal.py b/modules/Baadal.py index 09b5728..a682060 100644 --- a/modules/Baadal.py +++ b/modules/Baadal.py @@ -130,9 +130,7 @@ def clone(self, clone_name=None, full=False): defaults to Full (optional) :return: """ - # create a snapshot of the machine - # create a new vm using the newly created snapshot - # delete the snapshot + clone_name = clone_name or self.server.name + '_clone' flavor_id = self.server.flavor['id'] networks = self.get_networks().keys() @@ -140,33 +138,27 @@ def clone(self, clone_name=None, full=False): self.__conn.neutron.list_networks(name=net)['networks'][0]['id'] for net in networks] nics = [{'net-id': netid for netid in network_ids}] - snapshot_id = self.server.create_image("temp") - image = self.__conn.nova.images.find(id=snapshot_id) - while image.status != 'ACTIVE': - image = self.__conn.nova.images.find(id=snapshot_id) - pass + image = self.__conn.nova.images.find(id=self.server.image['id']) + flavor = self.__conn.nova.flavors.find(id=flavor_id) + clone = self.server.manager.create(clone_name, image, + flavor, nics=nics, + security_groups=networks, + meta=self.server.metadata) + while clone.status != 'ACTIVE': + clone = clone.manager.find(id=clone.id) else: - flavor = self.__conn.nova.flavors.find(id=flavor_id) - clone = self.server.manager.create(clone_name, image, - flavor, nics=nics, - security_groups=networks, - meta=self.server.metadata) - while clone.status != 'ACTIVE': - clone = clone.manager.find(id=clone.id) - else: - image.delete() - attached_disks = self.get_attached_disks() - for i in attached_disks: - volid = i['id'] - if full: - volume_clone = self.__conn.cinder.volumes.create( - i['size'], source_volid=i['id']) - volid = volume_clone.id - while volume_clone.status != 'available': - volume_clone = self.__conn.cinder.volumes.get( - volume_clone.id) - self.__conn.nova.volumes.create_server_volume( - clone.id, volid, i['path']) + attached_disks = self.get_attached_disks() + for i in attached_disks: + volid = i['id'] + if full: + volume_clone = self.__conn.cinder.volumes.create( + i['size'], source_volid=i['id']) + volid = volume_clone.id + while volume_clone.status != 'available': + volume_clone = self.__conn.cinder.volumes.get( + volume_clone.id) + self.__conn.nova.volumes.create_server_volume( + clone.id, volid, i['path']) return clone def create_snapshot(self, snapshot_name=None): diff --git a/modules/BaadalMailer.py b/modules/BaadalMailer.py index f8e0eed..3c70ee1 100644 --- a/modules/BaadalMailer.py +++ b/modules/BaadalMailer.py @@ -25,7 +25,7 @@ class VMRequest(object): class ApprovalReminder(object): subject = 'Request waiting for your approval' - mailbody = "Dear {0[approverName]},\n\n{0[userName]} has made a "\ + mailbody = "Dear {0[approver]},\n\n{0[user]} has made a "\ "'{0[requestType]}' request on {0[requestTime]}. It is "\ "waiting for your approval." @@ -37,16 +37,14 @@ class IPRequest(object): class VMCreated(object): subject = 'Your BaadalVM has been created' - mailbody = "Dear {0[username]},\n\nThe VM {0[vm_name]}, requested"\ - " at {0[request_time]} is successfully created and is now" \ - " available for use. Following operations are allowed on" \ - " VM:\n 1. Start\n2. Stop\n3. Pause\n4. Resume\n5. Destroy" \ - " \n6. Delete\n\n Default credentials for VM is as follows:" \ - " \nUsername:root/baadalservervm/baadaldesktopvm\n" \ - " Password:baadal\n\n To access VM using assigned private" \ - " IP; SSH to baadal gateway machine using your GCL" \ - " credential .\n username@{0[gateway_server]}\n For other"\ - " details, Please login to Baadal web interface." + mailbody = ("Dear {0[username]},\n\nThe VM {0[vm_name]}, requested" + " at {0[request_time]} is successfully created and is now" + " available for use. To access your VM from within the web" + " browser, login to Baadal using your credentials and click" + " on the open console icon, which looks like >_\n" + " Default credentials for VM is as follows:" + " \nUsername:root/baadalservervm/baadaldesktopvm\n" + " Password:baadal\n\n") class TaskComplete(object): subject = '{0[taskType]} task successful' diff --git a/static/css/baadal-custom.css b/static/css/baadal-custom.css index 563730c..e5f84f3 100644 --- a/static/css/baadal-custom.css +++ b/static/css/baadal-custom.css @@ -14,9 +14,51 @@ textarea { } #vm-settings-modal .panel-footer>span { - display: none; + display: none; } #disk-size-input input { - display: inline-block; -} \ No newline at end of file + display: inline-block; +} +/* Sticky footer styles +-------------------------------------------------- */ +html { + position: relative; + min-height: 100%; +} +body { + /* Margin bottom by footer height */ + margin-bottom: 60px; +} +.footer { + position: absolute; + bottom: 0; + width: 100%; + /* Set the fixed height of the footer here */ + height: 60px; + background-color: #222; +} + + +/* Custom page CSS +-------------------------------------------------- */ +/* Not required for template or sticky footer method. */ + +body > .container { + padding: 60px 15px 0; +} +.container .text-muted { + margin: 20px 0; +} + +.footer > .container { + padding-right: 15px; + padding-left: 15px; + text-align: center; + color: #9d9d9d; +} + +code { + font-size: 80%; +} + diff --git a/static/js/baadal.js b/static/js/baadal.js index c3b3ffe..b386308 100644 --- a/static/js/baadal.js +++ b/static/js/baadal.js @@ -8,7 +8,7 @@ jQuery var baadalApp = (function ($) { 'use strict'; baadalApp = {}; - + baadalApp.requestAction = function (data) { return $.ajax({ url: '/baadal/action/index.json', @@ -16,7 +16,7 @@ var baadalApp = (function ($) { data: data }); }; - + baadalApp.serialize = function (temp) { var obj = {}, i, curr; for (i = temp.length - 1; i >= 0; i -= 1) { @@ -202,12 +202,40 @@ var baadalApp = (function ($) { data = {}, promise; + $(document.body).on('click', '#btn-disk-request', function (e) { + e.preventDefault(); + data.disksize = document.getElementById('disksize').value; + promise = baadalApp.requestAction(data); + promise.then(function (response) { + console.log(response); + if (response.message) { + $('#modal-info-message').html(response.message).fadeIn().siblings().hide(); + } + }); + }); + + // Event handler for click of open-console button + $this.table.on('click', '.btn-console', function (e) { + var vmid = $(this).closest('tr').data('vmid'); + $.ajax({ + url: '/baadal/action/index.json', + data: { + vmid: vmid, + action: 'get-console-url', + urlonly: true + }, + success: function(response) { + window.open(response.message); + } + }); + }); + // Event handler for click of settings button $this.table.on('click', '.btn-settings', function (e) { var tr = this.closest('tr'), context = $this.$dt.row(tr).data(), html; - context['ip-addresses'] = baadalApp.ipObjectToString(context.addresses, true); + context['ip-addresses'] = baadalApp.ipObjectToString(context.addresses, true); html = $this.dialogTmpl(context); $(document.body).prepend(html); $this.$modal = $('#' + $this.modalid).modal('show'); @@ -225,11 +253,18 @@ var baadalApp = (function ($) { $(document.body).on('click', '#btn-resize-request', function (e) { e.preventDefault(); data.vmid = $('#vmid').val(); - data.new_flavor = $('#new_flavor').value(); - promise = $.post({ + data.new_flavor = $('#new_flavor').val(); + promise = $.ajax({ + type: 'post', url: '/baadal/post_request/request_resize.json', data: data }); + promise.then(function (response) { + console.log(response); + if (response.status === 'success') { + $('#modal-info-message').html('Resize request has been successfully posted.').fadeIn().siblings().hide(); + } + }); }); // Event handler for click of action buttons on settings dialog @@ -252,14 +287,6 @@ var baadalApp = (function ($) { break; case 'add-virtual-disk': $('#disk-size-input').fadeIn().siblings().hide(); - $('#btn-disk-request').on('click', function (e) { - e.preventDefault(); - promise = baadalApp.requestAction({ - vmid: vmid, - action: 'add-virtual-disk', - disksize: document.getElementById('disksize').value - }); - }); break; case 'resize': promise2 = $.getJSON('/baadal/ajax/configs.json'); @@ -286,7 +313,7 @@ var baadalApp = (function ($) { }); // Event handler for snapshot-restore buttons - $(document.body).on('click', '#' + $this.modalid + '.snapshot-restore', function (e) { + $(document.body).on('click', '#' + $this.modalid + ' .snapshot-restore', function (e) { var vmid = document.getElementById('vmid').value; $.ajax({ url: '/baadal/action/index.json', @@ -295,10 +322,34 @@ var baadalApp = (function ($) { vmid: vmid, action: 'restore-snapshot', snapshot_id: this.closest('tr').getAttribute('data-snapshot-id') + }, + success: function(response) { + if (response.message) { + $('#modal-info-message').html(response.message).fadeIn().siblings().hide(); + } + } + }); + }); + + $(document.body).on('click', '.snapshot-delete', function (e) { + var vmid = document.getElementById('vmid').value; + $.ajax({ + url: '/baadal/action/index.json', + type: 'post', + data: { + vmid: vmid, + action: 'delete-snapshot', + snapshot_id: this.closest('tr').getAttribute('data-snapshot-id') + }, + success: function(response) { + if (response.message) { + $('#modal-info-message').html(response.message).fadeIn().siblings().hide(); + } } }); }); + $(document.body).on('click', '#fetch-snapshots', function (e) { var $tr = $(this), vmid = document.getElementById('vmid').value; diff --git a/views/admin/clone_requests.html b/views/admin/clone_requests.html index d8ef682..a4270cf 100644 --- a/views/admin/clone_requests.html +++ b/views/admin/clone_requests.html @@ -62,6 +62,7 @@

Pending VM Clone Requests

$requests.on('click','.btn-action', function(e) { + this.disabled = true; var id = this.closest('tr').getAttribute('data-reqid'); var action = this.getAttribute('data-action'); $.ajax({ @@ -80,7 +81,7 @@

Pending VM Clone Requests

}, 'success': function(response) { if(response.status == 'success') { - $('#success-message').fadeIn(1000, function () { + $('#success-message').html(response.message).fadeIn(1000, function () { $table.ajax.reload(); $(this).fadeOut(); }); diff --git a/views/admin/disk_requests.html b/views/admin/disk_requests.html index 1d4e18f..073ae15 100644 --- a/views/admin/disk_requests.html +++ b/views/admin/disk_requests.html @@ -68,7 +68,7 @@

Pending Virtual Disk Requests

'type': 'post', 'dataType' : 'json', 'data': { - 'id':id, + 'id': id, 'action': action }, 'error' : function(response) { @@ -84,7 +84,9 @@

Pending Virtual Disk Requests

$(this).fadeOut(); }); } else { - $('#error-message').fadeIn(); + $('#error-message').fadeIn(1000, function () { + $(this).fadeOut(); + }); } } }); diff --git a/views/admin/index.html b/views/admin/index.html index 4922a1f..0d9326e 100644 --- a/views/admin/index.html +++ b/views/admin/index.html @@ -33,6 +33,10 @@

All Virtual Machines

+ + + + @@ -45,7 +49,7 @@

All Virtual Machines