James Iter пре 7 година
родитељ
комит
aa29705822

+ 4 - 9
docs/todo.md

@@ -61,18 +61,12 @@
 - [x] 融入 Systemd
 - [ ] 增加模板镜像上传功能
 - [x] 加入 Tag或分组,类似qq用户分组 功能
-- [ ] 添加对 Guest 的 IP 管理功能
-- [ ] 设计、制作替换 Guest IP 的系统级启动作业
-> 1. 给宿主机替换当前 IP 池中可用的 IP。替换后,新 IP 置为已用状态,被替换的 IP,
-> 要与系统配置中的可用 IP 范围做计算。计算其是不是可以被自动分配的合法 IP。如果是,
-> 则回收。如果不是,则直接丢弃;
-> 2. 给宿主机替换手动指定的任意 IP。此时需要判断手动分配的 IP 是否与已用的 IP 冲突;
 - [ ] 虚拟机状态变化时时展现在前端页面上
 - [ ] 迁移时计算出剩余时间
 - [ ] 初始化页面检测 redis、mysql 是否准备好
 - [ ] 日志里面加入操作轨迹
 - [ ] 考虑 vm_network,vm_manage_network 改名为 vm_network_bridge,vm_manage_network_bridge
-- [ ] 通过 sysctl 隔离出 vnc 使用的端口,避免被冲突。参见 net.ipv4.ip_local_reserved_ports
+- [x] 通过 sysctl 隔离出 vnc 使用的端口,避免被冲突。参见 net.ipv4.ip_local_reserved_ports
 - [ ] 考虑热更新、升级的问题
 - [ ] 加入清除老旧日志的功能
 - [ ] 忽略 guestfs- 为首的日志
@@ -82,9 +76,7 @@
 - [ ] 迁移中的虚拟机,不允许做任何操作
 - [x] 通过 QemuGuestAgent 实现 Guest 的内存使用率监控
 - [ ] 实现通过 JimV-C 修改主机名(当前为备注的地方),同步到具体 Guest 上
-- [ ] 修正创建虚拟机时,VNC 端口不够用时的 bug。创建失败时,需要返还对应的 IP 与 VNC 端口资源
 - [ ] 修复在大磁盘虚拟机创建快照时,等待时间过长的问题
-- [ ] IP 地址池管理功能。对 IP 池中的 IP 有查看、添加、禁用、解禁等功能
 - [x] 安装脚本加入中国本土仓库
 - [ ] 对某个 Guest 进行快照操作时,禁止对该镜像做其它快照方面的操作
 - [x] 修复JimV-C 先 Redis 启动后,JimV-C event_processor Redis lpop 异常的问题
@@ -102,4 +94,7 @@
 - [ ] 替换热迁移时计算节点的主机名为 IP 地址
 - [x] 保留 IP 列表(可用作 VIP 场景)
 - [x] IP 修正功能
+- [ ] 替换热迁移时计算节点的主机名为 IP 地址
+- [ ] 加入批量执行脚本功能
+- [ ] 创建虚拟机时,加入高级功能,选择 IP 池,选择分组
 

+ 4 - 0
jimvc/__init__.py

@@ -60,6 +60,8 @@ from jimvc.api.log import blueprint as log_blueprint
 from jimvc.api.log import blueprints as log_blueprints
 from jimvc.api.host import blueprint as host_blueprint
 from jimvc.api.host import blueprints as host_blueprints
+from jimvc.api.token import blueprint as token_blueprint
+from jimvc.api.token import blueprints as token_blueprints
 from jimvc.api.ssh_key import blueprint as ssh_key_blueprint
 from jimvc.api.ssh_key import blueprints as ssh_key_blueprints
 from jimvc.api.snapshot import blueprint as snapshot_blueprint
@@ -273,6 +275,8 @@ try:
     app.register_blueprint(log_blueprints)
     app.register_blueprint(host_blueprint)
     app.register_blueprint(host_blueprints)
+    app.register_blueprint(token_blueprint)
+    app.register_blueprint(token_blueprints)
     app.register_blueprint(ssh_key_blueprint)
     app.register_blueprint(ssh_key_blueprints)
     app.register_blueprint(snapshot_blueprint)

+ 2 - 2
jimvc/api/about.py

@@ -32,12 +32,12 @@ def r_get():
     ret = dict()
     ret['state'] = ji.Common.exchange_state(20000)
     ret['data'] = {
-        'version': 0.7,
+        'version': 0.8,
         'desc': """
     JimV 是一套舒适的驾驭系统。通过它,您可以轻松自如地,让那匹彪悍烈马,把您带上长空云霄。
     "舒适",是设计 JimV 的哲学理念。其中包含的意义有 "平滑、自在、敏捷、可控"。您所想、所要的,
 只需轻触鼠标,即可信手捏来。""",
-        'copyright': '版权所有2018 JamesIter. 保留所有权利。'
+        'copyright': '版权所有2019 上海正微网络科技有限公司。 保留所有权利。'
     }
 
     return ret

+ 76 - 0
jimvc/api/misc.py

@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import jimit as ji
+import json
+from flask import Blueprint, request
+
+from jimvc.models import app_config, Rules, Token
+from jimvc.models import Utils
+from jimvc.models import Host
+
+
+__author__ = 'James Iter'
+__date__ = '2018-12-29'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2018 by James Iter.'
+
+
+blueprint = Blueprint(
+    'api_misc',
+    __name__,
+    url_prefix='/api/misc'
+)
+
+blueprints = Blueprint(
+    'api_miscs',
+    __name__,
+    url_prefix='/api/miscs'
+)
+
+
+@Utils.dumps2response
+def r_join(node_id, _token):
+    # 如果该 node_id 已经被加入,那么要用户先去计算节点处删除作废的节点后,再添加。
+    ret = dict()
+    ret['state'] = ji.Common.exchange_state(20000)
+    ret['data'] = dict()
+
+    args_rules = [
+        Rules.NODE_ID.value,
+        Rules.TOKEN.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'node_id': node_id, 'token': _token})
+
+        token = Token()
+        token.token = token
+
+        # 检验 token 有效性
+        if not token.valid():
+            ret['state'] = ji.Common.exchange_state(41208)
+            return ret
+
+        nodes_id = list()
+        hosts = Host.get_all()
+
+        for host in hosts:
+            nodes_id.append(host['node_id'])
+
+        # 检测 node_id 是否已经存在
+        if node_id in nodes_id:
+            ret['state'] = ji.Common.exchange_state(40901)
+
+        else:
+            ret['data']['redis_host'] = request.host
+            ret['data']['redis_port'] = app_config.get('redis_port', 6379)
+            ret['data']['redis_password'] = app_config.get('redis_password', '')
+            ret['data']['redis_dbid'] = app_config.get('redis_dbid', 0)
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+

+ 102 - 0
jimvc/api/token.py

@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import json
+
+from flask import Blueprint
+import jimit as ji
+import time
+
+from jimvc.models import Utils, Rules
+from jimvc.models import Token
+
+
+__author__ = 'James Iter'
+__date__ = '2019-01-01'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2019 by James Iter.'
+
+
+blueprint = Blueprint(
+    'api_token',
+    __name__,
+    url_prefix='/api/token'
+)
+
+blueprints = Blueprint(
+    'api_tokens',
+    __name__,
+    url_prefix='/api/tokens'
+)
+
+
+@Utils.dumps2response
+def r_create():
+
+    try:
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+
+        token = Token()
+        token.generator()
+        token.create()
+
+        ret['data'] = {
+            'token': token.token,
+            'ttl': token.ttl,
+            'expire': time.strftime('%Y-%m-%dT%H:%M:%S %Z', time.localtime(ji.Common.ts() + token.ttl))
+        }
+
+        return ret
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_get_by_filter():
+
+    try:
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+        ret['data'] = list()
+
+        ts = ji.Common.ts()
+
+        for token in Token.get_all():
+            expire = time.strftime('%Y-%m-%dT%H:%M:%S %Z', time.localtime(token[1]))
+            ret['data'].append({'token': token[0], 'expire': expire, 'ttl': token[1] - ts})
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_delete(tokens):
+
+    args_rules = [
+        Rules.TOKENS.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {args_rules[0][1]: tokens})
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+        ret['data'] = list()
+
+        token = Token()
+
+        for _token in tokens.split(','):
+            token.token = _token
+            if token.delete():
+                ret['data'].append(_token)
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+

+ 6 - 1
jimvc/api_route_table.py

@@ -3,7 +3,7 @@
 
 
 from jimvc.models import add_rule_api
-from jimvc.api import config
+from jimvc.api import config, token
 from jimvc.api import ip_pool
 from jimvc.api import reserved_ip
 from jimvc.api import os_template_profile
@@ -179,6 +179,11 @@ add_rule_api(host.blueprints, '/<nodes_id>', api_func='host.r_delete', methods=[
 add_rule_api(host.blueprints, '/<hosts_name>/<random>', api_func='host.r_nonrandom', methods=['PUT'])
 add_rule_api(host.blueprints, '/_show', api_func='host.r_show', methods=['GET'])
 
+# Token 操作
+add_rule_api(token.blueprint, '', api_func='token.r_create', methods=['POST'])
+add_rule_api(token.blueprints, '', api_func='token.r_get_by_filter', methods=['GET'])
+add_rule_api(token.blueprints, '/<tokens>', api_func='token.r_delete', methods=['DELETE'])
+
 # SSH Key 操作
 add_rule_api(ssh_key.blueprint, '', api_func='ssh_key.r_create', methods=['POST'])
 add_rule_api(ssh_key.blueprints, '/<ids>', api_func='ssh_key.r_delete', methods=['DELETE'])

+ 5 - 1
jimvc/models/__init__.py

@@ -114,6 +114,10 @@ from host import (
     Host
 )
 
+from token import (
+    Token
+)
+
 from host_performance import (
     HostCPUMemory,
     HostTraffic,
@@ -146,6 +150,6 @@ __all__ = [
     'OSTemplateInitializeOperate', 'EventProcessor', 'ResponseState', 'GuestCPUMemory', 'GuestTraffic', 'GuestDiskIO',
     'HostCPUMemory', 'HostTraffic', 'HostDiskUsageIO', 'Host', 'Snapshot', 'SnapshotDiskMapping', 'OSTemplateImageKind',
     'GuestMigrateInfo', 'GuestCollectionPerformanceDataKind', 'HostCollectionPerformanceDataKind', 'StorageMode',
-    'OSTemplateInitializeOperateKind', 'dev_table', 'Project', 'Service', 'IPPool', 'ReservedIP'
+    'OSTemplateInitializeOperateKind', 'dev_table', 'Project', 'Service', 'IPPool', 'ReservedIP', 'Token'
 ]
 

+ 2 - 0
jimvc/models/rules.py

@@ -30,6 +30,7 @@ class Rules(Enum):
 
     ID = (REG_NUMBER, 'id')
     IDS = (REG_NUMBERS, 'ids')
+    TTL = (REG_NUMBER, 'ttl')
     HOSTS_NAME = (REG_HOSTS_NAME, 'hosts_name')
 
     CONFIG_ID = (int, 'id')
@@ -125,6 +126,7 @@ class Rules(Enum):
     OPERATE_RULE_COMMAND = (basestring, 'command')
 
     TOKEN = (basestring, 'token')
+    TOKENS = (basestring, 'tokens')
 
     PROJECT_ID = (int, 'project_id')
     PROJECTS_ID = (REG_NUMBERS, 'projects_id')

+ 48 - 0
jimvc/models/token.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import jimit as ji
+
+from jimvc.models import Database as db
+
+
+__author__ = 'James Iter'
+__date__ = '2019-01-01'
+__contact__ = 'james.iter.cn@gmail.com'
+__copyright__ = '(c) 2019 by James Iter.'
+
+
+class Token(object):
+
+    def __init__(self):
+        self.token = None
+        self.ttl = 86400
+
+    def generator(self):
+        self.token = ji.Common.generate_random_code(length=32)
+
+    def create(self):
+        key = 'Z:Token'
+        score = ji.Common.ts() + self.ttl
+        value = self.token
+
+        # 可重入,不会产生副作用
+        # http://redisdoc.com/sorted_set/zadd.html
+        db.r.zadd(key, score, value)
+
+    @staticmethod
+    def get_all():
+        return db.r.zrange('Z:Token', start=0, end=-1, withscores=True, score_cast_func=int)
+
+    def valid(self):
+        score = db.r.zscore('Z:Token', self.token)
+
+        if score is not None and score <= ji.Common.ts():
+            return True
+
+        return False
+
+    def delete(self):
+        return db.r.zrem('Z:Token', self.token)
+

+ 1 - 0
jimvc/themes/default/templates/about.html

@@ -44,6 +44,7 @@
                 <pre style="width: 800px; text-align: left; padding: 80px; display: inline-block;">
                     <span id="desc"></span>
 </pre>
+                <br>
                 <pre style="width: 800px; display: inline-block;">
 版本 <span id="version"></span>
 <span id="copyright"></span>

+ 68 - 1
jimvc/themes/default/templates/config_show.html

@@ -53,6 +53,7 @@
 
             register_iops_bps_event();
             refresh_ip_pools();
+            tokens_list_refresh();
 
             $('input.input-switch[name="activity"]').on('switchChange.bootstrapSwitch', function(event, state) {
                 console.log(this); // DOM element
@@ -82,7 +83,7 @@
             $(me).parent().parent().children().removeClass('active');
             $(me).parent().addClass('active');
 
-            $('#jimv_system_config, #quota, #ip_pool, #auxiliary').css('display', 'none');
+            $('#jimv_system_config, #quota, #ip_pool, #auxiliary, #token').css('display', 'none');
 
             if (me.id === 'quota_label') {
                 $('#quota').css('display', 'unset');
@@ -90,6 +91,9 @@
             } else if (me.id === 'ip_pool_label') {
                 $('#ip_pool').css('display', 'unset');
 
+            } else if (me.id === 'token_label') {
+                $('#token').css('display', 'unset');
+
             } else if (me.id === 'auxiliary_label') {
                 $('#auxiliary').css('display', 'unset');
 
@@ -600,6 +604,47 @@
                 }
             });
         }
+
+        function tokens_list_refresh() {
+            $.ajax({
+                url : '/api/tokens',
+                type : 'GET',
+                contentType: "application/json; charset=utf-8",
+                dataType: 'json',
+                async: false,
+                error : function() {
+                },
+                success : function(data, textStatus, xhr) {
+                    var html = NaN;
+                    $('#tokens_list_table tbody').empty();
+                    $.each(data.data, function(k, v) {
+                        html = '<tr role="row" class="odd">\n' +
+                            '    <td class="text-center">' + v['token'] + '</td>\n' +
+                            '    <td class="text-center">' + v['ttl'] + '</td>\n' +
+                            '    <td class="text-center">' + v['expire'] + '</td>\n' +
+                            '</tr>';
+                        $('#tokens_list_table tbody').append(html);
+                    });
+                }
+            });
+        }
+
+        function add_token() {
+            $.ajax({
+                url : '/api/token',
+                type : 'POST',
+                contentType: "application/json; charset=utf-8",
+                async: false,
+                error : function(data, textStatus, xhr) {
+                    alter_danger(data.responseText);
+                    alter_warning('添加 Token 失败!');
+                },
+                success : function(data, textStatus, xhr) {
+                    tokens_list_refresh();
+                    alter_success('添加 Token 成功!');
+                }
+            });
+        }
     </script>
     <div class="panel">
         <div class="panel-body">
@@ -607,6 +652,7 @@
                 <li class="active"><a href="javascript:;" onclick="select_this(this);" id="jimv_system_config_label">JimV 系统配置</a></li>
                 <li><a href="javascript:;" onclick="select_this(this);" id="ip_pool_label">IP 池</a></li>
                 <li><a href="javascript:;" onclick="select_this(this);" id="quota_label">性能配额</a></li>
+                <li><a href="javascript:;" onclick="select_this(this);" id="token_label">Token</a></li>
                 <li><a href="javascript:;" onclick="select_this(this);" id="auxiliary_label">辅助</a></li>
             </ul>
             <div id="jimv_system_config" class="example-box-wrapper">
@@ -740,6 +786,27 @@
             </div>
             <div id="ip_pool" class="example-box-wrapper" style="display: none;">
             </div>
+            <div id="token" class="example-box-wrapper" style="display: none;">
+                <div class="row" style="padding: 10px 10px 10px 0; width: 100%;">
+                    <div class="col-sm-12" style="padding-right: 0;">
+                        <div class="pull-right">
+                            <button class="btn btn-default" onclick="window.location.reload();" style="border-radius: 0;"><span class="glyph-icon icon-elusive-arrows-cw"></span></button>
+                            <a class="btn btn-info" href="javascript:;" onclick="add_token();" style="border-radius: 0; padding-left: 40px; padding-right: 40px;">添加 Token</a>
+                        </div>
+                    </div>
+                </div>
+                <table id="tokens_list_table" class="table table-bordered table-hover" cellspacing="0" width="100%" role="grid" style="width: 100%;">
+                    <thead>
+                    <tr role="row">
+                        <th class="text-center">Token</th>
+                        <th class="text-center">TTL</th>
+                        <th class="text-center">逾期时间</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    </tbody>
+                </table>
+            </div>
             <div id="auxiliary" class="example-box-wrapper" style="display: none;">
                 <form class="form-horizontal bordered-row" style="padding-left: 8%;">
                     <div class="form-group" style="margin-bottom: 0;">