Procházet zdrojové kódy

分离出disk对象

James Iter před 9 roky
rodič
revize
a1ad418c00
8 změnil soubory, kde provedl 253 přidání a 127 odebrání
  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(
 CREATE TABLE IF NOT EXISTS guest_disk(
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
     id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    uuid CHAR(36) NOT NULL,
     label VARCHAR(255) NOT NULL,
     label VARCHAR(255) NOT NULL,
     size INT UNSIGNED NOT NULL,
     size INT UNSIGNED NOT NULL,
     sequence TINYINT UNSIGNED NOT NULL,
     sequence TINYINT UNSIGNED NOT NULL,

+ 2 - 0
models/guest.py

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

+ 8 - 12
models/guest_xml.py

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

+ 1 - 1
models/rules.py

@@ -50,7 +50,7 @@ class Rules(Enum):
     PASSWORD = (basestring, 'password')
     PASSWORD = (basestring, 'password')
     LEASE_TERM = (int, 'lease_term')
     LEASE_TERM = (int, 'lease_term')
     DESTINATION_HOST = (basestring, 'destination_host', (5, 64))
     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')
     DISK_SIZE = ('regex:^\d{1,7}$', 'size')
 
 
     REMARK = (basestring, 'remark')
     REMARK = (basestring, 'remark')

+ 8 - 3
route_table.py

@@ -5,6 +5,7 @@
 from models.utils import add_rule
 from models.utils import add_rule
 from views import config
 from views import config
 from views import guest
 from views import guest
+from views import disk
 from views import os_init
 from views import os_init
 from views import os_init_write
 from views import os_init_write
 from views import os_template
 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, '/_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, '/_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.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'])
 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'])
 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',
         'code': '41255',
         'zh-cn': u'磁盘大小超出范围'
         'zh-cn': u'磁盘大小超出范围'
     },
     },
+    '41256': {
+        'code': '41256',
+        'zh-cn': u'欲删除的磁盘正在被使用中'
+    },
+    '41257': {
+        'code': '41257',
+        'zh-cn': u'扩容必须大于当前磁盘空间大小'
+    },
+    '41258': {
+        'code': '41258',
+        'zh-cn': u'欲挂载的磁盘已被别的实例使用'
+    },
     '50050': {
     '50050': {
         'code': '50050',
         'code': '50050',
         'zh-cn': u'MySQL 链接或执行出错'
         '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.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.xml = guest_xml.get_domain()
             guest.create()
             guest.create()
 
 
@@ -149,7 +132,7 @@ def r_create():
                 'name': guest.name,
                 'name': guest.name,
                 'glusterfs_volume': config.glusterfs_volume,
                 'glusterfs_volume': config.glusterfs_volume,
                 'template_path': 'template_pool/' + os_template.name,
                 'template_path': 'template_pool/' + os_template.name,
-                'guest_disks': guest_disks,
+                'guest_disk': guest_disk,
                 'writes': _os_init_writes,
                 'writes': _os_init_writes,
                 'password': guest.password,
                 'password': guest.password,
                 'xml': guest_xml.get_domain()
                 'xml': guest_xml.get_domain()
@@ -358,6 +341,7 @@ def r_delete(uuids):
         Rules.UUIDS.value
         Rules.UUIDS.value
     ]
     ]
 
 
+    # TODO: 加入是否删除使用的数据磁盘开关
     try:
     try:
         ji.Check.previewing(args_rules, {'uuids': uuids})
         ji.Check.previewing(args_rules, {'uuids': uuids})
 
 
@@ -379,69 +363,56 @@ def r_delete(uuids):
 
 
 
 
 @Utils.dumps2response
 @Utils.dumps2response
-def r_disk_resize(device_node_uuid, size):
+def r_attach_disk(uuid, disk_uuid):
 
 
     args_rules = [
     args_rules = [
-        Rules.DEVICE_NODE_UUID.value,
-        Rules.DISK_SIZE.value
+        Rules.UUID.value,
+        Rules.DISK_UUID.value
     ]
     ]
 
 
     try:
     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 = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         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
             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 = Config()
         config.id = 1
         config.id = 1
         config.get()
         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
         return ret
 
 
@@ -450,55 +421,48 @@ def r_create_disk(size):
 
 
 
 
 @Utils.dumps2response
 @Utils.dumps2response
-def r_attach_disk(uuid, size):
+def r_detach_disk(disk_uuid):
 
 
     args_rules = [
     args_rules = [
-        Rules.UUID.value,
-        Rules.DISK_SIZE.value
+        Rules.DISK_UUID.value
     ]
     ]
 
 
     try:
     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 = 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 = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         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
             return ret
 
 
+        guest_disk.guest_uuid = ''
+        guest_disk.sequence = -1
+
         config = Config()
         config = Config()
         config.id = 1
         config.id = 1
         config.get()
         config.get()
 
 
-        guest_disk.label = uuid4().__str__()
-        guest_disk.sequence = count + 1
-        guest_disk.format = 'qcow2'
-        guest_disk.create()
+        guest_disk.update()
 
 
         xml = """
         xml = """
             <disk type='network' device='disk'>
             <disk type='network' device='disk'>
                 <driver name='qemu' type='qcow2' cache='none'/>
                 <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'/>
                     <host name='127.0.0.1' port='24007'/>
                 </source>
                 </source>
-                <target dev='{4}' bus='virtio'/>
+                <target dev='{3}' bus='virtio'/>
             </disk>
             </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])
                    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))
         Guest.emit_instruction(message=json.dumps(message))
 
 
         return ret
         return ret
@@ -507,31 +471,6 @@ def r_attach_disk(uuid, size):
         return json.loads(e.message)
         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
 @Utils.dumps2response
 def r_migrate(uuids, destination_host):
 def r_migrate(uuids, destination_host):