浏览代码

分离出disk对象

James Iter 9 年之前
父节点
当前提交
a1ad418c00
共有 8 个文件被更改,包括 253 次插入127 次删除
  1. 1 0
      misc/init.sql
  2. 2 0
      models/guest.py
  3. 8 12
      models/guest_xml.py
  4. 1 1
      models/rules.py
  5. 8 3
      route_table.py
  6. 12 0
      state_code.py
  7. 171 0
      views/disk.py
  8. 50 111
      views/guest.py

+ 1 - 0
misc/init.sql

@@ -33,6 +33,7 @@ ALTER TABLE guest ADD INDEX (ip);
 
 CREATE TABLE IF NOT EXISTS guest_disk(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    uuid CHAR(36) NOT NULL,
     label VARCHAR(255) NOT NULL,
     size INT UNSIGNED NOT NULL,
     sequence TINYINT UNSIGNED NOT NULL,

+ 2 - 0
models/guest.py

@@ -72,6 +72,7 @@ class GuestDisk(ORM):
     def __init__(self):
         super(GuestDisk, self).__init__()
         self.id = 0
+        self.uuid = None
         self.label = None
         self.size = None
         self.sequence = None
@@ -81,6 +82,7 @@ class GuestDisk(ORM):
     @staticmethod
     def get_filter_keywords():
         return {
+            'uuid': FilterFieldType.STR.value,
             'label': FilterFieldType.STR.value,
             'size': FilterFieldType.INT.value,
             'guest_uuid': FilterFieldType.STR.value

+ 8 - 12
models/guest_xml.py

@@ -53,13 +53,13 @@ class GuestXML(object):
     </domain>
     """
 
-    def __init__(self, guest=None, disks=None, config=None):
+    def __init__(self, guest=None, disk=None, config=None):
         assert isinstance(guest, Guest)
-        assert isinstance(disks, list)
+        assert isinstance(disk, dict)
         assert isinstance(config, Config)
 
         self.guest = guest
-        self.disks = disks
+        self.disk = disk
         self.config = config
 
     def get_domain(self):
@@ -115,7 +115,7 @@ class GuestXML(object):
                 {2}
                 {3}
             </devices>
-        """.format(self.get_interface(), self.get_disks(), self.get_graphics(), self.get_console())
+        """.format(self.get_interface(), self.get_disk(), self.get_graphics(), self.get_console())
 
     def get_interface(self):
         return """
@@ -125,14 +125,11 @@ class GuestXML(object):
             </interface>
         """.format(self.guest.network)
 
-    def get_disks(self):
+    def get_disk(self):
 
         from initialize import dev_table
 
-        disks = []
-
-        for i, disk in enumerate(self.disks):
-            disks.append("""
+        return """
                 <disk type='network' device='disk'>
                     <driver name='qemu' type='qcow2' cache='none'/>
                     <source protocol='gluster' name='{0}/VMs/{1}/{2}.{3}'>
@@ -140,9 +137,8 @@ class GuestXML(object):
                     </source>
                     <target dev='{4}' bus='virtio'/>
                 </disk>
-            """.format(self.config.glusterfs_volume, self.guest.name, disk['label'], disk['format'], dev_table[i]))
-
-        return ''.join(disks)
+        """.format(self.config.glusterfs_volume, self.guest.name, self.disk['uuid'], self.disk['format'],
+                   dev_table[self.disk['sequence']])
 
     def get_graphics(self):
         return """

+ 1 - 1
models/rules.py

@@ -50,7 +50,7 @@ class Rules(Enum):
     PASSWORD = (basestring, 'password')
     LEASE_TERM = (int, 'lease_term')
     DESTINATION_HOST = (basestring, 'destination_host', (5, 64))
-    DEVICE_NODE_UUID = (basestring, 'device_node_uuid', (36, 36))
+    DISK_UUID = (basestring, 'disk_uuid', (36, 36))
     DISK_SIZE = ('regex:^\d{1,7}$', 'size')
 
     REMARK = (basestring, 'remark')

+ 8 - 3
route_table.py

@@ -5,6 +5,7 @@
 from models.utils import add_rule
 from views import config
 from views import guest
+from views import disk
 from views import os_init
 from views import os_init_write
 from views import os_template
@@ -51,11 +52,15 @@ add_rule(guest.blueprints, '/_boot/<uuids>', view_func='guest.r_boot', methods=[
 add_rule(guest.blueprints, '/_suspend/<uuids>', view_func='guest.r_suspend', methods=['PUT'])
 add_rule(guest.blueprints, '/_resume/<uuids>', view_func='guest.r_resume', methods=['PUT'])
 add_rule(guest.blueprints, '/_delete/<uuids>', view_func='guest.r_delete', methods=['PUT'])
-add_rule(guest.blueprint, '/_disk_resize/<device_node_uuid>/<size>', view_func='guest.r_disk_resize', methods=['PUT'])
-add_rule(guest.blueprint, '/_attach_disk/<uuid>', view_func='guest.r_attach_disk', methods=['PUT'])
-add_rule(guest.blueprint, '/_detach_disk/<uuid>', view_func='guest.r_detach_disk', methods=['PUT'])
+add_rule(guest.blueprint, '/_attach_disk/<uuid>/<disk_uuid>', view_func='guest.r_attach_disk', methods=['PUT'])
+add_rule(guest.blueprint, '/_detach_disk/<disk_uuid>', view_func='guest.r_detach_disk', methods=['PUT'])
 add_rule(guest.blueprint, '/_migrate/<uuids>/<destination_host>', view_func='guest.r_migrate', methods=['PUT'])
 
+# Disk操作
+add_rule(disk.blueprint, '/<size>', view_func='disk.r_create', methods=['POST'])
+add_rule(disk.blueprint, '/_disk_resize/<uuid>/<size>', view_func='disk.r_resize', methods=['PUT'])
+add_rule(disk.blueprint, '/<uuid>', view_func='disk.r_delete', methods=['DELETE'])
+
 # 日志查询
 # 系统模板操作
 add_rule(log.blueprints, '', view_func='log.r_get_by_filter', methods=['GET'])

+ 12 - 0
state_code.py

@@ -41,6 +41,18 @@ own_state_branch = {
         'code': '41255',
         'zh-cn': u'磁盘大小超出范围'
     },
+    '41256': {
+        'code': '41256',
+        'zh-cn': u'欲删除的磁盘正在被使用中'
+    },
+    '41257': {
+        'code': '41257',
+        'zh-cn': u'扩容必须大于当前磁盘空间大小'
+    },
+    '41258': {
+        'code': '41258',
+        'zh-cn': u'欲挂载的磁盘已被别的实例使用'
+    },
     '50050': {
         'code': '50050',
         'zh-cn': u'MySQL 链接或执行出错'

+ 171 - 0
views/disk.py

@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+from flask import Blueprint
+import json
+from uuid import uuid4
+import jimit as ji
+
+from models import Guest
+from models.initialize import app, dev_table
+from models import Database as db
+from models import Config
+from models import GuestDisk
+from models import Rules
+from models import Utils
+
+
+__author__ = 'James Iter'
+__date__ = '2017/4/24'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2017 by James Iter.'
+
+
+blueprint = Blueprint(
+    'disk',
+    __name__,
+    url_prefix='/api/disk'
+)
+
+blueprints = Blueprint(
+    'disks',
+    __name__,
+    url_prefix='/api/disks'
+)
+
+
+@Utils.dumps2response
+def r_create(size):
+
+    args_rules = [
+        Rules.DISK_SIZE.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'size': size})
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        size = int(size)
+
+        if not isinstance(size, int) or size < 1:
+            ret['state'] = ji.Common.exchange_state(41255)
+            return ret
+
+        guest_disk = GuestDisk()
+        guest_disk.guest_uuid = ''
+        guest_disk.size = size
+        guest_disk.uuid = uuid4().__str__()
+        guest_disk.label = ji.Common.generate_random_code(length=8)
+        guest_disk.sequence = -1
+        guest_disk.format = 'qcow2'
+        guest_disk.create()
+
+        config = Config()
+        config.id = 1
+        config.get()
+
+        image_path = '/'.join(['DiskPool', guest_disk.uuid + '.' + guest_disk.format])
+
+        message = {'action': 'create_disk', 'glusterfs_volume': config.glusterfs_volume,
+                   'image_path': image_path, 'size': guest_disk.size}
+
+        db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_resize(uuid, size):
+
+    args_rules = [
+        Rules.UUID.value,
+        Rules.DISK_SIZE.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'uuid': uuid, 'size': size})
+
+        guest_disk = GuestDisk()
+        guest_disk.uuid = uuid
+        guest_disk.get_by('uuid')
+
+        used = True
+
+        if guest_disk.guest_uuid.__len__() != 36:
+            used = False
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        if guest_disk.size <= size:
+            ret['state'] = ji.Common.exchange_state(41257)
+            return ret
+
+        guest_disk.size = size
+        guest_disk.update()
+
+        message = {'action': 'resize_disk', 'size': size}
+
+        if used:
+            message['uuid'] = guest_disk.guest_uuid
+            message['device_node'] = dev_table[guest_disk.sequence]
+            Guest.emit_instruction(message=json.dumps(message))
+        else:
+            config = Config()
+            config.id = 1
+            config.get()
+
+            image_path = '/'.join(['DiskPool', guest_disk.uuid + '.' + guest_disk.format])
+            message['glusterfs_volume'] = config.glusterfs_volume
+            message['image_path'] = image_path
+
+            db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_delete(uuid):
+
+    args_rules = [
+        Rules.UUID.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'uuid': uuid})
+
+        guest_disk = GuestDisk()
+        guest_disk.uuid = uuid
+        guest_disk.get_by('uuid')
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        if guest_disk.guest_uuid.__len__() > 0:
+            ret['state'] = ji.Common.exchange_state(41256)
+            return ret
+
+        config = Config()
+        config.id = 1
+        config.get()
+
+        image_path = '/'.join(['DiskPool', guest_disk.uuid + '.' + guest_disk.format])
+
+        message = {'action': 'delete_disk', 'glusterfs_volume': config.glusterfs_volume, 'image_path': image_path}
+        db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+
+        guest_disk.delete()
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)

+ 50 - 111
views/guest.py

@@ -110,26 +110,9 @@ def r_create():
 
             guest.vnc_password = ji.Common.generate_random_code(length=16)
 
-            guest_disks = list()
-            guest_disks.append({'label': uuid4().__str__(), 'size': -1, 'format': 'qcow2'})
+            guest_disk = {'uuid': uuid4().__str__(), 'size': -1, 'format': 'qcow2', 'sequence': 0}
 
-            for i, disk in enumerate(request.json.get('disks')):
-                guest_disk = GuestDisk()
-
-                guest_disk.size = disk.get('size')
-                # TODO: 设定磁盘最大大小
-                if not isinstance(guest_disk.size, int) or guest_disk.size < 1:
-                    continue
-
-                guest_disk.guest_uuid = guest.uuid
-                guest_disk.label = uuid4().__str__()
-                guest_disk.sequence = i + 1
-                guest_disk.format = 'qcow2'
-                guest_disk.create()
-
-                guest_disks.append({'label': guest_disk.label, 'size': guest_disk.size, 'format': guest_disk.format})
-
-            guest_xml = GuestXML(guest=guest, disks=guest_disks, config=config)
+            guest_xml = GuestXML(guest=guest, disk=guest_disk, config=config)
             guest.xml = guest_xml.get_domain()
             guest.create()
 
@@ -149,7 +132,7 @@ def r_create():
                 'name': guest.name,
                 'glusterfs_volume': config.glusterfs_volume,
                 'template_path': 'template_pool/' + os_template.name,
-                'guest_disks': guest_disks,
+                'guest_disk': guest_disk,
                 'writes': _os_init_writes,
                 'password': guest.password,
                 'xml': guest_xml.get_domain()
@@ -358,6 +341,7 @@ def r_delete(uuids):
         Rules.UUIDS.value
     ]
 
+    # TODO: 加入是否删除使用的数据磁盘开关
     try:
         ji.Check.previewing(args_rules, {'uuids': uuids})
 
@@ -379,69 +363,56 @@ def r_delete(uuids):
 
 
 @Utils.dumps2response
-def r_disk_resize(device_node_uuid, size):
+def r_attach_disk(uuid, disk_uuid):
 
     args_rules = [
-        Rules.DEVICE_NODE_UUID.value,
-        Rules.DISK_SIZE.value
+        Rules.UUID.value,
+        Rules.DISK_UUID.value
     ]
 
     try:
-        ji.Check.previewing(args_rules, {'device_node_uuid': device_node_uuid, 'size': size})
-
-        guest_disk = GuestDisk()
-        guest_disk.label = device_node_uuid
-        guest_disk.get_by('label')
-
-        message = {'action': 'disk-resize', 'uuid': guest_disk.guest_uuid,
-                   'device_node': dev_table[guest_disk.sequence], 'size': size}
-        Guest.emit_instruction(message=json.dumps(message))
-
-        ret = dict()
-        ret['state'] = ji.Common.exchange_state(20000)
-        return ret
-
-    except ji.PreviewingError, e:
-        return json.loads(e.message)
-
-
-@Utils.dumps2response
-def r_create_disk(size):
+        ji.Check.previewing(args_rules, {'uuid': uuid, 'disk_uuid': disk_uuid})
 
-    args_rules = [
-        Rules.DISK_SIZE.value
-    ]
+        guest = Guest()
+        guest.uuid = uuid
+        guest.get_by('uuid')
 
-    try:
-        ji.Check.previewing(args_rules, {'size': size})
+        guest_disk = GuestDisk()
+        guest_disk.uuid = disk_uuid
+        guest_disk.get_by('uuid')
 
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
 
-        size = int(size)
-
-        if not isinstance(size, int) or size < 1:
-            ret['state'] = ji.Common.exchange_state(41255)
+        # 判断欲挂载的磁盘是否空闲
+        if guest_disk.guest_uuid.__len__() > 0:
+            ret['state'] = ji.Common.exchange_state(41258)
             return ret
 
-        guest_disk = GuestDisk()
-        guest_disk.guest_uuid = ''
-        guest_disk.size = size
-        guest_disk.label = uuid4().__str__()
-        guest_disk.sequence = -1
-        guest_disk.format = 'qcow2'
-        guest_disk.create()
+        # 取出该 guest 已挂载的磁盘,来做出决定,确定该磁盘的序列
+        guest_disk.guest_uuid = guest.uuid
+        disks, count = guest_disk.get_all()
+        guest_disk.sequence = count + 1
 
         config = Config()
         config.id = 1
         config.get()
 
-        image_path = '/'.join(['DiskPool', guest_disk.label + '.' + guest_disk.format])
+        guest_disk.update()
 
-        message = {'action': 'create_disk', 'glusterfs_volume': config.glusterfs_volume,
-                   'image_path': image_path, 'size': guest_disk.size}
+        xml = """
+            <disk type='network' device='disk'>
+                <driver name='qemu' type='qcow2' cache='none'/>
+                <source protocol='gluster' name='{0}/DiskPool/{1}.{2}'>
+                    <host name='127.0.0.1' port='24007'/>
+                </source>
+                <target dev='{3}' bus='virtio'/>
+            </disk>
+        """.format(config.glusterfs_volume, guest_disk.uuid, guest_disk.format,
+                   dev_table[guest_disk.sequence])
 
-        db.r.rpush(app.config['downstream_queue'], json.dumps(message, ensure_ascii=False))
+        message = {'action': 'attach_disk', 'uuid': uuid, 'xml': xml}
+        Guest.emit_instruction(message=json.dumps(message))
 
         return ret
 
@@ -450,55 +421,48 @@ def r_create_disk(size):
 
 
 @Utils.dumps2response
-def r_attach_disk(uuid, size):
+def r_detach_disk(disk_uuid):
 
     args_rules = [
-        Rules.UUID.value,
-        Rules.DISK_SIZE.value
+        Rules.DISK_UUID.value
     ]
 
     try:
-        ji.Check.previewing(args_rules, {'uuid': uuid, 'size': size})
-
-        guest = Guest()
-        guest.uuid = uuid
-        guest.get_by('uuid')
+        ji.Check.previewing(args_rules, {'disk_uuid': disk_uuid})
 
         guest_disk = GuestDisk()
-        guest_disk.guest_uuid = guest.uuid
-        disks, count = guest_disk.get_all()
-
-        guest_disk.size = int(size)
+        guest_disk.uuid = disk_uuid
+        guest_disk.get_by('uuid')
 
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
 
-        if not isinstance(guest_disk.size, int) or guest_disk.size < 1:
-            ret['state'] = ji.Common.exchange_state(41255)
+        if guest_disk.guest_uuid.__len__() != 36 and guest_disk.sequence < 1:
+            # 表示未被任何实例使用,已被分离
+            # 序列为 0 的表示实例系统盘,系统盘不可以被分离
             return ret
 
+        guest_disk.guest_uuid = ''
+        guest_disk.sequence = -1
+
         config = Config()
         config.id = 1
         config.get()
 
-        guest_disk.label = uuid4().__str__()
-        guest_disk.sequence = count + 1
-        guest_disk.format = 'qcow2'
-        guest_disk.create()
+        guest_disk.update()
 
         xml = """
             <disk type='network' device='disk'>
                 <driver name='qemu' type='qcow2' cache='none'/>
-                <source protocol='gluster' name='{0}/VMs/{1}/{2}.{3}'>
+                <source protocol='gluster' name='{0}/DiskPool/{1}.{2}'>
                     <host name='127.0.0.1' port='24007'/>
                 </source>
-                <target dev='{4}' bus='virtio'/>
+                <target dev='{3}' bus='virtio'/>
             </disk>
-        """.format(config.glusterfs_volume, guest.name, guest_disk.label, guest_disk.format,
+        """.format(config.glusterfs_volume, guest_disk.uuid, guest_disk.format,
                    dev_table[guest_disk.sequence])
 
-        message = {'action': 'attach_disk', 'uuid': uuid, 'xml': xml,
-                   'disk': {'label': guest_disk.label, 'size': guest_disk.size, 'format': guest_disk.format}}
+        message = {'action': 'detach_disk', 'uuid': guest_disk.guest_uuid, 'xml': xml}
         Guest.emit_instruction(message=json.dumps(message))
 
         return ret
@@ -507,31 +471,6 @@ def r_attach_disk(uuid, size):
         return json.loads(e.message)
 
 
-@Utils.dumps2response
-def r_detach_disk(uuid):
-
-    args_rules = [
-        Rules.UUID.value
-    ]
-
-    try:
-        ji.Check.previewing(args_rules, {'uuid': uuid})
-
-        guest = Guest()
-        guest.uuid = uuid
-        guest.get_by('uuid')
-
-        message = {'action': 'detach_disk', 'uuid': uuid}
-        Guest.emit_instruction(message=json.dumps(message))
-
-        ret = dict()
-        ret['state'] = ji.Common.exchange_state(20000)
-        return ret
-
-    except ji.PreviewingError, e:
-        return json.loads(e.message)
-
-
 @Utils.dumps2response
 def r_migrate(uuids, destination_host):