Browse Source

重构代码,使其更好的融合单机与链接版本

James Iter 9 years ago
parent
commit
d2453a10a2

+ 63 - 30
api/disk.py

@@ -8,12 +8,12 @@ from uuid import uuid4
 import jimit as ji
 import jimit as ji
 
 
 from models import Guest, DiskState
 from models import Guest, DiskState
-from models.initialize import app, dev_table
-from models import Database as db
+from models.initialize import dev_table
 from models import Config
 from models import Config
 from models import Disk
 from models import Disk
 from models import Rules
 from models import Rules
 from models import Utils
 from models import Utils
+from models.status import JimVEdition
 
 
 from base import Base
 from base import Base
 
 
@@ -46,9 +46,17 @@ def r_create():
     args_rules = [
     args_rules = [
         Rules.DISK_SIZE.value,
         Rules.DISK_SIZE.value,
         Rules.REMARK.value,
         Rules.REMARK.value,
+        Rules.DISK_ON_HOST.value,
         Rules.QUANTITY.value
         Rules.QUANTITY.value
     ]
     ]
 
 
+    config = Config()
+    config.id = 1
+    config.get()
+
+    if config.jimv_edition == JimVEdition.hyper_convergence.value:
+        request.json['on_host'] = 'shared_storage'
+
     try:
     try:
         ji.Check.previewing(args_rules, request.json)
         ji.Check.previewing(args_rules, request.json)
 
 
@@ -56,7 +64,8 @@ def r_create():
         ret['state'] = ji.Common.exchange_state(20000)
         ret['state'] = ji.Common.exchange_state(20000)
 
 
         size = request.json['size']
         size = request.json['size']
-        quantity = request.json.get('quantity')
+        quantity = request.json['quantity']
+        on_host = request.json['on_host']
 
 
         if size < 1:
         if size < 1:
             ret['state'] = ji.Common.exchange_state(41255)
             ret['state'] = ji.Common.exchange_state(41255)
@@ -69,19 +78,28 @@ def r_create():
             disk.size = size
             disk.size = size
             disk.uuid = uuid4().__str__()
             disk.uuid = uuid4().__str__()
             disk.remark = request.json.get('remark', '')
             disk.remark = request.json.get('remark', '')
+            disk.on_host = on_host
             disk.sequence = -1
             disk.sequence = -1
             disk.format = 'qcow2'
             disk.format = 'qcow2'
 
 
-            config = Config()
-            config.id = 1
-            config.get()
-
             disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
             disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
 
 
-            message = {'action': 'create_disk', 'glusterfs_volume': config.dfs_volume,
-                       'image_path': disk.path, 'size': disk.size, 'uuid': disk.uuid}
+            message = {
+                '_object': 'disk',
+                'action': 'create',
+                'uuid': disk.uuid,
+                'jimv_edition': config.jimv_edition,
+                'dfs': config.dfs,
+                'dfs_volume': config.dfs_volume,
+                'hostname': disk.on_host,
+                'image_path': disk.path,
+                'size': disk.size
+            }
+
+            if disk.on_host == 'shared_storage':
+                message['hostname'] = Guest.get_lightest_host()['hostname']
 
 
-            db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+            Guest.emit_instruction(message=json.dumps(message, ensure_ascii=False))
 
 
             disk.create()
             disk.create()
 
 
@@ -106,11 +124,6 @@ def r_resize(uuid, size):
         disk.uuid = uuid
         disk.uuid = uuid
         disk.get_by('uuid')
         disk.get_by('uuid')
 
 
-        used = True
-
-        if disk.guest_uuid.__len__() != 36:
-            used = False
-
         ret = dict()
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         ret['state'] = ji.Common.exchange_state(20000)
 
 
@@ -118,21 +131,30 @@ def r_resize(uuid, size):
             ret['state'] = ji.Common.exchange_state(41257)
             ret['state'] = ji.Common.exchange_state(41257)
             return ret
             return ret
 
 
-        message = {'action': 'resize_disk', 'size': int(size), 'guest_uuid': disk.guest_uuid,
-                   'disk_uuid': disk.uuid, 'passback_parameters': {'size': size}}
+        config = Config()
+        config.id = 1
+        config.get()
 
 
-        if used:
+        message = {
+            '_object': 'disk',
+            'action': 'resize',
+            'uuid': disk.uuid,
+            'guest_uuid': disk.guest_uuid,
+            'jimv_edition': config.jimv_edition,
+            'size': int(size),
+            'dfs_volume': config.dfs_volume,
+            'hostname': disk.on_host,
+            'image_path': disk.path,
+            'passback_parameters': {'size': size}
+        }
+
+        if disk.on_host == 'shared_storage':
+            message['hostname'] = Guest.get_lightest_host()['hostname']
+
+        if disk.guest_uuid.__len__() == 36:
             message['device_node'] = dev_table[disk.sequence]
             message['device_node'] = dev_table[disk.sequence]
-            Guest.emit_instruction(message=json.dumps(message))
-        else:
-            config = Config()
-            config.id = 1
-            config.get()
 
 
-            message['glusterfs_volume'] = config.dfs_volume
-            message['image_path'] = disk.path
-
-            db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+        Guest.emit_instruction(message=json.dumps(message, ensure_ascii=False))
 
 
         return ret
         return ret
 
 
@@ -173,9 +195,20 @@ def r_delete(uuids):
             disk.uuid = uuid
             disk.uuid = uuid
             disk.get_by('uuid')
             disk.get_by('uuid')
 
 
-            message = {'action': 'delete_disk', 'uuid': disk.uuid,
-                       'glusterfs_volume': config.dfs_volume, 'image_path': disk.path}
-            db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+            message = {
+                '_object': 'disk',
+                'action': 'delete',
+                'uuid': disk.uuid,
+                'jimv_edition': config.jimv_edition,
+                'dfs_volume': config.dfs_volume,
+                'hostname': disk.on_host,
+                'image_path': disk.path
+            }
+
+            if disk.on_host == 'shared_storage':
+                message['hostname'] = Guest.get_lightest_host()['hostname']
+
+            Guest.emit_instruction(message=json.dumps(message, ensure_ascii=False))
 
 
         return ret
         return ret
 
 

+ 125 - 36
api/guest.py

@@ -149,25 +149,14 @@ def r_create():
                     replace('{DNS1}', config.dns1). \
                     replace('{DNS1}', config.dns1). \
                     replace('{DNS2}', config.dns2)
                     replace('{DNS2}', config.dns2)
 
 
-            # 负载最小的宿主机
-            lightest_host = None
-            for k, v in db.r.hgetall(app.config['hosts_info']).items():
-                v = json.loads(v)
-
-                if lightest_host is None:
-                    lightest_host = v
-
-                if float(lightest_host['system_load'][0]) / lightest_host['cpu'] > \
-                        float(v['system_load'][0]) / v['cpu']:
-                    lightest_host = v
-
-            create_vm_msg = {
+            message = {
+                '_object': 'guest',
                 'action': 'create',
                 'action': 'create',
+                'uuid': guest.uuid,
                 'jimv_edition': config.jimv_edition,
                 'jimv_edition': config.jimv_edition,
                 'dfs': config.dfs,
                 'dfs': config.dfs,
                 'dfs_volume': config.dfs_volume,
                 'dfs_volume': config.dfs_volume,
-                'uuid': guest.uuid,
-                'hostname': lightest_host['hostname'],
+                'hostname': Guest.get_lightest_host()['hostname'],
                 'name': guest.label,
                 'name': guest.label,
                 'template_path': os_template.path,
                 'template_path': os_template.path,
                 'disk': disk.__dict__,
                 'disk': disk.__dict__,
@@ -176,7 +165,7 @@ def r_create():
                 'passback_parameters': {'boot_jobs_id': boot_jobs_id}
                 'passback_parameters': {'boot_jobs_id': boot_jobs_id}
             }
             }
 
 
-            Guest.emit_instruction(message=json.dumps(create_vm_msg, ensure_ascii=False))
+            Guest.emit_instruction(message=json.dumps(message, ensure_ascii=False))
 
 
         return ret
         return ret
 
 
@@ -200,7 +189,16 @@ def r_reboot(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'reboot', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'reboot',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -227,7 +225,16 @@ def r_force_reboot(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'force_reboot', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'force_reboot',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -254,7 +261,16 @@ def r_shutdown(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'shutdown', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'shutdown',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -281,7 +297,16 @@ def r_force_shutdown(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'force_shutdown', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'force_shutdown',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -313,6 +338,9 @@ def r_boot(uuids):
         config.get()
         config.get()
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
             _, boot_jobs_id = guest.get_boot_jobs()
             _, boot_jobs_id = guest.get_boot_jobs()
 
 
             boot_jobs = list()
             boot_jobs = list()
@@ -338,8 +366,15 @@ def r_boot(uuids):
                     replace('{DNS1}', config.dns1). \
                     replace('{DNS1}', config.dns1). \
                     replace('{DNS2}', config.dns2)
                     replace('{DNS2}', config.dns2)
 
 
-            message = {'action': 'boot', 'uuid': uuid, 'boot_jobs': boot_jobs,
-                       'passback_parameters': {'boot_jobs_id': boot_jobs_id}}
+            message = {
+                '_object': 'guest',
+                'action': 'boot',
+                'uuid': uuid,
+                'boot_jobs': boot_jobs,
+                'hostname': guest.on_host,
+                'passback_parameters': {'boot_jobs_id': boot_jobs_id}
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -366,7 +401,16 @@ def r_suspend(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'suspend', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'suspend',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -393,7 +437,16 @@ def r_resume(uuids):
             guest.get_by('uuid')
             guest.get_by('uuid')
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'resume', 'uuid': uuid}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'resume',
+                'uuid': uuid,
+                'hostname': guest.on_host
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -427,13 +480,19 @@ def r_delete(uuids):
 
 
         # 执行删除操作
         # 执行删除操作
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
             message = {
             message = {
-                'action': 'delete_guest',
+                '_object': 'guest',
+                'action': 'delete',
                 'uuid': uuid,
                 'uuid': uuid,
                 'jimv_edition': config.jimv_edition,
                 'jimv_edition': config.jimv_edition,
                 'dfs': config.dfs,
                 'dfs': config.dfs,
-                'dfs_volume': config.dfs_volume
+                'dfs_volume': config.dfs_volume,
+                'hostname': guest.on_host
             }
             }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()
@@ -463,6 +522,10 @@ def r_attach_disk(uuid, disk_uuid):
         disk.uuid = disk_uuid
         disk.uuid = disk_uuid
         disk.get_by('uuid')
         disk.get_by('uuid')
 
 
+        config = Config()
+        config.id = 1
+        config.get()
+
         ret = dict()
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         ret['state'] = ji.Common.exchange_state(20000)
 
 
@@ -476,6 +539,12 @@ def r_attach_disk(uuid, disk_uuid):
             ret['state'] = ji.Common.exchange_state(41259)
             ret['state'] = ji.Common.exchange_state(41259)
             return ret
             return ret
 
 
+        # 判断 Guest 与 磁盘是否在同一宿主机上
+        if config.jimv_edition == status.JimVEdition.standalone.value:
+            if guest.on_host != disk.on_host:
+                ret['state'] = ji.Common.exchange_state(41260)
+                return ret
+
         # 通过检测未被使用的序列,来确定当前磁盘在目标 Guest 身上的序列
         # 通过检测未被使用的序列,来确定当前磁盘在目标 Guest 身上的序列
         disk.guest_uuid = guest.uuid
         disk.guest_uuid = guest.uuid
         disks, count = disk.get_by_filter(filter_str='guest_uuid:in:' + guest.uuid)
         disks, count = disk.get_by_filter(filter_str='guest_uuid:in:' + guest.uuid)
@@ -492,14 +561,17 @@ def r_attach_disk(uuid, disk_uuid):
 
 
         disk.state = DiskState.mounting.value
         disk.state = DiskState.mounting.value
 
 
-        config = Config()
-        config.id = 1
-        config.get()
-
         guest_xml = GuestXML(guest=guest, disk=disk, config=config)
         guest_xml = GuestXML(guest=guest, disk=disk, config=config)
 
 
-        message = {'action': 'attach_disk', 'uuid': uuid, 'xml': guest_xml.get_disk(),
-                   'passback_parameters': {'disk_uuid': disk.uuid, 'sequence': disk.sequence}}
+        message = {
+            '_object': 'guest',
+            'action': 'attach_disk',
+            'uuid': uuid,
+            'hostname': guest.on_host,
+            'xml': guest_xml.get_disk(),
+            'passback_parameters': {'disk_uuid': disk.uuid, 'sequence': disk.sequence}
+        }
+
         Guest.emit_instruction(message=json.dumps(message))
         Guest.emit_instruction(message=json.dumps(message))
         disk.update()
         disk.update()
 
 
@@ -547,8 +619,15 @@ def r_detach_disk(disk_uuid):
 
 
         guest_xml = GuestXML(guest=guest, disk=disk, config=config)
         guest_xml = GuestXML(guest=guest, disk=disk, config=config)
 
 
-        message = {'action': 'detach_disk', 'uuid': disk.guest_uuid, 'xml': guest_xml.get_disk(),
-                   'passback_parameters': {'disk_uuid': disk.uuid}}
+        message = {
+            '_object': 'guest',
+            'action': 'detach_disk',
+            'uuid': disk.guest_uuid,
+            'hostname': guest.on_host,
+            'xml': guest_xml.get_disk(),
+            'passback_parameters': {'disk_uuid': disk.uuid}
+        }
+
         Guest.emit_instruction(message=json.dumps(message))
         Guest.emit_instruction(message=json.dumps(message))
 
 
         disk.state = DiskState.unloading.value
         disk.state = DiskState.unloading.value
@@ -581,8 +660,18 @@ def r_migrate(uuids, destination_host):
         config.get()
         config.get()
 
 
         for uuid in uuids.split(','):
         for uuid in uuids.split(','):
-            message = {'action': 'migrate', 'jimv_edition': config.jimv_edition, 'uuid': uuid,
-                       'duri': 'qemu+ssh://' + destination_host + '/system'}
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
+            message = {
+                '_object': 'guest',
+                'action': 'migrate',
+                'uuid': uuid,
+                'hostname': guest.on_host,
+                'jimv_edition': config.jimv_edition,
+                'duri': 'qemu+ssh://' + destination_host + '/system'
+            }
+
             Guest.emit_instruction(message=json.dumps(message))
             Guest.emit_instruction(message=json.dumps(message))
 
 
         ret = dict()
         ret = dict()

+ 1 - 1
docs/todo.md

@@ -30,7 +30,7 @@
 > 则回收。如果不是,则直接丢弃;
 > 则回收。如果不是,则直接丢弃;
 > 2. 给宿主机替换手动指定的任意 IP。此时需要判断手动分配的 IP 是否与已用的 IP 冲突;
 > 2. 给宿主机替换手动指定的任意 IP。此时需要判断手动分配的 IP 是否与已用的 IP 冲突;
 - [x] 宿主机性能状态展现
 - [x] 宿主机性能状态展现
-- [ ] 设计、实现 Dashboard 页面
+- [x] 设计、实现 Dashboard 页面
 - [x] 重新设计创建虚拟机时的机制,虚拟机创建分配由 JimV-C 角色实现
 - [x] 重新设计创建虚拟机时的机制,虚拟机创建分配由 JimV-C 角色实现
 - [ ] 虚拟机状态变化时时展现在前端页面上
 - [ ] 虚拟机状态变化时时展现在前端页面上
 - [ ] 迁移时计算出剩余时间
 - [ ] 迁移时计算出剩余时间

+ 84 - 81
models/event_processor.py

@@ -120,105 +120,108 @@ class EventProcessor(object):
 
 
     @classmethod
     @classmethod
     def response_processor(cls):
     def response_processor(cls):
+        _object = cls.message['message']['_object']
         action = cls.message['message']['action']
         action = cls.message['message']['action']
         uuid = cls.message['message']['uuid']
         uuid = cls.message['message']['uuid']
         state = cls.message['type']
         state = cls.message['type']
         data = cls.message['message']['data']
         data = cls.message['message']['data']
         hostname = cls.message['host']
         hostname = cls.message['host']
 
 
-        if action == 'create':
-            if state == ResponseState.success.value:
-                # 系统盘的 UUID 与其 Guest 的 UUID 相同
-                cls.disk.uuid = uuid
-                cls.disk.get_by('uuid')
-                cls.disk.guest_uuid = uuid
-                cls.disk.state = DiskState.mounted.value
-                # disk_info['virtual-size'] 的单位为Byte,需要除以 1024 的 3 次方,换算成单位为 GB 的值
-                cls.disk.size = data['disk_info']['virtual-size'] / (1024 ** 3)
-                cls.disk.update()
-
-            else:
-                cls.guest.uuid = uuid
-                cls.guest.get_by('uuid')
-                cls.guest.status = GuestState.dirty.value
-                cls.guest.update()
-
-        elif action == 'migrate':
-            pass
+        if _object == 'guest':
+            if action == 'create':
+                if state == ResponseState.success.value:
+                    # 系统盘的 UUID 与其 Guest 的 UUID 相同
+                    cls.disk.uuid = uuid
+                    cls.disk.get_by('uuid')
+                    cls.disk.guest_uuid = uuid
+                    cls.disk.state = DiskState.mounted.value
+                    # disk_info['virtual-size'] 的单位为Byte,需要除以 1024 的 3 次方,换算成单位为 GB 的值
+                    cls.disk.size = data['disk_info']['virtual-size'] / (1024 ** 3)
+                    cls.disk.update()
 
 
-        elif action == 'delete_guest':
-            if state == ResponseState.success.value:
-                cls.config.id = 1
-                cls.config.get()
-                cls.guest.uuid = uuid
-                cls.guest.get_by('uuid')
-
-                if IP(cls.config.start_ip).int() <= IP(cls.guest.ip).int() <= IP(cls.config.end_ip).int():
-                    if db.r.srem(app.config['ip_used_set'], cls.guest.ip):
-                        db.r.sadd(app.config['ip_available_set'], cls.guest.ip)
-
-                if (cls.guest.vnc_port - cls.config.start_vnc_port) <= \
-                        (IP(cls.config.end_ip).int() - IP(cls.config.start_ip).int()):
-                    if db.r.srem(app.config['vnc_port_used_set'], cls.guest.vnc_port):
-                        db.r.sadd(app.config['vnc_port_available_set'], cls.guest.vnc_port)
-
-                cls.guest.delete()
-
-                # TODO: 加入是否删除使用的数据磁盘开关,如果为True,则顺便删除使用的磁盘。否则解除该磁盘被使用的状态。
-                cls.disk.uuid = uuid
+                else:
+                    cls.guest.uuid = uuid
+                    cls.guest.get_by('uuid')
+                    cls.guest.status = GuestState.dirty.value
+                    cls.guest.update()
+
+            elif action == 'migrate':
+                pass
+
+            elif action == 'delete':
+                if state == ResponseState.success.value:
+                    cls.config.id = 1
+                    cls.config.get()
+                    cls.guest.uuid = uuid
+                    cls.guest.get_by('uuid')
+
+                    if IP(cls.config.start_ip).int() <= IP(cls.guest.ip).int() <= IP(cls.config.end_ip).int():
+                        if db.r.srem(app.config['ip_used_set'], cls.guest.ip):
+                            db.r.sadd(app.config['ip_available_set'], cls.guest.ip)
+
+                    if (cls.guest.vnc_port - cls.config.start_vnc_port) <= \
+                            (IP(cls.config.end_ip).int() - IP(cls.config.start_ip).int()):
+                        if db.r.srem(app.config['vnc_port_used_set'], cls.guest.vnc_port):
+                            db.r.sadd(app.config['vnc_port_available_set'], cls.guest.vnc_port)
+
+                    cls.guest.delete()
+
+                    # TODO: 加入是否删除使用的数据磁盘开关,如果为True,则顺便删除使用的磁盘。否则解除该磁盘被使用的状态。
+                    cls.disk.uuid = uuid
+                    cls.disk.get_by('uuid')
+                    cls.disk.delete()
+                    cls.disk.update_by_filter({'guest_uuid': '', 'sequence': -1, 'state': DiskState.idle.value},
+                                              filter_str='guest_uuid:eq:' + cls.guest.uuid)
+
+            elif action == 'attach_disk':
+                cls.disk.uuid = cls.message['message']['passback_parameters']['disk_uuid']
                 cls.disk.get_by('uuid')
                 cls.disk.get_by('uuid')
-                cls.disk.delete()
-                cls.disk.update_by_filter({'guest_uuid': '', 'sequence': -1, 'state': DiskState.idle.value},
-                                          filter_str='guest_uuid:eq:' + cls.guest.uuid)
-
-        elif action == 'create_disk':
-            cls.disk.uuid = uuid
-            cls.disk.get_by('uuid')
-            cls.disk.on_host = hostname
-            if state == ResponseState.success.value:
-                cls.disk.state = DiskState.idle.value
+                if state == ResponseState.success.value:
+                    cls.disk.guest_uuid = uuid
+                    cls.disk.sequence = cls.message['message']['passback_parameters']['sequence']
+                    cls.disk.state = DiskState.mounted.value
+                    cls.disk.update()
+
+            elif action == 'detach_disk':
+                cls.disk.uuid = cls.message['message']['passback_parameters']['disk_uuid']
+                cls.disk.get_by('uuid')
+                if state == ResponseState.success.value:
+                    cls.disk.guest_uuid = ''
+                    cls.disk.sequence = -1
+                    cls.disk.state = DiskState.idle.value
+                    cls.disk.update()
 
 
-            else:
-                cls.disk.state = DiskState.dirty.value
+            elif action == 'boot':
+                boot_jobs_id = cls.message['message']['passback_parameters']['boot_jobs_id']
 
 
-            cls.disk.update()
+                if state == ResponseState.success.value:
+                    cls.guest.uuid = uuid
+                    cls.guest.delete_boot_jobs(boot_jobs_id=boot_jobs_id)
 
 
-        elif action == 'resize_disk':
-            if state == ResponseState.success.value:
+        elif _object == 'disk':
+            if action == 'create':
                 cls.disk.uuid = uuid
                 cls.disk.uuid = uuid
                 cls.disk.get_by('uuid')
                 cls.disk.get_by('uuid')
-                cls.disk.size = cls.message['message']['passback_parameters']['size']
-                cls.disk.update()
+                cls.disk.on_host = hostname
+                if state == ResponseState.success.value:
+                    cls.disk.state = DiskState.idle.value
 
 
-        elif action == 'attach_disk':
-            cls.disk.uuid = cls.message['message']['passback_parameters']['disk_uuid']
-            cls.disk.get_by('uuid')
-            if state == ResponseState.success.value:
-                cls.disk.guest_uuid = uuid
-                cls.disk.sequence = cls.message['message']['passback_parameters']['sequence']
-                cls.disk.state = DiskState.mounted.value
-                cls.disk.update()
+                else:
+                    cls.disk.state = DiskState.dirty.value
 
 
-        elif action == 'detach_disk':
-            cls.disk.uuid = cls.message['message']['passback_parameters']['disk_uuid']
-            cls.disk.get_by('uuid')
-            if state == ResponseState.success.value:
-                cls.disk.guest_uuid = ''
-                cls.disk.sequence = -1
-                cls.disk.state = DiskState.idle.value
                 cls.disk.update()
                 cls.disk.update()
 
 
-        elif action == 'delete_disk':
-            cls.disk.uuid = uuid
-            cls.disk.get_by('uuid')
-            cls.disk.delete()
-
-        elif action == 'boot':
-            boot_jobs_id = cls.message['message']['passback_parameters']['boot_jobs_id']
+            elif action == 'resize':
+                if state == ResponseState.success.value:
+                    cls.disk.uuid = uuid
+                    cls.disk.get_by('uuid')
+                    cls.disk.size = cls.message['message']['passback_parameters']['size']
+                    cls.disk.update()
 
 
-            if state == ResponseState.success.value:
-                cls.guest.uuid = uuid
-                cls.guest.delete_boot_jobs(boot_jobs_id=boot_jobs_id)
+            elif action == 'delete':
+                cls.disk.uuid = uuid
+                cls.disk.get_by('uuid')
+                cls.disk.delete()
 
 
         else:
         else:
             pass
             pass

+ 17 - 0
models/guest.py

@@ -3,6 +3,7 @@
 
 
 
 
 import jimit as ji
 import jimit as ji
+import json
 
 
 from filter import FilterFieldType
 from filter import FilterFieldType
 from orm import ORM
 from orm import ORM
@@ -99,6 +100,22 @@ class Guest(ORM):
 
 
         return uuids
         return uuids
 
 
+    @staticmethod
+    def get_lightest_host():
+        # 负载最小的宿主机
+        lightest_host = None
+        for k, v in db.r.hgetall(app.config['hosts_info']).items():
+            v = json.loads(v)
+
+            if lightest_host is None:
+                lightest_host = v
+
+            if float(lightest_host['system_load'][0]) / lightest_host['cpu'] > \
+                    float(v['system_load'][0]) / v['cpu']:
+                lightest_host = v
+
+        return lightest_host
+
 
 
 class Disk(ORM):
 class Disk(ORM):
 
 

+ 1 - 0
models/rules.py

@@ -62,6 +62,7 @@ class Rules(Enum):
     DISK_UUID = (basestring, 'disk_uuid', (36, 36))
     DISK_UUID = (basestring, 'disk_uuid', (36, 36))
     DISK_SIZE = (int, 'size')
     DISK_SIZE = (int, 'size')
     DISK_SIZE_STR = (REG_NUMBER, 'size')
     DISK_SIZE_STR = (REG_NUMBER, 'size')
+    DISK_ON_HOST = (basestring, 'on_host', (1, 128))
 
 
     REMARK = (basestring, 'remark')
     REMARK = (basestring, 'remark')
     USE_FOR = (int, 'use_for')
     USE_FOR = (int, 'use_for')

+ 4 - 0
state_code.py

@@ -57,6 +57,10 @@ own_state_branch = {
         'code': '41259',
         'code': '41259',
         'zh-cn': u'Guest 暂时不可用'
         'zh-cn': u'Guest 暂时不可用'
     },
     },
+    '41260': {
+        'code': '41260',
+        'zh-cn': u'Guest 与 磁盘不在同一宿主机上'
+    },
     '50050': {
     '50050': {
         'code': '50050',
         'code': '50050',
         'zh-cn': u'MySQL 链接或执行出错'
         'zh-cn': u'MySQL 链接或执行出错'

+ 129 - 0
templates/config_init.html

@@ -0,0 +1,129 @@
+{% extends "layout.html" %}
+{% block head %}
+    {{ super() }}
+
+    <style type="text/css">
+
+        label>span {
+            color: deepskyblue;
+        }
+
+        .btn,
+        .form-group>div>div,
+        .form-control {
+            border-radius: 0;
+        }
+
+        .btn-ability {
+            margin-right: 16px;
+            height: 50px;
+            padding: 8px 50px;
+            margin-bottom: 30px;
+        }
+
+        .btn-ability-line {
+        }
+
+    </style>
+{% endblock head %}
+{% block body %}
+<script>
+    $(document).ready(function() {
+        $('#os_template').chosen({
+            "disable_search": true
+        });
+
+        $('body').addClass('add-transition');
+        $('.add-page-transition').on('click', function(){
+            var transAttr = $(this).attr('data-transition');
+            $('.add-transition').attr('class', 'add-transition');
+            $('.add-transition').addClass(transAttr);
+        });
+    });
+
+    $(function() { "use strict";
+        $(".chosen-select").chosen();
+        $(".chosen-search").append('<i class="glyph-icon icon-search"></i>');
+        $(".chosen-single div").html('<i class="glyph-icon icon-caret-down"></i>');
+    });
+
+    $(function() { "use strict";
+        $("#quantity").TouchSpin({
+            max: 20,
+            min: 1,
+            verticalbuttons: true,
+            verticalupclass: 'glyph-icon icon-plus',
+            verticaldownclass: 'glyph-icon icon-minus'
+        });
+    });
+</script>
+<div class="container" style="padding-top: 100px;">
+    <div class="panel">
+        <div class="panel-body">
+            <h3 class="title-hero" style="text-transform: unset;">
+                初始化 JimV
+            </h3>
+            <div class="example-box-wrapper">
+                <form class="form-horizontal bordered-row" action="/config/create" method="post">
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">网桥(业务网络)</label>
+                        <div class="col-sm-3">
+                            <input title="Guest 业务网络" class="form-control" name="vm_network" type="text" value="net-br0">
+                        </div>
+                        <label class="col-sm-2 control-label">网桥(管理网络)</label>
+                        <div class="col-sm-3">
+                            <input title="Guest 管理网络" class="form-control" name="vm_manage_network" type="text" value="net-br0">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">起始 IP</label>
+                        <div class="col-sm-3">
+                            <input title="起始 IP" class="form-control" name="start_ip" type="text" value="10.10.0.1">
+                        </div>
+                        <label class="col-sm-2 control-label">截止 IP</label>
+                        <div class="col-sm-3">
+                            <input title="截止 IP" class="form-control" name="end_ip" type="text" value="10.10.15.253">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">子网掩码</label>
+                        <div class="col-sm-3">
+                            <input title="子网掩码" class="form-control" name="netmask" type="text" value="255.255.240.0">
+                        </div>
+                        <label class="col-sm-2 control-label">网关</label>
+                        <div class="col-sm-3">
+                            <input title="网关" class="form-control" name="gateway" type="text" value="10.10.15.254">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">DNS1</label>
+                        <div class="col-sm-3">
+                            <input title="DNS1" class="form-control" name="dns1" type="text" value="223.5.5.5">
+                        </div>
+                        <label class="col-sm-2 control-label">DNS2</label>
+                        <div class="col-sm-3">
+                            <input title="DNS2" class="form-control" name="dns2" type="text" value="8.8.8.8">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">起始 VNC 端口</label>
+                        <div class="col-sm-3">
+                            <input title="起始 VNC 端口" class="form-control" name="start_vnc_port" type="text" value="15900">
+                        </div>
+                        <label class="col-sm-2 control-label">虚拟机磁盘存放路径</label>
+                        <div class="col-sm-3">
+                            <input title="虚拟机磁盘存放路径" class="form-control" name="storage_path" type="text" value="Images">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label"></label>
+                        <div class="col-sm-3 pull-right">
+                            <button class="btn btn-blue-alt" style="width: 180px; height: 40px; font-size: 16px;">创建</button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock body %}

+ 5 - 2
templates/disks_show.html

@@ -381,7 +381,8 @@
                     <th>状态</th>
                     <th>状态</th>
                     <th>大小</th>
                     <th>大小</th>
                     <th>设备号</th>
                     <th>设备号</th>
-                    <th>所属 Guest</th>
+                    <th>所属虚拟机</th>
+                    <th style="{% if not show_on_host %}display: none;{% endif %}">所属宿主机</th>
                     <th>磁盘性别</th>
                     <th>磁盘性别</th>
                     <th>创建时间</th>
                     <th>创建时间</th>
                     <th>操作</th>
                     <th>操作</th>
@@ -407,7 +408,9 @@
                     <td>{{ format_sequence_to_device_name(item.sequence) }}</td>
                     <td>{{ format_sequence_to_device_name(item.sequence) }}</td>
                     <td><a href="/guest/detail/{{ item.guest_uuid }}" {% if 'guest' not in item %}style="display: none"{% endif %}>{% if 'guest' in item %}
                     <td><a href="/guest/detail/{{ item.guest_uuid }}" {% if 'guest' not in item %}style="display: none"{% endif %}>{% if 'guest' in item %}
                         {{ item.guest.label }}/{{ item.guest.remark }}
                         {{ item.guest.label }}/{{ item.guest.remark }}
-                    {% endif %}</a></td>
+                    {% endif %}</a>
+                    </td>
+                    <td style="{% if not show_on_host %}display: none;{% endif %}">{{ item.on_host }}</td>
                     <td>
                     <td>
                         {% if item.sequence == 0 %}
                         {% if item.sequence == 0 %}
                             系统盘
                             系统盘

+ 1 - 1
templates/layout.html

@@ -93,6 +93,7 @@
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='elements/timeline.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='elements/timeline.css') }}">
 
 
     <!-- ICONS -->
     <!-- ICONS -->
+    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/whhg-font/css/whhg.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/fontawesome/fontawesome.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/fontawesome/fontawesome.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/linecons/linecons.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/linecons/linecons.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/spinnericon/spinnericon.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons-min/spinnericon/spinnericon.css') }}">
@@ -100,7 +101,6 @@
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/elusive/elusive.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/elusive/elusive.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/meteocons/meteocons.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/meteocons/meteocons.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/typicons/typicons.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/typicons/typicons.css') }}">
-    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='icons/whhg-font/css/whhg.css') }}">
 
 
     <!-- WIDGETS -->
     <!-- WIDGETS -->
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='widgets/accordion-ui/accordion.css') }}">
     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='widgets/accordion-ui/accordion.css') }}">

+ 2 - 2
views/boot_job.py

@@ -83,7 +83,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -95,7 +95,7 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('boot_jobs_show.html', boot_jobs_ret=boot_jobs_ret,
     return render_template('boot_jobs_show.html', boot_jobs_ret=boot_jobs_ret,

+ 159 - 0
views/config.py

@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import json
+from flask import Blueprint, render_template, url_for, request
+import requests
+from math import ceil
+import re
+
+
+__author__ = 'James Iter'
+__date__ = '2017/8/29'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2017 by James Iter.'
+
+
+blueprint = Blueprint(
+    'v_config',
+    __name__,
+    url_prefix='/config'
+)
+
+blueprints = Blueprint(
+    'v_configs',
+    __name__,
+    url_prefix='/configs'
+)
+
+
+def show():
+    args = list()
+    page = int(request.args.get('page', 1))
+    page_size = int(request.args.get('page_size', 10))
+    keyword = request.args.get('keyword', None)
+    resource_path = request.path
+
+    if page is not None:
+        args.append('page=' + page.__str__())
+
+    if page_size is not None:
+        args.append('page_size=' + page_size.__str__())
+
+    if keyword is not None:
+        args.append('keyword=' + keyword.__str__())
+
+    host_url = request.host_url.rstrip('/')
+
+    guests_url = host_url + url_for('api_guests.r_get_by_filter')
+    if keyword is not None:
+        guests_url = host_url + url_for('api_guests.r_content_search')
+
+    os_templates_url = host_url + url_for('api_os_templates.r_get_by_filter')
+
+    if args.__len__() > 0:
+        guests_url = guests_url + '?' + '&'.join(args)
+
+    guests_ret = requests.get(url=guests_url)
+    guests_ret = json.loads(guests_ret.content)
+
+    os_templates_ret = requests.get(url=os_templates_url)
+    os_templates_ret = json.loads(os_templates_ret.content)
+    os_templates_mapping_by_id = dict()
+    for os_template in os_templates_ret['data']:
+        os_templates_mapping_by_id[os_template['id']] = os_template
+
+    guests_uuid = list()
+
+    for guest in guests_ret['data']:
+        guests_uuid.append(guest['uuid'])
+
+    guests_boot_jobs_ret = {'data': dict()}
+
+    if guests_uuid.__len__() > 0:
+        # 获取指定 Guest 的启动作业 ID
+        guests_boot_jobs_url = host_url + url_for('api_guests.r_get_boot_jobs', uuids=','.join(guests_uuid))
+        guests_boot_jobs_ret = requests.get(url=guests_boot_jobs_url)
+
+        guests_boot_jobs_ret = json.loads(guests_boot_jobs_ret.content)
+
+        # 统一单个、多个的返回JSON格式
+        if guests_uuid.__len__() == 1:
+            guests_boot_jobs_ret['data'] = {guests_uuid[0]: guests_boot_jobs_ret['data']}
+
+    last_page = int(ceil(guests_ret['paging']['total'] / float(page_size)))
+    page_length = 5
+    pages = list()
+    if page < int(ceil(page_length / 2.0)):
+        for i in range(1, page_length + 1):
+            pages.append(i)
+            if i == last_page:
+                break
+
+    elif last_page - page < page_length / 2:
+        for i in range(last_page - page_length + 1, last_page + 1):
+            if i < 1:
+                continue
+            pages.append(i)
+
+    else:
+        for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
+            pages.append(i)
+            if i == last_page:
+                break
+
+    return render_template('config_show.html', guests_ret=guests_ret, resource_path=resource_path,
+                           os_templates_mapping_by_id=os_templates_mapping_by_id,
+                           guests_boot_jobs_ret=guests_boot_jobs_ret, page=page,
+                           page_size=page_size, keyword=keyword, pages=pages, last_page=last_page)
+
+
+def create():
+    host_url = request.host_url.rstrip('/')
+
+    if request.method == 'POST':
+        ability = request.form.get('ability')
+        os_template_id = request.form.get('os_template_id')
+        quantity = request.form.get('quantity')
+        password = request.form.get('password')
+        remark = request.form.get('remark')
+
+        if not isinstance(ability, basestring):
+            pass
+
+        m = re.search('^(\d)c(\d)g$', ability.lower())
+        if m is None:
+            pass
+
+        cpu = m.groups()[0]
+        memory = m.groups()[1]
+
+        payload = {
+            "cpu": int(cpu),
+            "memory": int(memory),
+            "os_template_id": int(os_template_id),
+            "quantity": int(quantity),
+            "remark": remark,
+            "password": password,
+            "lease_term": 100
+        }
+
+        url = host_url + '/api/guest'
+        headers = {'content-type': 'application/json'}
+        r = requests.post(url, data=json.dumps(payload), headers=headers)
+        j_r = json.loads(r.content)
+        return render_template('success.html', go_back_url='/', timeout=5000, title='提交成功',
+                               message_title='初始化 JimV 请求已被接受',
+                               message='JimV 已被初始化。页面将在5秒钟后自动跳转到实例列表页面!')
+
+    else:
+        return render_template('config_init.html')
+
+
+def success():
+    return render_template('success.html', go_back_url='/', timeout=5000, title='提交成功',
+                           message_title='初始化 JimV 的请求已被接受',
+                           message='JimV 已被初始化。页面将在5秒钟后自动跳转到实例列表页面!')
+
+

+ 14 - 3
views/disk.py

@@ -6,6 +6,7 @@ import json
 from flask import Blueprint, render_template, url_for, request
 from flask import Blueprint, render_template, url_for, request
 import requests
 import requests
 from math import ceil
 from math import ceil
+from models.status import JimVEdition
 
 
 
 
 __author__ = 'James Iter'
 __author__ = 'James Iter'
@@ -83,6 +84,9 @@ def show():
     host_url = request.host_url.rstrip('/')
     host_url = request.host_url.rstrip('/')
 
 
     disks_url = host_url + url_for('api_disks.r_get_by_filter')
     disks_url = host_url + url_for('api_disks.r_get_by_filter')
+
+    config_url = host_url + url_for('api_config.r_get')
+
     if keyword is not None:
     if keyword is not None:
         disks_url = host_url + url_for('api_disks.r_content_search')
         disks_url = host_url + url_for('api_disks.r_content_search')
         # 关键字检索,不支持显示域过滤
         # 关键字检索,不支持显示域过滤
@@ -114,13 +118,20 @@ def show():
             if disk['guest_uuid'].__len__() == 36:
             if disk['guest_uuid'].__len__() == 36:
                 disks_ret['data'][i]['guest'] = guests_uuid_mapping[disk['guest_uuid']]
                 disks_ret['data'][i]['guest'] = guests_uuid_mapping[disk['guest_uuid']]
 
 
+    config_ret = requests.get(url=config_url)
+    config_ret = json.loads(config_ret.content)
+
+    show_on_host = False
+    if config_ret['data']['jimv_edition'] == JimVEdition.standalone.value:
+        show_on_host = True
+
     last_page = int(ceil(disks_ret['paging']['total'] / float(page_size)))
     last_page = int(ceil(disks_ret['paging']['total'] / float(page_size)))
     page_length = 5
     page_length = 5
     pages = list()
     pages = list()
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -132,12 +143,12 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('disks_show.html', disks_ret=disks_ret, resource_path=resource_path, page=page,
     return render_template('disks_show.html', disks_ret=disks_ret, resource_path=resource_path, page=page,
                            page_size=page_size, keyword=keyword, pages=pages, order_by=order_by, order=order,
                            page_size=page_size, keyword=keyword, pages=pages, order_by=order_by, order=order,
-                           last_page=last_page, show_area=show_area)
+                           last_page=last_page, show_area=show_area, config_ret=config_ret, show_on_host=show_on_host)
 
 
 
 
 def create():
 def create():

+ 2 - 2
views/guest.py

@@ -91,7 +91,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -103,7 +103,7 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('guests_show.html', guests_ret=guests_ret, resource_path=resource_path,
     return render_template('guests_show.html', guests_ret=guests_ret, resource_path=resource_path,

+ 2 - 2
views/log.py

@@ -69,7 +69,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -81,7 +81,7 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('logs.html', logs_ret=logs_ret, resource_path=resource_path, page=page,
     return render_template('logs.html', logs_ret=logs_ret, resource_path=resource_path, page=page,

+ 2 - 2
views/operate_rule.py

@@ -96,7 +96,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -108,7 +108,7 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('operate_rules_show.html', operate_rules_ret=operate_rules_ret,
     return render_template('operate_rules_show.html', operate_rules_ret=operate_rules_ret,

+ 2 - 2
views/os_template.py

@@ -89,7 +89,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
         for i in range(1, page_length + 1):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     elif last_page - page < page_length / 2:
     elif last_page - page < page_length / 2:
@@ -101,7 +101,7 @@ def show():
     else:
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
                 break
 
 
     return render_template('os_templates_show.html', os_templates_ret=os_templates_ret,
     return render_template('os_templates_show.html', os_templates_ret=os_templates_ret,

+ 3 - 1
views_route_table.py

@@ -3,7 +3,7 @@
 
 
 
 
 from models.utils import add_rule_views
 from models.utils import add_rule_views
-from views import guest, disk, log, os_template, boot_job, operate_rule, host, dashboard
+from views import guest, disk, log, os_template, boot_job, operate_rule, host, dashboard, config
 
 
 
 
 __author__ = 'James Iter'
 __author__ = 'James Iter'
@@ -12,6 +12,8 @@ __contact__ = 'james.iter.cn@gmail.com'
 __copyright__ = '(c) 2017 by James Iter.'
 __copyright__ = '(c) 2017 by James Iter.'
 
 
 
 
+add_rule_views(config.blueprint, '/create', views_func='config.create', methods=['GET', 'POST'])
+
 add_rule_views(dashboard.blueprint, '', views_func='dashboard.show', methods=['GET'])
 add_rule_views(dashboard.blueprint, '', views_func='dashboard.show', methods=['GET'])
 
 
 add_rule_views(guest.blueprints, '', views_func='guest.show', methods=['GET'])
 add_rule_views(guest.blueprints, '', views_func='guest.show', methods=['GET'])