James Iter hace 8 años
padre
commit
3ca52ed155

+ 0 - 2
api/guest.py

@@ -141,8 +141,6 @@ def r_create():
             db.r.sadd(app.config['vnc_port_used_set'], guest.vnc_port)
 
             guest.vnc_password = ji.Common.generate_random_code(length=16)
-            guest.ssh_keys_id = []
-            guest.ssh_keys_id = json.dumps(guest.ssh_keys_id)
 
             disk = Disk()
             disk.uuid = guest.uuid

+ 147 - 0
api/ssh_key.py

@@ -9,6 +9,8 @@ import jimit as ji
 
 from api.base import Base
 from models import SSHKey
+from models import SSHKeyGuestMapping
+from models import Guest
 from models import Utils
 from models import Rules
 
@@ -33,6 +35,7 @@ blueprints = Blueprint(
 
 
 ssh_key_base = Base(the_class=SSHKey, the_blueprint=blueprint, the_blueprints=blueprints)
+guest_base = Base(the_class=Guest, the_blueprint=blueprint, the_blueprints=blueprints)
 
 
 @Utils.dumps2response
@@ -130,3 +133,147 @@ def r_content_search():
 @Utils.dumps2response
 def r_delete(ids):
     return ssh_key_base.delete(ids=ids, ids_rule=Rules.IDS.value, by_field='id')
+
+
+@Utils.dumps2response
+def r_bound(ssh_key_id):
+
+    args_rules = [
+        Rules.SSH_KEY_ID_EXT.value,
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'ssh_key_id': ssh_key_id})
+
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+        guests_uuid = list()
+        for row in rows:
+            guests_uuid.append(row['guest_uuid'])
+
+        request.args['filter_str'] = ':'.join(['uuid', 'in', ','.join(guests_uuid)])
+
+        return guest_base.get_by_filter()
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_unbound(ssh_key_id):
+
+    args_rules = [
+        Rules.SSH_KEY_ID_EXT.value,
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'ssh_key_id': ssh_key_id})
+
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+        guests_uuid = list()
+        for row in rows:
+            guests_uuid.append(row['guest_uuid'])
+
+        from werkzeug.datastructures import ImmutableMultiDict
+        request.__setattr__('args', ImmutableMultiDict([
+            ('filter', ':'.join(['uuid', 'notin', ','.join(guests_uuid)])),
+            ('page_size', 10000)
+        ]))
+
+        x = guest_base.get_by_filter()
+        return guest_base.get_by_filter()
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_bind(ssh_key_id, uuids):
+
+    args_rules = [
+        Rules.SSH_KEY_ID_EXT.value,
+        Rules.UUIDS.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'ssh_key_id': ssh_key_id, 'uuids': uuids})
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        ssh_key = SSHKey()
+        ssh_key.id = ssh_key_id
+
+        # 判断 ssh_key id 为 ssh_key_id 的对象是否存在
+        if not ssh_key.exist():
+            ret['state'] = ji.Common.exchange_state(40401)
+            ret['state']['sub']['zh-cn'] = ''.join([ret['state']['sub']['zh-cn'], ': ssh_key_id: ', ssh_key_id])
+            return ret
+
+        # 获取已经和该 ssh_key 绑定过的 guest uuid 集合,用于判断是否已经绑定过该 ssh_key,避免重复绑定
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+
+        guests_uuid = list()
+        for row in rows:
+            guests_uuid.append(row['guest_uuid'])
+
+        ssh_key_guest_mapping = SSHKeyGuestMapping()
+        for uuid in uuids.split(','):
+            # 如果已经绑定过,则忽略
+            if uuid in guests_uuid:
+                continue
+
+            ssh_key_guest_mapping.ssh_key_id = ssh_key_id
+            ssh_key_guest_mapping.guest_uuid = uuid
+            ssh_key_guest_mapping.create()
+
+        # 返回执行结果
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+        guests_uuid = list()
+        for row in rows:
+            guests_uuid.append(row['guest_uuid'])
+
+        request.args['filter_str'] = ':'.join(['uuid', 'in', ','.join(guests_uuid)])
+        request.args['page_size'] = 10000
+
+        return guest_base.get_by_filter()
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_unbind(ssh_key_id, uuids):
+
+    args_rules = [
+        Rules.SSH_KEY_ID_EXT.value,
+        Rules.UUIDS.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'ssh_key_id': ssh_key_id, 'uuids': uuids})
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        # 获取已经和该 ssh_key 绑定过的映射集合,从中获取映射 id,用于解绑操作
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+
+        guests_uuid = uuids.split(',')
+
+        ssh_key_guest_mapping = SSHKeyGuestMapping()
+        for row in rows:
+            # 解除已经绑定过的 guest
+            if row['guest_uuid'] in guests_uuid:
+                ssh_key_guest_mapping.id = row['id']
+                ssh_key_guest_mapping.delete()
+
+        # 返回执行结果
+        rows, _ = SSHKeyGuestMapping.get_by_filter(filter_str=':'.join(['ssh_key_id', 'eq', ssh_key_id]))
+        guests_uuid = list()
+        for row in rows:
+            guests_uuid.append(row['guest_uuid'])
+
+        request.args['filter_str'] = ':'.join(['uuid', 'in', ','.join(guests_uuid)])
+        request.args['page_size'] = 10000
+
+        return guest_base.get_by_filter()
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+

+ 4 - 0
api_route_table.py

@@ -141,6 +141,10 @@ add_rule_api(ssh_key.blueprints, '/<_id>', api_func='ssh_key.r_update', methods=
 add_rule_api(ssh_key.blueprints, '/<ids>', api_func='ssh_key.r_get', methods=['GET'])
 add_rule_api(ssh_key.blueprints, '', api_func='ssh_key.r_get_by_filter', methods=['GET'])
 add_rule_api(ssh_key.blueprints, '/_search', api_func='ssh_key.r_content_search', methods=['GET'])
+add_rule_api(ssh_key.blueprint, '/_bound/<ssh_key_id>', api_func='ssh_key.r_bound', methods=['GET'])
+add_rule_api(ssh_key.blueprint, '/_unbound/<ssh_key_id>', api_func='ssh_key.r_unbound', methods=['GET'])
+add_rule_api(ssh_key.blueprint, '/_bind/<ssh_key_id>/<uuids>', api_func='ssh_key.r_bind', methods=['PUT'])
+add_rule_api(ssh_key.blueprint, '/_unbind/<ssh_key_id>/<uuids>', api_func='ssh_key.r_unbind', methods=['PUT'])
 
 # 日志查询
 # Guest 性能查询

+ 12 - 1
misc/init.sql

@@ -45,7 +45,6 @@ CREATE TABLE IF NOT EXISTS guest(
     manage_network VARCHAR(64) NOT NULL,
     vnc_port INT UNSIGNED NOT NULL,
     vnc_password VARCHAR(255) NOT NULL,
-    ssh_keys_id TEXT NOT NULL,
     xml TEXT NOT NULL,
     PRIMARY KEY (id))
     ENGINE=InnoDB
@@ -370,6 +369,18 @@ CREATE TABLE IF NOT EXISTS ssh_key(
 ALTER TABLE ssh_key ADD INDEX (label);
 
 
+CREATE TABLE IF NOT EXISTS ssh_key_guest_mapping(
+    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    ssh_key_id BIGINT UNSIGNED NOT NULL,
+    guest_uuid CHAR(36) NOT NULL,
+    PRIMARY KEY (id))
+    ENGINE=Innodb
+    DEFAULT CHARSET=utf8;
+
+ALTER TABLE ssh_key_guest_mapping ADD UNIQUE INDEX (ssh_key_id, guest_uuid);
+ALTER TABLE ssh_key_guest_mapping ADD INDEX (guest_uuid);
+
+
 INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-Systemd', '用作 Redhat Systemd 系列的系统初始化。初始化操作依据 CentOS 7 来实现。', 1);
 INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('CentOS-SysV', '用作 Redhat SysV 系列的系统初始化。初始化操作依据 CentOS 6.8 来实现。', 1);
 INSERT INTO os_template_initialize_operate_set (label, description, active) VALUES ('Gentoo-OpenRC', '用作 Gentoo OpenRC 系列的系统初始化。', 1);

+ 11 - 1
misc/v0.2_to_v0.3/update.sql

@@ -11,7 +11,17 @@ CREATE TABLE IF NOT EXISTS ssh_key(
 
 ALTER TABLE ssh_key ADD INDEX (label);
 
-ALTER TABLE guest ADD COLUMN ssh_keys_id TEXT AFTER xml;
+
+CREATE TABLE IF NOT EXISTS ssh_key_guest_mapping(
+    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+    ssh_key_id BIGINT UNSIGNED NOT NULL,
+    guest_uuid CHAR(36) NOT NULL,
+    PRIMARY KEY (id))
+    ENGINE=Innodb
+    DEFAULT CHARSET=utf8;
+
+ALTER TABLE ssh_key_guest_mapping ADD UNIQUE INDEX (ssh_key_id, guest_uuid);
+ALTER TABLE ssh_key_guest_mapping ADD INDEX (guest_uuid);
 
 
 -- 操作系统模板镜像

+ 5 - 1
models/__init__.py

@@ -43,6 +43,10 @@ from ssh_key import (
     SSHKey
 )
 
+from ssh_key_guest_mapping import (
+    SSHKeyGuestMapping
+)
+
 from os_template_image import (
     OSTemplateImage
 )
@@ -104,7 +108,7 @@ __copyright__ = '(c) 2017 by James Iter.'
 
 __all__ = [
     'Rules', 'Utils', 'Init', 'Database', 'FilterFieldType', 'Filter', 'EmitKind', 'GuestState', 'DiskState',
-    'LogLevel', 'ORM', 'User', 'Config', 'Guest', 'Disk', 'GuestXML', 'Log', 'SSHKey',
+    'LogLevel', 'ORM', 'User', 'Config', 'Guest', 'Disk', 'GuestXML', 'Log', 'SSHKey', 'SSHKeyGuestMapping',
     'OSTemplateImage', 'OSTemplateProfile', 'OSTemplateInitializeOperateSet', 'OSTemplateInitializeOperate',
     'EventProcessor', 'ResponseState', 'GuestCPUMemory', 'GuestTraffic', 'GuestDiskIO', 'HostCPUMemory', 'HostTraffic',
     'HostDiskUsageIO', 'Host'

+ 1 - 1
models/filter.py

@@ -25,7 +25,7 @@ class Filter(object):
                 'lt': '<',
                 'ne': '!=',
                 'in': 'in',
-                'notin': 'in',
+                'notin': 'not in',
                 'like': 'like'}
 
     def __init__(self):

+ 1 - 2
models/guest.py

@@ -40,7 +40,6 @@ class Guest(ORM):
         self.vnc_port = None
         self.vnc_password = None
         self.xml = None
-        self.ssh_keys_id = None
 
     @staticmethod
     def get_filter_keywords():
@@ -56,7 +55,7 @@ class Guest(ORM):
 
     @staticmethod
     def get_allow_update_keywords():
-        return ['remark', 'cpu', 'memory', 'network', 'manage_network', 'vnc_password', 'ssh_keys_id']
+        return ['remark', 'cpu', 'memory', 'network', 'manage_network', 'vnc_password']
 
     @staticmethod
     def get_allow_content_search_keywords():

+ 1 - 1
models/rules.py

@@ -57,6 +57,7 @@ class Rules(Enum):
     BPS_MAX = (int, 'iops_max')
     BPS_MAX_LENGTH = (int, 'iops_max_length')
 
+    SSH_KEY_ID_EXT = (REG_NUMBER, 'ssh_key_id')
     PUBLIC_KEY = (basestring, 'public_key')
 
     UUID = (basestring, 'uuid', (36, 36))
@@ -97,7 +98,6 @@ class Rules(Enum):
     ICON = (basestring, 'icon')
     LOGO = (basestring, 'logo')
 
-    BOOT_JOB_ID_EXT = (int, 'boot_job_id')
     OS_TEMPLATE_PROFILE_ID_EXT = (int, 'os_template_profile_id')
     OS_TEMPLATE_INITIALIZE_OPERATE_SET_ID_EXT = (int, 'os_template_initialize_operate_set_id')
     OS_TEMPLATE_INITIALIZE_OPERATE_KIND = (int, 'kind')

+ 41 - 0
models/ssh_key_guest_mapping.py

@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+from filter import FilterFieldType
+from orm import ORM
+
+
+__author__ = 'James Iter'
+__date__ = '2018/3/1'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2018 by James Iter.'
+
+
+class SSHKeyGuestMapping(ORM):
+
+    _table_name = 'ssh_key_guest_mapping'
+    _primary_key = 'id'
+
+    def __init__(self):
+        super(SSHKeyGuestMapping, self).__init__()
+        self.id = 0
+        self.ssh_key_id = None
+        self.guest_uuid = None
+
+    @staticmethod
+    def get_filter_keywords():
+        return {
+            'id': FilterFieldType.INT.value,
+            'ssh_key_id': FilterFieldType.INT.value,
+            'guest_uuid': FilterFieldType.STR.value,
+        }
+
+    @staticmethod
+    def get_allow_update_keywords():
+        return []
+
+    @staticmethod
+    def get_allow_content_search_keywords():
+        return ['guest_uuid']
+

+ 77 - 0
templates/ssh_keys_show.html

@@ -367,6 +367,10 @@
                     </td>
                     <td>
                         <div class="dropdown inline-block">
+                            <a href="javascript:;" style="color: #0066cc" data-toggle="modal" data-target="#ssh_key_bind_modal">绑定</a>
+                            <span> | </span>
+                            <a href="javascript:;" style="color: #0066cc" data-toggle="modal" data-target="#ssh_key_unbind_modal">解绑</a>
+                            <span> | </span>
                             <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
                                 更多
                             </a>
@@ -519,4 +523,77 @@
         </div>
     </div>
 </div>
+
+<div class="modal" id="ssh_key_bind_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">绑定 SSH Key:</h4>
+            </div>
+            <div class="modal-body" style="padding-top: 0; padding-bottom: 0;">
+                <div class="example-box-wrapper">
+                    <form id="add_ssh_key_form" class="form-horizontal bordered-row" action="/ssh_key" method="post">
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-elusive-compass-circled"></span>&nbsp;&nbsp;名称</label>
+                            <div class="col-sm-6">
+                                <input id="label" name="label" type="text" title="SSH Key 名称" class="form-control">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-newspaper-o"></span>&nbsp;&nbsp;Public Key</label>
+                            <div class="col-sm-6">
+                                <textarea id="public_key" name="public_key" title="Public key" class="form-control"></textarea>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4"></div>
+                            <div class="col-sm-6 pull-right">
+                                <button type="submit" class="btn btn-blue-alt" style="width: 180px; height: 40px; font-size: 16px;" disabled>创建</button>
+                                <button class="btn btn-default" style="width: 64px; height: 40px; font-size: 16px;" data-dismiss="modal">取消</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal" id="ssh_key_unbind_modal" tabindex="-1" role="dialog" style="margin-top: 100px;">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">解绑 SSH Key:</h4>
+            </div>
+            <div class="modal-body" style="padding-top: 0; padding-bottom: 0;">
+                <div class="example-box-wrapper">
+                    <form id="add_ssh_key_form" class="form-horizontal bordered-row" action="/ssh_key" method="post">
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-elusive-compass-circled"></span>&nbsp;&nbsp;名称</label>
+                            <div class="col-sm-6">
+                                <input id="label" name="label" type="text" title="SSH Key 名称" class="form-control">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-2"></div>
+                            <label class="col-sm-2 control-label"><span class="glyph-icon icon-newspaper-o"></span>&nbsp;&nbsp;Public Key</label>
+                            <div class="col-sm-6">
+                                <textarea id="public_key" name="public_key" title="Public key" class="form-control"></textarea>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4"></div>
+                            <div class="col-sm-6 pull-right">
+                                <button type="submit" class="btn btn-blue-alt" style="width: 180px; height: 40px; font-size: 16px;" disabled>创建</button>
+                                <button class="btn btn-default" style="width: 64px; height: 40px; font-size: 16px;" data-dismiss="modal">取消</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
 {% endblock content %}