Explorar o código

Merge pull request #2 from jamesiter/f_IO_limit

实现磁盘限速
jimit %!s(int64=8) %!d(string=hai) anos
pai
achega
6ff52d6815
Modificáronse 10 ficheiros con 267 adicións e 12 borrados
  1. 89 5
      api/config.py
  2. 1 1
      api/disk.py
  3. 12 3
      api/guest.py
  4. 1 0
      docs/todo.md
  5. 20 0
      misc/init.sql
  6. 10 0
      models/config.py
  7. 35 0
      models/guest.py
  8. 10 0
      models/rules.py
  9. 74 2
      templates/config_init.html
  10. 15 1
      views/config.py

+ 89 - 5
api/config.py

@@ -41,7 +41,17 @@ def r_create():
         Rules.NETMASK.value,
         Rules.GATEWAY.value,
         Rules.DNS1.value,
-        Rules.DNS2.value
+        Rules.DNS2.value,
+        Rules.IOPS_BASE.value,
+        Rules.IOPS_PRE_UNIT.value,
+        Rules.IOPS_CAP.value,
+        Rules.IOPS_MAX.value,
+        Rules.IOPS_MAX_LENGTH.value,
+        Rules.BPS_BASE.value,
+        Rules.BPS_PRE_UNIT.value,
+        Rules.BPS_CAP.value,
+        Rules.BPS_MAX.value,
+        Rules.BPS_MAX_LENGTH.value
     ]
 
     config = Config()
@@ -54,11 +64,25 @@ def r_create():
     config.vm_manage_network = request.json.get('vm_manage_network')
     config.start_ip = request.json.get('start_ip')
     config.end_ip = request.json.get('end_ip')
-    config.start_vnc_port = int(request.json.get('start_vnc_port'))
+    config.start_vnc_port = int(request.json.get('start_vnc_port', 15900))
     config.netmask = request.json.get('netmask')
     config.gateway = request.json.get('gateway')
     config.dns1 = request.json.get('dns1')
     config.dns2 = request.json.get('dns2')
+    config.iops_base = int(request.json.get('iops_base', 1000))
+    config.iops_pre_unit = int(request.json.get('iops_pre_unit', 1))
+    config.iops_cap = int(request.json.get('iops_cap', 2000))
+    config.iops_max = int(request.json.get('iops_max', 3000))
+    config.iops_max_length = int(request.json.get('iops_max_length', 20))
+    # 200 MiB
+    config.bps_base = int(request.json.get('bps_base', 1024 * 1024 * 200))
+    # 0.3 MiB
+    config.bps_pre_unit = int(request.json.get('bps_pre_unit', 1024 * 1024 * 0.3))
+    # 500 MiB
+    config.bps_cap = int(request.json.get('bps_cap', 1024 * 1024 * 500))
+    # 1 GiB
+    config.bps_max = int(request.json.get('bps_max', 1024 * 1024 * 1024))
+    config.bps_max_length = int(request.json.get('bps_max_length', 10))
 
     try:
         ji.Check.previewing(args_rules, config.__dict__)
@@ -157,6 +181,56 @@ def r_update():
             Rules.DNS2.value,
         )
 
+    if 'iops_base' in request.json:
+        args_rules.append(
+            Rules.IOPS_BASE.value,
+        )
+
+    if 'iops_pre_unit' in request.json:
+        args_rules.append(
+            Rules.IOPS_PRE_UNIT.value,
+        )
+
+    if 'iops_cap' in request.json:
+        args_rules.append(
+            Rules.IOPS_CAP.value,
+        )
+
+    if 'iops_max' in request.json:
+        args_rules.append(
+            Rules.IOPS_MAX.value,
+        )
+
+    if 'iops_max_length' in request.json:
+        args_rules.append(
+            Rules.IOPS_MAX_LENGTH.value,
+        )
+
+    if 'bps_base' in request.json:
+        args_rules.append(
+            Rules.BPS_BASE.value,
+        )
+
+    if 'bps_pre_unit' in request.json:
+        args_rules.append(
+            Rules.BPS_PRE_UNIT.value,
+        )
+
+    if 'bps_cap' in request.json:
+        args_rules.append(
+            Rules.BPS_CAP.value,
+        )
+
+    if 'bps_max' in request.json:
+        args_rules.append(
+            Rules.BPS_MAX.value,
+        )
+
+    if 'bps_max_length' in request.json:
+        args_rules.append(
+            Rules.BPS_MAX_LENGTH.value,
+        )
+
     if args_rules.__len__() < 1:
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
@@ -167,19 +241,29 @@ def r_update():
         ji.Check.previewing(args_rules, request.json)
         config.get()
 
-        config.jimv_edition = request.json.get('jimv_edition', config.jimv_edition)
-        config.storage_mode = request.json.get('storage_mode', config.storage_mode)
+        config.jimv_edition = int(request.json.get('jimv_edition', config.jimv_edition))
+        config.storage_mode = int(request.json.get('storage_mode', config.storage_mode))
         config.dfs_volume = request.json.get('dfs_volume', config.dfs_volume)
         config.storage_path = request.json.get('storage_path', config.storage_path)
         config.vm_network = request.json.get('vm_network', config.vm_network)
         config.vm_manage_network = request.json.get('vm_manage_network', config.vm_manage_network)
         config.start_ip = request.json.get('start_ip', config.start_ip)
         config.end_ip = request.json.get('end_ip', config.end_ip)
-        config.start_vnc_port = request.json.get('start_vnc_port', config.start_vnc_port)
+        config.start_vnc_port = int(request.json.get('start_vnc_port', config.start_vnc_port))
         config.netmask = request.json.get('netmask', config.netmask)
         config.gateway = request.json.get('gateway', config.gateway)
         config.dns1 = request.json.get('dns1', config.dns1)
         config.dns2 = request.json.get('dns2', config.dns2)
+        config.iops_base = int(request.json.get('iops_base', config.iops_base))
+        config.iops_pre_unit = int(request.json.get('iops_pre_unit', config.iops_pre_unit))
+        config.iops_cap = int(request.json.get('iops_cap', config.iops_cap))
+        config.iops_max = int(request.json.get('iops_max', config.iops_max))
+        config.iops_max_length = int(request.json.get('iops_max_length', config.iops_max_length))
+        config.bps_base = int(request.json.get('bps_base', config.bps_base))
+        config.bps_pre_unit = int(request.json.get('bps_pre_unit', config.bps_pre_unit))
+        config.bps_cap = int(request.json.get('bps_cap', config.bps_cap))
+        config.bps_max = int(request.json.get('bps_max', config.bps_max))
+        config.bps_max_length = int(request.json.get('bps_max_length', config.bps_max_length))
 
         config.check_ip()
         config.generate_available_ip2set()

+ 1 - 1
api/disk.py

@@ -82,8 +82,8 @@ def r_create():
             disk.on_host = on_host
             disk.sequence = -1
             disk.format = 'qcow2'
-
             disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
+            disk.quota(config=config)
 
             message = {
                 '_object': 'disk',

+ 12 - 3
api/guest.py

@@ -130,6 +130,7 @@ def r_create():
             disk.size = 0
             disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
             disk.guest_uuid = ''
+            disk.quota(config=config)
             disk.create()
 
             guest_xml = GuestXML(guest=guest, disk=disk, config=config, os_type=os_template.os_type)
@@ -171,6 +172,8 @@ def r_create():
                 'template_path': os_template.path,
                 'os_type': os_template.os_type,
                 'disk': disk.__dict__,
+                # disk 将被废弃,由 disks 代替,暂时保留它的目的,是为了保持与 JimV-N 的兼容性
+                'disks': [disk.__dict__],
                 'xml': guest_xml.get_domain(),
                 'boot_jobs': _boot_jobs,
                 'passback_parameters': {'boot_jobs_id': boot_jobs_id}
@@ -238,12 +241,14 @@ def r_force_reboot(uuids):
         for uuid in uuids.split(','):
             guest.uuid = uuid
             guest.get_by('uuid')
+            disks, _ = Disk.get_by_filter(filter_str=':'.join(['guest_uuid', 'eq', guest.uuid]))
 
             message = {
                 '_object': 'guest',
                 'action': 'force_reboot',
                 'uuid': uuid,
-                'hostname': guest.on_host
+                'hostname': guest.on_host,
+                'disks': disks
             }
 
             Guest.emit_instruction(message=json.dumps(message))
@@ -377,13 +382,16 @@ def r_boot(uuids):
                     replace('{DNS1}', config.dns1). \
                     replace('{DNS2}', config.dns2)
 
+            disks, _ = Disk.get_by_filter(filter_str=':'.join(['guest_uuid', 'eq', guest.uuid]))
+
             message = {
                 '_object': 'guest',
                 'action': 'boot',
                 'uuid': uuid,
                 'boot_jobs': boot_jobs,
                 'hostname': guest.on_host,
-                'passback_parameters': {'boot_jobs_id': boot_jobs_id}
+                'passback_parameters': {'boot_jobs_id': boot_jobs_id},
+                'disks': disks
             }
 
             Guest.emit_instruction(message=json.dumps(message))
@@ -589,7 +597,8 @@ def r_attach_disk(uuid, disk_uuid):
             'uuid': uuid,
             'hostname': guest.on_host,
             'xml': guest_xml.get_disk(),
-            'passback_parameters': {'disk_uuid': disk.uuid, 'sequence': disk.sequence}
+            'passback_parameters': {'disk_uuid': disk.uuid, 'sequence': disk.sequence},
+            'disks': [disk.__dict__]
         }
 
         Guest.emit_instruction(message=json.dumps(message))

+ 1 - 0
docs/todo.md

@@ -73,5 +73,6 @@
 - [ ] 周期轮询试的同步虚拟机状态
 - [ ] 实现类似于VMware的故障转移功能
 - [ ] 实现创建虚拟机时,手动指定计算节点功能
+- [ ] 计算机节点加入是否接受自动分配虚拟机开关
 
 

+ 20 - 0
misc/init.sql

@@ -92,6 +92,16 @@ CREATE TABLE IF NOT EXISTS disk(
     state TINYINT UNSIGNED NOT NULL DEFAULT 0,
     create_time BIGINT UNSIGNED NOT NULL,
     guest_uuid CHAR(36) NOT NULL,
+    iops BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_rd BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_wr BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_max BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_max_length BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_rd BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_wr BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_max BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_max_length BIGINT UNSIGNED NOT NULL DEFAULT 0,
     PRIMARY KEY (id))
     ENGINE=InnoDB
     DEFAULT CHARSET=utf8;
@@ -153,6 +163,16 @@ CREATE TABLE IF NOT EXISTS config(
     gateway CHAR(15) NOT NULL,
     dns1 CHAR(15) NOT NULL DEFAULT '223.5.5.5',
     dns2 CHAR(15) NOT NULL DEFAULT '8.8.8.8',
+    iops_base BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_pre_unit BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_cap BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_max BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    iops_max_length BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_base BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_pre_unit BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_cap BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_max BIGINT UNSIGNED NOT NULL DEFAULT 0,
+    bps_max_length BIGINT UNSIGNED NOT NULL DEFAULT 0,
     PRIMARY KEY (id))
     ENGINE=InnoDB
     DEFAULT CHARSET=utf8;

+ 10 - 0
models/config.py

@@ -40,6 +40,16 @@ class Config(ORM):
         self.gateway = ''
         self.dns1 = ''
         self.dns2 = ''
+        self.iops_base = 0
+        self.iops_pre_unit = 0
+        self.iops_cap = 0
+        self.iops_max = 0
+        self.iops_max_length = 0
+        self.bps_base = 0
+        self.bps_pre_unit = 0
+        self.bps_cap = 0
+        self.bps_max = 0
+        self.bps_max_length = 0
 
     @staticmethod
     def get_filter_keywords():

+ 35 - 0
models/guest.py

@@ -158,6 +158,41 @@ class Disk(ORM):
         self.format = 'qcow2'
         self.create_time = ji.Common.tus()
         self.guest_uuid = None
+        self.iops = 0
+        self.iops_rd = 0
+        self.iops_wr = 0
+        self.iops_max = 0
+        self.iops_max_length = 0
+        self.bps = 0
+        self.bps_rd = 0
+        self.bps_wr = 0
+        self.bps_max = 0
+        self.bps_max_length = 0
+
+    def quota(self, config=None):
+
+        from models import Config
+        assert isinstance(config, Config)
+
+        self.iops = config.iops_base + config.iops_pre_unit * self.size
+
+        if self.iops > config.iops_cap:
+            self.iops = config.iops_cap
+
+        self.iops_max = config.iops_max
+        self.iops_max_length = config.iops_max_length
+        self.iops_rd = 0
+        self.iops_wr = 0
+
+        self.bps = config.bps_base + config.bps_pre_unit * self.size
+
+        if self.bps > config.bps_cap:
+            self.bps = config.bps_cap
+
+        self.bps_max = config.bps_max
+        self.bps_max_length = config.bps_max_length
+        self.bps_rd = 0
+        self.bps_wr = 0
 
     @staticmethod
     def get_filter_keywords():

+ 10 - 0
models/rules.py

@@ -44,6 +44,16 @@ class Rules(Enum):
     GATEWAY = (REG_IP, 'gateway')
     DNS1 = (REG_IP, 'dns1')
     DNS2 = (REG_IP, 'dns2')
+    IOPS_BASE = (int, 'iops_base')
+    IOPS_PRE_UNIT = (int, 'iops_pre_unit')
+    IOPS_CAP = (int, 'iops_cap')
+    IOPS_MAX = (int, 'iops_max')
+    IOPS_MAX_LENGTH = (int, 'iops_max_length')
+    BPS_BASE = (int, 'iops_base')
+    BPS_PRE_UNIT = (int, 'iops_pre_unit')
+    BPS_CAP = (int, 'iops_cap')
+    BPS_MAX = (int, 'iops_max')
+    BPS_MAX_LENGTH = (int, 'iops_max_length')
     RSA_PRIVATE = (basestring, 'rsa_private')
     RSA_PUBLIC = (basestring, 'rsa_public')
 

+ 74 - 2
templates/config_init.html

@@ -30,7 +30,7 @@
         });
 
         $('#storage_mode').on('changed.bs.select', function (me) {
-            if ($('#storage_mode').val() == '2' || $('#storage_mode').val() == '3') {
+            if ($('#storage_mode').val() === '2' || $('#storage_mode').val() === '3') {
                 $('#dfs_volume_label').css('display', 'unset');
                 $('#dfs_volume').css('display', 'unset');
             } else {
@@ -39,6 +39,56 @@
             }
         });
     });
+
+    function hdd_type_select(me) {
+        var hdd_type = $(me).val();
+
+        if (hdd_type === 'sas') {
+            $('#iops_base').val('1000');
+            $('#iops_pre_unit').val('1');
+            $('#iops_cap').val('2000');
+            $('#iops_max').val('3000');
+            $('#iops_max_length').val('20');
+            // 200 MiB
+            $('#bps_base').val('209715200');
+            // 0.3 MiB
+            $('#bps_pre_unit').val('314572');
+            // 500 MiB
+            $('#bps_cap').val('524288000');
+            // 1 GiB
+            $('#bps_max').val('1073741824');
+            $('#bps_max_length').val('10')
+
+        } else if (hdd_type === 'ssd') {
+            $('#iops_base').val('1000');
+            $('#iops_pre_unit').val('30');
+            $('#iops_cap').val('10000');
+            $('#iops_max').val('20000');
+            $('#iops_max_length').val('20');
+            $('#bps_base').val('209715200');
+            // 1 MiB
+            $('#bps_pre_unit').val('1048576');
+            $('#bps_cap').val('524288000');
+            $('#bps_max').val('1073741824');
+            $('#bps_max_length').val('10')
+
+        } else {
+            $('#iops_base').val('100');
+            $('#iops_pre_unit').val('0');
+            $('#iops_cap').val('100');
+            $('#iops_max').val('200');
+            $('#iops_max_length').val('20');
+            // 30 MiB
+            $('#bps_base').val('31457280');
+            // 0.1 MiB
+            $('#bps_pre_unit').val('104857');
+            // 50 MiB
+            $('#bps_cap').val('52428800');
+            // 100 MiB
+            $('#bps_max').val('104857600');
+            $('#bps_max_length').val('10')
+        }
+    }
 </script>
     <div class="container" style="padding-top: 100px;">
         <div class="panel">
@@ -112,7 +162,29 @@
                         <div class="form-group">
                             <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">
+                                <input title="虚拟机磁盘存放路径" class="form-control" name="storage_path" type="text" value="/opt/Images">
+                            </div>
+                            <!-- HDD for Hard Disk Drive -->
+                            <label id="hdd_type_label" class="col-sm-2 control-label">磁盘类型</label>
+                            <div class="col-sm-1">
+                                <label>STAT<input class="form-control" name="hdd_type" type="radio" value="stat" onclick="hdd_type_select(this)" /></label>
+                            </div>
+                            <div class="col-sm-1">
+                                <label>SAS<input class="form-control" name="hdd_type" type="radio" checked value="sas" onclick="hdd_type_select(this)" /></label>
+                            </div>
+                            <div class="col-sm-1">
+                                <label>SSD<input class="form-control" name="hdd_type" type="radio" value="ssd" onclick="hdd_type_select(this)" /></label>
+                            </div>
+                                <input class="form-control" id="iops_base" name="iops_base" type="hidden" value="1000" />
+                                <input class="form-control" id="iops_pre_unit" name="iops_pre_unit" type="hidden" value="1" />
+                                <input class="form-control" id="iops_cap" name="iops_cap" type="hidden" value="2000" />
+                                <input class="form-control" id="iops_max" name="iops_max" type="hidden" value="3000" />
+                                <input class="form-control" id="iops_max_length" name="iops_max_length" type="hidden" value="20" />
+                                <input class="form-control" id="bps_base" name="bps_base" type="hidden" value="209715200" />
+                                <input class="form-control" id="bps_pre_unit" name="bps_pre_unit" type="hidden" value="314572" />
+                                <input class="form-control" id="bps_cap" name="bps_cap" type="hidden" value="524288000" />
+                                <input class="form-control" id="bps_max" name="bps_max" type="hidden" value="1073741824" />
+                                <input class="form-control" id="bps_max_length" name="bps_max_length" type="hidden" value="10" />
                             </div>
                         </div>
                         <div class="form-group">

+ 15 - 1
views/config.py

@@ -50,7 +50,21 @@ def create():
             'netmask': request.form.get('netmask'),
             'gateway': request.form.get('gateway'),
             'dns1': request.form.get('dns1'),
-            'dns2': request.form.get('dns2')
+            'dns2': request.form.get('dns2'),
+            'iops_base': int(request.form.get('iops_base', 1000)),
+            'iops_pre_unit': int(request.form.get('iops_pre_unit', 1)),
+            'iops_cap': int(request.form.get('iops_cap', 2000)),
+            'iops_max': int(request.form.get('iops_max', 3000)),
+            'iops_max_length': int(request.form.get('iops_max_length', 20)),
+            # 200 MiB
+            'bps_base': int(request.form.get('bps_base', 1024 * 1024 * 200)),
+            # 0.3 MiB
+            'bps_pre_unit': int(request.form.get('bps_pre_unit', 1024 * 1024 * 0.3)),
+            # 500 MiB
+            'bps_cap': int(request.form.get('bps_cap', 1024 * 1024 * 500)),
+            # 1 GiB
+            'bps_max': int(request.form.get('bps_max', 1024 * 1024 * 1024)),
+            'bps_max_length': int(request.form.get('bps_max_length', 10))
         }
 
         url = host_url + '/api/config'