Просмотр исходного кода

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

James Iter 9 лет назад
Родитель
Сommit
d2453a10a2

+ 63 - 30
api/disk.py

@@ -8,12 +8,12 @@ from uuid import uuid4
 import jimit as ji
 
 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 Disk
 from models import Rules
 from models import Utils
+from models.status import JimVEdition
 
 from base import Base
 
@@ -46,9 +46,17 @@ def r_create():
     args_rules = [
         Rules.DISK_SIZE.value,
         Rules.REMARK.value,
+        Rules.DISK_ON_HOST.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:
         ji.Check.previewing(args_rules, request.json)
 
@@ -56,7 +64,8 @@ def r_create():
         ret['state'] = ji.Common.exchange_state(20000)
 
         size = request.json['size']
-        quantity = request.json.get('quantity')
+        quantity = request.json['quantity']
+        on_host = request.json['on_host']
 
         if size < 1:
             ret['state'] = ji.Common.exchange_state(41255)
@@ -69,19 +78,28 @@ def r_create():
             disk.size = size
             disk.uuid = uuid4().__str__()
             disk.remark = request.json.get('remark', '')
+            disk.on_host = on_host
             disk.sequence = -1
             disk.format = 'qcow2'
 
-            config = Config()
-            config.id = 1
-            config.get()
-
             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()
 
@@ -106,11 +124,6 @@ def r_resize(uuid, size):
         disk.uuid = uuid
         disk.get_by('uuid')
 
-        used = True
-
-        if disk.guest_uuid.__len__() != 36:
-            used = False
-
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
 
@@ -118,21 +131,30 @@ def r_resize(uuid, size):
             ret['state'] = ji.Common.exchange_state(41257)
             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]
-            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
 
@@ -173,9 +195,20 @@ def r_delete(uuids):
             disk.uuid = 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
 

+ 125 - 36
api/guest.py

@@ -149,25 +149,14 @@ def r_create():
                     replace('{DNS1}', config.dns1). \
                     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',
+                'uuid': guest.uuid,
                 'jimv_edition': config.jimv_edition,
                 'dfs': config.dfs,
                 'dfs_volume': config.dfs_volume,
-                'uuid': guest.uuid,
-                'hostname': lightest_host['hostname'],
+                'hostname': Guest.get_lightest_host()['hostname'],
                 'name': guest.label,
                 'template_path': os_template.path,
                 'disk': disk.__dict__,
@@ -176,7 +165,7 @@ def r_create():
                 '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
 
@@ -200,7 +189,16 @@ def r_reboot(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -227,7 +225,16 @@ def r_force_reboot(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -254,7 +261,16 @@ def r_shutdown(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -281,7 +297,16 @@ def r_force_shutdown(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -313,6 +338,9 @@ def r_boot(uuids):
         config.get()
 
         for uuid in uuids.split(','):
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
             _, boot_jobs_id = guest.get_boot_jobs()
 
             boot_jobs = list()
@@ -338,8 +366,15 @@ def r_boot(uuids):
                     replace('{DNS1}', config.dns1). \
                     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))
 
         ret = dict()
@@ -366,7 +401,16 @@ def r_suspend(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -393,7 +437,16 @@ def r_resume(uuids):
             guest.get_by('uuid')
 
         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))
 
         ret = dict()
@@ -427,13 +480,19 @@ def r_delete(uuids):
 
         # 执行删除操作
         for uuid in uuids.split(','):
+            guest.uuid = uuid
+            guest.get_by('uuid')
+
             message = {
-                'action': 'delete_guest',
+                '_object': 'guest',
+                'action': 'delete',
                 'uuid': uuid,
                 'jimv_edition': config.jimv_edition,
                 'dfs': config.dfs,
-                'dfs_volume': config.dfs_volume
+                'dfs_volume': config.dfs_volume,
+                'hostname': guest.on_host
             }
+
             Guest.emit_instruction(message=json.dumps(message))
 
         ret = dict()
@@ -463,6 +522,10 @@ def r_attach_disk(uuid, disk_uuid):
         disk.uuid = disk_uuid
         disk.get_by('uuid')
 
+        config = Config()
+        config.id = 1
+        config.get()
+
         ret = dict()
         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)
             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 身上的序列
         disk.guest_uuid = 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
 
-        config = Config()
-        config.id = 1
-        config.get()
-
         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))
         disk.update()
 
@@ -547,8 +619,15 @@ def r_detach_disk(disk_uuid):
 
         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))
 
         disk.state = DiskState.unloading.value
@@ -581,8 +660,18 @@ def r_migrate(uuids, destination_host):
         config.get()
 
         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))
 
         ret = dict()

+ 1 - 1
docs/todo.md

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

+ 84 - 81
models/event_processor.py

@@ -120,105 +120,108 @@ class EventProcessor(object):
 
     @classmethod
     def response_processor(cls):
+        _object = cls.message['message']['_object']
         action = cls.message['message']['action']
         uuid = cls.message['message']['uuid']
         state = cls.message['type']
         data = cls.message['message']['data']
         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.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.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()
 
-        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:
             pass

+ 17 - 0
models/guest.py

@@ -3,6 +3,7 @@
 
 
 import jimit as ji
+import json
 
 from filter import FilterFieldType
 from orm import ORM
@@ -99,6 +100,22 @@ class Guest(ORM):
 
         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):
 

+ 1 - 0
models/rules.py

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

+ 4 - 0
state_code.py

@@ -57,6 +57,10 @@ own_state_branch = {
         'code': '41259',
         'zh-cn': u'Guest 暂时不可用'
     },
+    '41260': {
+        'code': '41260',
+        'zh-cn': u'Guest 与 磁盘不在同一宿主机上'
+    },
     '50050': {
         'code': '50050',
         '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>所属 Guest</th>
+                    <th>所属虚拟机</th>
+                    <th style="{% if not show_on_host %}display: none;{% endif %}">所属宿主机</th>
                     <th>磁盘性别</th>
                     <th>创建时间</th>
                     <th>操作</th>
@@ -407,7 +408,9 @@
                     <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 %}
                         {{ 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>
                         {% 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') }}">
 
     <!-- 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/linecons/linecons.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/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/whhg-font/css/whhg.css') }}">
 
     <!-- WIDGETS -->
     <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)):
         for i in range(1, page_length + 1):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -95,7 +95,7 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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
 import requests
 from math import ceil
+from models.status import JimVEdition
 
 
 __author__ = 'James Iter'
@@ -83,6 +84,9 @@ def show():
     host_url = request.host_url.rstrip('/')
 
     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:
         disks_url = host_url + url_for('api_disks.r_content_search')
         # 关键字检索,不支持显示域过滤
@@ -114,13 +118,20 @@ def show():
             if disk['guest_uuid'].__len__() == 36:
                 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)))
     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:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -132,12 +143,12 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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,
-                           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():

+ 2 - 2
views/guest.py

@@ -91,7 +91,7 @@ def show():
     if page < int(ceil(page_length / 2.0)):
         for i in range(1, page_length + 1):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -103,7 +103,7 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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)):
         for i in range(1, page_length + 1):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -81,7 +81,7 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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)):
         for i in range(1, page_length + 1):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -108,7 +108,7 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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)):
         for i in range(1, page_length + 1):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     elif last_page - page < page_length / 2:
@@ -101,7 +101,7 @@ def show():
     else:
         for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
             pages.append(i)
-            if i == last_page:
+            if i == last_page or last_page == 0:
                 break
 
     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 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'
@@ -12,6 +12,8 @@ __contact__ = 'james.iter.cn@gmail.com'
 __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(guest.blueprints, '', views_func='guest.show', methods=['GET'])