_plugin.py 34.7 KB
Newer Older
O
oceanbase-admin 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# coding: utf-8
# OceanBase Deploy.
# Copyright (C) 2021 OceanBase
#
# This file is part of OceanBase Deploy.
#
# OceanBase Deploy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OceanBase Deploy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OceanBase Deploy.  If not, see <https://www.gnu.org/licenses/>.


from __future__ import absolute_import, division, print_function

import os
R
Rongfeng Fu 已提交
24
import re
O
oceanbase-admin 已提交
25 26 27
import sys
from enum import Enum
from glob import glob
R
Rongfeng Fu 已提交
28
from copy import deepcopy, copy
O
oceanbase-admin 已提交
29 30

from _manager import Manager
R
Rongfeng Fu 已提交
31
from _rpm import Version
F
v1.5.0  
frf12 已提交
32
from ssh import ConcurrentExecutor
F
v1.6.0  
frf12 已提交
33
from tool import ConfigUtil, DynamicLoading, YamlLoader, FileUtil
O
oceanbase-admin 已提交
34 35 36 37 38 39 40


yaml = YamlLoader()


class PluginType(Enum):

F
v1.6.0  
frf12 已提交
41
    # 插件类型 = 插件加载类
O
oceanbase-admin 已提交
42 43 44
    START = 'StartPlugin'
    PARAM = 'ParamPlugin'
    INSTALL = 'InstallPlugin'
F
v1.6.0  
frf12 已提交
45
    SNAP_CONFIG = 'SnapConfigPlugin'
O
oceanbase-admin 已提交
46 47 48 49
    PY_SCRIPT = 'PyScriptPlugin'


class Plugin(object):
R
Rongfeng Fu 已提交
50

O
oceanbase-admin 已提交
51 52 53
    PLUGIN_TYPE = None
    FLAG_FILE = None

R
Rongfeng Fu 已提交
54
    def __init__(self, component_name, plugin_path, version, dev_mode):
O
oceanbase-admin 已提交
55 56 57 58
        if not self.PLUGIN_TYPE or not self.FLAG_FILE:
            raise NotImplementedError
        self.component_name = component_name
        self.plugin_path = plugin_path
R
Rongfeng Fu 已提交
59
        self.version = Version(version)
R
Rongfeng Fu 已提交
60
        self.dev_mode = dev_mode
O
oceanbase-admin 已提交
61 62

    def __str__(self):
R
Rongfeng Fu 已提交
63
        return '%s-%s-%s' % (self.component_name, self.PLUGIN_TYPE.name.lower(), self.version)
O
oceanbase-admin 已提交
64 65 66 67 68 69

    @property
    def mirror_type(self):
        return self.PLUGIN_TYPE


R
Rongfeng Fu 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
class PluginContextNamespace:

    def __init__(self, spacename):
        self.spacename = spacename
        self._variables = {}
        self._return = {}

    @property
    def variables(self):
        return self._variables

    def get_variable(self, name):
        return self._variables.get(name)

    def set_variable(self, name, value):
        self._variables[name] = value

    def get_return(self, plugin_name):
        ret = self._return.get(plugin_name)
        if isinstance(ret, PluginReturn):
            return ret
        return None

    def set_return(self, plugin_name, plugin_return):
        self._return[plugin_name] = plugin_return


O
oceanbase-admin 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
class PluginReturn(object):

    def __init__(self, value=False, *arg, **kwargs):
        self._return_value = value
        self._return_args = arg
        self._return_kwargs = kwargs

    def __nonzero__(self):
        return self.__bool__()

    def __bool__(self):
        return True if self._return_value else False

    @property
    def value(self):
        return self._return_value
R
Rongfeng Fu 已提交
113

O
oceanbase-admin 已提交
114 115 116 117 118 119 120
    @property
    def args(self):
        return self._return_args

    @property
    def kwargs(self):
        return self._return_kwargs
R
Rongfeng Fu 已提交
121 122 123

    def get_return(self, key, default=None):
        return self.kwargs.get(key, default)
O
oceanbase-admin 已提交
124 125 126 127 128 129 130 131 132

    def set_args(self, *args):
        self._return_args = args

    def set_kwargs(self, **kwargs):
        self._return_kwargs = kwargs

    def set_return(self, value):
        self._return_value = value
R
Rongfeng Fu 已提交
133

O
oceanbase-admin 已提交
134 135 136 137
    def return_true(self, *args, **kwargs):
        self.set_return(True)
        self.set_args(*args)
        self.set_kwargs(**kwargs)
R
Rongfeng Fu 已提交
138

O
oceanbase-admin 已提交
139 140 141 142 143 144 145 146
    def return_false(self, *args, **kwargs):
        self.set_return(False)
        self.set_args(*args)
        self.set_kwargs(**kwargs)


class PluginContext(object):

R
Rongfeng Fu 已提交
147 148 149 150 151 152
    def __init__(self, plugin_name, namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmd, options, dev_mode, stdio):
        self.namespace = namespace
        self.namespaces = namespaces
        self.deploy_name  = deploy_name
        self.repositories =repositories
        self.plugin_name = plugin_name
O
oceanbase-admin 已提交
153 154 155
        self.components = components
        self.clients = clients
        self.cluster_config = cluster_config
R
Rongfeng Fu 已提交
156
        self.cmds = cmd
O
oceanbase-admin 已提交
157
        self.options = options
R
Rongfeng Fu 已提交
158
        self.dev_mode = dev_mode
O
oceanbase-admin 已提交
159
        self.stdio = stdio
F
v1.6.0  
frf12 已提交
160
        self.concurrent_executor = ConcurrentExecutor(32)
O
oceanbase-admin 已提交
161 162
        self._return = PluginReturn()

R
Rongfeng Fu 已提交
163 164 165 166 167 168 169 170
    def get_return(self, plugin_name=None, spacename=None):
        if spacename:
            namespace = self.namespaces.get(spacename)
        else:
            namespace = self.namespace
        if plugin_name is None:
            plugin_name = self.plugin_name
        return namespace.get_return(plugin_name) if namespace else None
O
oceanbase-admin 已提交
171 172 173

    def return_true(self, *args, **kwargs):
        self._return.return_true(*args, **kwargs)
R
Rongfeng Fu 已提交
174 175
        self.namespace.set_return(self.plugin_name, self._return)

O
oceanbase-admin 已提交
176 177
    def return_false(self, *args, **kwargs):
        self._return.return_false(*args, **kwargs)
R
Rongfeng Fu 已提交
178 179 180 181 182 183 184 185 186 187 188
        self.namespace.set_return(self.plugin_name, self._return)

    def get_variable(self, name, spacename=None):
        if spacename:
            namespace = self.namespaces.get(spacename)
        else:
            namespace = self.namespace
        return namespace.get_variable(name) if namespace else None

    def set_variable(self, name, value):
        self.namespace.set_variable(name, value)
O
oceanbase-admin 已提交
189 190 191 192 193 194 195 196 197 198


class SubIO(object):

    def __init__(self, stdio):
        self.stdio = getattr(stdio, 'sub_io', lambda: None)()
        self._func = {}

    def __del__(self):
        self.before_close()
R
Rongfeng Fu 已提交
199

O
oceanbase-admin 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
    def _temp_function(self, *arg, **kwargs):
        pass

    def __getattr__(self, name):
        if name not in self._func:
            self._func[name] = getattr(self.stdio, name, self._temp_function)
        return self._func[name]


class ScriptPlugin(Plugin):

    class ClientForScriptPlugin(object):

        def __init__(self, client, stdio):
            self.client = client
            self.stdio = stdio

        def __getattr__(self, key):
            def new_method(*args, **kwargs):
F
v1.5.0  
frf12 已提交
219 220
                if "stdio" not in kwargs:
                    kwargs['stdio'] = self.stdio
O
oceanbase-admin 已提交
221 222 223 224 225 226
                return attr(*args, **kwargs)
            attr = getattr(self.client, key)
            if hasattr(attr, '__call__'):
                return new_method
            return attr

R
Rongfeng Fu 已提交
227 228
    def __init__(self, component_name, plugin_path, version, dev_mode):
        super(ScriptPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
O
oceanbase-admin 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242
        self.context = None

    def __call__(self):
        raise NotImplementedError

    def _import(self, stdio=None):
        raise NotImplementedError

    def _export(self):
        raise NotImplementedError

    def __del__(self):
        self._export()

R
Rongfeng Fu 已提交
243 244 245 246 247
    def before_do(
        self, plugin_name, namespace, namespaces, deploy_name,
        repositories, components, clients, cluster_config, cmd,
        options, stdio, *arg, **kwargs
        ):
O
oceanbase-admin 已提交
248 249 250 251 252
        self._import(stdio)
        sub_stdio = SubIO(stdio)
        sub_clients = {}
        for server in clients:
            sub_clients[server] = ScriptPlugin.ClientForScriptPlugin(clients[server], sub_stdio)
R
Rongfeng Fu 已提交
253 254 255 256 257
        self.context = PluginContext(
            plugin_name, namespace, namespaces, deploy_name, repositories, components,
            sub_clients, cluster_config, cmd, options, self.dev_mode, sub_stdio
        )
        namespace.set_return(plugin_name, None)
O
oceanbase-admin 已提交
258 259 260 261 262 263 264

    def after_do(self, stdio, *arg, **kwargs):
        self._export(stdio)
        self.context = None


def pyScriptPluginExec(func):
R
Rongfeng Fu 已提交
265 266 267 268 269 270 271 272
    def _new_func(
        self, namespace, namespaces, deploy_name,
        repositories, components, clients, cluster_config, cmd,
        options, stdio, *arg, **kwargs
        ):
        self.before_do(self.name, namespace, namespaces, deploy_name,
        repositories, components, clients, cluster_config, cmd,
        options, stdio, *arg, **kwargs)
O
oceanbase-admin 已提交
273 274 275
        if self.module:
            method_name = func.__name__
            method = getattr(self.module, method_name, False)
R
Rongfeng Fu 已提交
276 277 278
            namespace_vars = copy(self.context.namespace.variables)
            namespace_vars.update(kwargs)
            kwargs = namespace_vars
O
oceanbase-admin 已提交
279 280
            if method:
                try:
R
Rongfeng Fu 已提交
281 282 283
                    ret = method(self.context, *arg, **kwargs)
                    if ret is None and self.context and self.context.get_return() is None:
                        self.context.return_false()
O
oceanbase-admin 已提交
284
                except Exception as e:
R
Rongfeng Fu 已提交
285
                    self.context.return_false(exception=e)
O
oceanbase-admin 已提交
286 287 288 289 290 291 292 293 294 295
                    stdio and getattr(stdio, 'exception', print)('%s RuntimeError: %s' % (self, e))
        ret = self.context.get_return() if self.context else PluginReturn()
        self.after_do(stdio, *arg, **kwargs)
        return ret
    return _new_func


class PyScriptPlugin(ScriptPlugin):

    LIBS_PATH = []
R
Rongfeng Fu 已提交
296
    PLUGIN_NAME = None
O
oceanbase-admin 已提交
297

R
Rongfeng Fu 已提交
298
    def __init__(self, component_name, plugin_path, version, dev_mode):
R
Rongfeng Fu 已提交
299
        if not self.PLUGIN_NAME:
O
oceanbase-admin 已提交
300
            raise NotImplementedError
R
Rongfeng Fu 已提交
301
        super(PyScriptPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
O
oceanbase-admin 已提交
302
        self.module = None
R
Rongfeng Fu 已提交
303
        self.name = self.PLUGIN_NAME
O
oceanbase-admin 已提交
304 305 306
        self.libs_path = deepcopy(self.LIBS_PATH)
        self.libs_path.append(self.plugin_path)

R
Rongfeng Fu 已提交
307 308 309 310 311 312
    def __call__(
        self, namespace, namespaces, deploy_name,
        repositories, components, clients, cluster_config, cmd,
        options, stdio, *arg, **kwargs
        ):
        method = getattr(self, self.PLUGIN_NAME, False)
O
oceanbase-admin 已提交
313
        if method:
R
Rongfeng Fu 已提交
314 315 316 317 318
            return method(
                namespace, namespaces, deploy_name,
                repositories, components, clients, cluster_config, cmd,
                options, stdio, *arg, **kwargs
            )
O
oceanbase-admin 已提交
319 320 321 322 323 324
        else:
            raise NotImplementedError

    def _import(self, stdio=None):
        if self.module is None:
            DynamicLoading.add_libs_path(self.libs_path)
R
Rongfeng Fu 已提交
325
            self.module = DynamicLoading.import_module(self.PLUGIN_NAME, stdio)
O
oceanbase-admin 已提交
326 327 328 329

    def _export(self, stdio=None):
        if self.module:
            DynamicLoading.remove_libs_path(self.libs_path)
R
Rongfeng Fu 已提交
330
            DynamicLoading.export_module(self.PLUGIN_NAME, stdio)
O
oceanbase-admin 已提交
331 332 333 334 335

# this is PyScriptPlugin demo
# class InitPlugin(PyScriptPlugin):

#     FLAG_FILE = 'init.py'
R
Rongfeng Fu 已提交
336
#     PLUGIN_NAME = 'init'
O
oceanbase-admin 已提交
337 338 339 340 341 342
#     PLUGIN_TYPE = PluginType.INIT

#     def __init__(self, component_name, plugin_path, version):
#         super(InitPlugin, self).__init__(component_name, plugin_path, version)

#     @pyScriptPluginExec
R
Rongfeng Fu 已提交
343 344 345 346
#     def init(
#         self, namespace, namespaces, deploy_name,
#         repositories, components, clients, cluster_config, cmd,
#         options, stdio, *arg, **kwargs):
O
oceanbase-admin 已提交
347 348
#         pass

F
v1.6.0  
frf12 已提交
349 350 351 352 353
class Null(object):

    def __init__(self):
        pass

O
oceanbase-admin 已提交
354 355 356

class ParamPlugin(Plugin):

F
v1.6.0  
frf12 已提交
357

R
Rongfeng Fu 已提交
358 359 360
    class ConfigItemType(object):

        TYPE_STR = None
F
v1.6.0  
frf12 已提交
361
        NULL = Null()
R
Rongfeng Fu 已提交
362 363 364 365 366

        def __init__(self, s):
            try:
                self._origin = s
                self._value = 0
F
v1.6.0  
frf12 已提交
367
                self.value = self.NULL
R
Rongfeng Fu 已提交
368
                self._format()
F
v1.6.0  
frf12 已提交
369 370
                if self.value == self.NULL:
                    self.value = self._origin
R
Rongfeng Fu 已提交
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
            except:
                raise Exception("'%s' is not %s" % (self._origin, self._type_str))

        @property
        def _type_str(self):
            if self.TYPE_STR is None:
                self.TYPE_STR = str(self.__class__.__name__).split('.')[-1]
            return self.TYPE_STR

        def _format(self):
            raise NotImplementedError

        def __str__(self):
            return str(self._origin)

        def __hash__(self):
            return self._origin.__hash__()

        @property
        def __cmp_value__(self):
            return self._value

        def __eq__(self, value):
            if value is None:
                return False
            return self.__cmp_value__ == value.__cmp_value__

        def __gt__(self, value):
            if value is None:
                return True
            return self.__cmp_value__ > value.__cmp_value__

        def __ge__(self, value):
            if value is None:
                return True
            return self.__eq__(value) or self.__gt__(value)

        def __lt__(self, value):
            if value is None:
                return False
            return self.__cmp_value__ < value.__cmp_value__

        def __le__(self, value):
            if value is None:
                return False
            return self.__eq__(value) or self.__lt__(value)


    class Moment(ConfigItemType):

        def _format(self):
            if self._origin:
                if self._origin.upper() == 'DISABLE':
                    self._value = 0
                else:
                    r = re.match('^(\d{1,2}):(\d{1,2})$', self._origin)
                    h, m = r.groups()
                    h, m = int(h), int(m)
                    if 0 <= h <= 23 and 0 <= m <= 60:
                        self._value = h * 60 + m
                    else:
                        raise Exception('Invalid Value')
            else:
                self._value = 0
R
Rongfeng Fu 已提交
435

R
Rongfeng Fu 已提交
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    class Time(ConfigItemType):

        UNITS = {
            'ns': 0.000000001,
            'us': 0.000001,
            'ms': 0.001,
            's': 1,
            'm': 60,
            'h': 3600,
            'd': 86400
        }

        def _format(self):
            if self._origin:
                self._origin = str(self._origin).strip()
                if self._origin.isdigit():
                    n = self._origin
                    unit = self.UNITS['s']
                else:
                    r = re.match('^(\d+)(\w+)$', self._origin.lower())
                    n, u = r.groups()
                unit = self.UNITS.get(u.lower())
                if unit:
                    self._value = int(n) * unit
                else:
                    raise Exception('Invalid Value')
            else:
                self._value = 0

    class Capacity(ConfigItemType):
R
Rongfeng Fu 已提交
466

R
Rongfeng Fu 已提交
467 468 469 470 471 472 473 474 475 476 477
        UNITS = {"B": 1, "K": 1<<10, "M": 1<<20, "G": 1<<30, "T": 1<<40, 'P': 1 << 50}

        def _format(self):
            if self._origin:
                self._origin = str(self._origin).strip()
                if self._origin.isdigit():
                    n = self._origin
                    unit = self.UNITS['M']
                else:
                    r = re.match('^(\d+)(\w)B?$', self._origin.upper())
                    n, u = r.groups()
R
Rongfeng Fu 已提交
478
                    unit = self.UNITS.get(u.upper())
R
Rongfeng Fu 已提交
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
                if unit:
                    self._value = int(n) * unit
                else:
                    raise Exception('Invalid Value')
            else:
                self._value = 0

    class StringList(ConfigItemType):

        def _format(self):
            if self._origin:
                self._origin = str(self._origin).strip()
                self._value = self._origin.split(';')
            else:
                self._value = []

F
v1.6.0  
frf12 已提交
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
    class Dict(ConfigItemType):

        def _format(self):
            if self._origin:
                if not isinstance(self._origin, dict):
                    raise Exception("Invalid Value")
                self._value = self._origin
            else:
                self._value = self.value = {}

    class List(ConfigItemType):

        def _format(self):
            if self._origin:
                if not isinstance(self._origin, list):
                    raise Exception("Invalid value: {} is not a list.".format(self._origin))
                self._value = self._origin
            else:
                self._value = self.value = []

    class StringOrKvList(ConfigItemType):

        def _format(self):
            if self._origin:
                if not isinstance(self._origin, list):
                    raise Exception("Invalid value: {} is not a list.".format(self._origin))
                for item in self._origin:
                    if not item:
                        continue
                    if not isinstance(item, (str, dict)):
                        raise Exception("Invalid value: {} should be string or key-value format.".format(item))
                    if isinstance(item, dict):
                        if len(item.keys()) != 1:
                            raise Exception("Invalid value: {} should be single key-value format".format(item))
                self._value = self._origin
            else:
                self._value = self.value = []

R
Rongfeng Fu 已提交
533 534 535
    class Double(ConfigItemType):

        def _format(self):
F
v1.6.0  
frf12 已提交
536
            self.value = self._value = float(self._origin) if self._origin else 0
R
Rongfeng Fu 已提交
537 538 539 540 541 542 543 544

    class Boolean(ConfigItemType):

        def _format(self):
            if isinstance(self._origin, bool):
                self._value = self._origin
            else:
                _origin = str(self._origin).lower()
F
v1.6.0  
frf12 已提交
545 546 547 548 549
                if _origin == 'true':
                    self._value = True
                elif _origin == 'false':
                    self._value = False
                elif _origin.isdigit():
R
Rongfeng Fu 已提交
550 551
                    self._value = bool(self._origin)
                else:
F
v1.6.0  
frf12 已提交
552 553
                    raise Exception('%s is not Boolean' % _origin)
            self.value = self._value
R
Rongfeng Fu 已提交
554 555 556 557 558 559 560 561 562

    class Integer(ConfigItemType):

        def _format(self):
            if self._origin is None:
                self._value = 0
                self._origin = 0
            else:
                _origin = str(self._origin)
F
v1.6.0  
frf12 已提交
563 564 565 566
                try:
                    self.value = self._value = int(_origin)
                except:
                    raise Exception('%s is not Integer' % _origin)
R
Rongfeng Fu 已提交
567 568 569 570

    class String(ConfigItemType):

        def _format(self):
F
v1.6.0  
frf12 已提交
571
            self.value = self._value = str(self._origin) if self._origin else ''
R
Rongfeng Fu 已提交
572

O
oceanbase-admin 已提交
573 574
    class ConfigItem(object):

R
Rongfeng Fu 已提交
575 576 577
        def __init__(
            self,
            name,
R
Rongfeng Fu 已提交
578 579 580 581 582 583 584 585 586
            param_type=str,
            default=None,
            min_value=None,
            max_value=None,
            require=False,
            essential=False,
            section="",
            need_reload=False,
            need_restart=False,
R
Rongfeng Fu 已提交
587
            need_redeploy=False,
R
Rongfeng Fu 已提交
588 589 590 591
            modify_limit=None,
            name_local=None,
            description_en=None,
            description_local=None
R
Rongfeng Fu 已提交
592
        ):
O
oceanbase-admin 已提交
593 594 595
            self.name = name
            self.default = default
            self.require = require
R
Rongfeng Fu 已提交
596 597 598
            self.essential = essential
            self.section = section
            self.need_reload = need_reload
O
oceanbase-admin 已提交
599 600
            self.need_restart = need_restart
            self.need_redeploy = need_redeploy
R
Rongfeng Fu 已提交
601 602 603 604 605
            self._param_type = param_type
            self.min_value = param_type(min_value) if min_value is not None else None
            self.max_value = param_type(max_value) if max_value is not None else None
            self.modify_limit = getattr(self, ('_%s_limit' % modify_limit).lower(), self._none_limit)
            self.had_modify_limit = self.modify_limit != self._none_limit
R
Rongfeng Fu 已提交
606 607 608
            self.name_local = name_local if name_local is not None else self.name
            self.description_en = description_en
            self.description_local = description_local if description_local is not None else self.description_en
O
oceanbase-admin 已提交
609

R
Rongfeng Fu 已提交
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
        def param_type(self, value):
            try:
                return self._param_type(value)
            except Exception as e:
                raise Exception('%s: %s' % (self.name, e))

        def check_value(self, value):
            if not isinstance(value, self._param_type):
                value = self.param_type(value)
            if self.min_value is not None and value < self.min_value:
                raise Exception('%s less then %s' % (self.name, self.min_value))
            if self.max_value is not None and value > self.max_value:
                raise Exception('%s more then %s' % (self.name, self.max_value))
            return True

        def _modify_limit(self, old_value, new_value):
            if old_value == new_value:
                return True
            raise Exception('DO NOT modify %s after startup' % self.name)
R
Rongfeng Fu 已提交
629

R
Rongfeng Fu 已提交
630 631 632 633
        def _increase_limit(self, old_value, new_value):
            if self.param_type(new_value) > self.param_type(old_value):
                raise Exception('DO NOT increase %s after startup' % self.name)
            return True
R
Rongfeng Fu 已提交
634

R
Rongfeng Fu 已提交
635 636 637 638 639 640 641
        def _decrease_limit(self, old_value, new_value):
            if self.param_type(new_value) < self.param_type(old_value):
                raise Exception('DO NOT decrease %s after startup' % self.name)
            return True

        def _none_limit(self, old_value, new_value):
            return True
R
Rongfeng Fu 已提交
642

O
oceanbase-admin 已提交
643 644 645 646
    PLUGIN_TYPE = PluginType.PARAM
    DEF_PARAM_YAML = 'parameter.yaml'
    FLAG_FILE = DEF_PARAM_YAML

R
Rongfeng Fu 已提交
647 648
    def __init__(self, component_name, plugin_path, version, dev_mode):
        super(ParamPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
O
oceanbase-admin 已提交
649 650
        self.def_param_yaml_path = os.path.join(self.plugin_path, self.DEF_PARAM_YAML)
        self._src_data = None
R
Rongfeng Fu 已提交
651 652 653 654
        self._need_redploy_items = None
        self._had_modify_limit_items = None
        self._need_restart_items = None
        self._params_default = None
O
oceanbase-admin 已提交
655 656 657 658 659

    @property
    def params(self):
        if self._src_data is None:
            try:
R
Rongfeng Fu 已提交
660 661 662 663 664 665 666 667
                TYPES = {
                    'DOUBLE': ParamPlugin.Double,
                    'BOOL': ParamPlugin.Boolean,
                    'INT': ParamPlugin.Integer,
                    'STRING': ParamPlugin.String,
                    'MOMENT': ParamPlugin.Moment,
                    'TIME': ParamPlugin.Time,
                    'CAPACITY': ParamPlugin.Capacity,
F
v1.6.0  
frf12 已提交
668 669 670 671
                    'STRING_LIST': ParamPlugin.StringList,
                    'DICT': ParamPlugin.Dict,
                    'LIST': ParamPlugin.List,
                    'PARAM_LIST': ParamPlugin.StringOrKvList
R
Rongfeng Fu 已提交
672
                }
O
oceanbase-admin 已提交
673 674 675 676
                self._src_data = {}
                with open(self.def_param_yaml_path, 'rb') as f:
                    configs = yaml.load(f)
                    for conf in configs:
F
v1.6.0  
frf12 已提交
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
                        try:
                            param_type = ConfigUtil.get_value_from_dict(conf, 'type', 'STRING').upper()
                            if param_type in TYPES:
                                param_type = TYPES[param_type]
                            else:
                                param_type = ParamPlugin.String

                            self._src_data[conf['name']] = ParamPlugin.ConfigItem(
                                name=conf['name'],
                                param_type=param_type,
                                default=ConfigUtil.get_value_from_dict(conf, 'default', None),
                                min_value=ConfigUtil.get_value_from_dict(conf, 'min_value', None),
                                max_value=ConfigUtil.get_value_from_dict(conf, 'max_value', None),
                                modify_limit=ConfigUtil.get_value_from_dict(conf, 'modify_limit', None),
                                require=ConfigUtil.get_value_from_dict(conf, 'require', False),
R
Rongfeng Fu 已提交
692 693 694
                                section=ConfigUtil.get_value_from_dict(conf, 'section', ""),
                                essential=ConfigUtil.get_value_from_dict(conf, 'essential', False),
                                need_reload=ConfigUtil.get_value_from_dict(conf, 'need_reload', False),
F
v1.6.0  
frf12 已提交
695
                                need_restart=ConfigUtil.get_value_from_dict(conf, 'need_restart', False),
R
Rongfeng Fu 已提交
696 697 698
                                need_redeploy=ConfigUtil.get_value_from_dict(conf, 'need_redeploy', False),
                                description_en=ConfigUtil.get_value_from_dict(conf, 'description_en', None),
                                description_local=ConfigUtil.get_value_from_dict(conf, 'description_local', None),
F
v1.6.0  
frf12 已提交
699 700 701
                            )
                        except:
                            pass
O
oceanbase-admin 已提交
702 703 704 705
            except:
                pass
        return self._src_data

R
Rongfeng Fu 已提交
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
    @property
    def redploy_params(self):
        if self._need_redploy_items is None:
            self._need_redploy_items = []
            params = self.params
            for name in params:
                conf = params[name]
                if conf.need_redeploy:
                    self._need_redploy_items.append(conf)
        return self._need_redploy_items

    @property
    def modify_limit_params(self):
        if self._had_modify_limit_items is None:
            self._had_modify_limit_items = []
            params = self.params
            for name in params:
                conf = params[name]
                if conf.had_modify_limit:
                    self._had_modify_limit_items.append(conf)
        return self._had_modify_limit_items

    @property
    def restart_params(self):
        if self._need_restart_items is None:
            self._need_restart_items = []
            params = self.params
            for name in params:
                conf = params[name]
                if conf.need_restart:
                    self._need_restart_items.append(conf)
        return self._need_restart_items

    @property
    def params_default(self):
        if self._params_default is None:
            self._params_default = {}
            params = self.params
            for name in params:
                conf = params[name]
R
Rongfeng Fu 已提交
746
                self._params_default[conf.name] = conf.default
R
Rongfeng Fu 已提交
747
        return self._params_default
O
oceanbase-admin 已提交
748 749


F
v1.6.0  
frf12 已提交
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
class SnapConfigPlugin(Plugin):

    PLUGIN_TYPE = PluginType.SNAP_CONFIG
    CONFIG_YAML = 'snap_config.yaml'
    FLAG_FILE = CONFIG_YAML
    _KEYCRE = re.compile(r"\$(\w+)")

    def __init__(self, component_name, plugin_path, version, dev_mode):
        super(SnapConfigPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
        self.config_path = os.path.join(self.plugin_path, self.CONFIG_YAML)
        self._config = None
        self._file_hash = None

    def __hash__(self):
        if self._file_hash is None:
            self._file_hash = int(''.join(['%03d' % (ord(v) if isinstance(v, str) else v) for v in FileUtil.checksum(self.config_path)]))
        return self._file_hash

    @property
    def config(self):
        if self._config is None:
            with open(self.config_path, 'rb') as f:
                self._config = yaml.load(f)
        return self._config

    @property
    def backup(self):
        return self.config.get('backup', [])

    @property
    def clean(self):
        return self.config.get('clean', [])


O
oceanbase-admin 已提交
784 785
class InstallPlugin(Plugin):

R
Rongfeng Fu 已提交
786 787 788 789 790 791
    class FileItemType(Enum):

        FILE = 0
        DIR = 1
        BIN = 2

F
v1.5.0  
frf12 已提交
792 793 794 795 796
    class InstallMethod(Enum):

        ANY = 0
        CP = 1

O
oceanbase-admin 已提交
797 798
    class FileItem(object):

F
v1.5.0  
frf12 已提交
799
        def __init__(self, src_path, target_path, _type, install_method):
O
oceanbase-admin 已提交
800 801
            self.src_path = src_path
            self.target_path = target_path
R
Rongfeng Fu 已提交
802
            self.type = _type if _type else InstallPlugin.FileItemType.FILE
F
v1.5.0  
frf12 已提交
803
            self.install_method = install_method or InstallPlugin.InstallMethod.ANY
O
oceanbase-admin 已提交
804 805 806 807

    PLUGIN_TYPE = PluginType.INSTALL
    FILES_MAP_YAML = 'file_map.yaml'
    FLAG_FILE = FILES_MAP_YAML
R
Rongfeng Fu 已提交
808
    _KEYCRE = re.compile(r"\$(\w+)")
O
oceanbase-admin 已提交
809

R
Rongfeng Fu 已提交
810 811
    def __init__(self, component_name, plugin_path, version, dev_mode):
        super(InstallPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
O
oceanbase-admin 已提交
812
        self.file_map_path = os.path.join(self.plugin_path, self.FILES_MAP_YAML)
R
Rongfeng Fu 已提交
813
        self._file_map = {}
F
v1.5.0  
frf12 已提交
814
        self._file_map_data = None
F
v1.6.0  
frf12 已提交
815
        self._check_value = None
R
Rongfeng Fu 已提交
816 817 818 819 820

    @classmethod
    def var_replace(cls, string, var):
        if not var:
            return string
R
Rongfeng Fu 已提交
821
        done = []
R
Rongfeng Fu 已提交
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838

        while string:
            m = cls._KEYCRE.search(string)
            if not m:
                done.append(string)
                break

            varname = m.group(1).lower()
            replacement = var.get(varname, m.group())

            start, end = m.span()
            done.append(string[:start])
            done.append(str(replacement))
            string = string[end:]

        return ''.join(done)

F
v1.6.0  
frf12 已提交
839 840 841 842 843 844
    @property
    def check_value(self):
        if self._check_value is None:
            self._check_value = os.path.getmtime(self.file_map_path)
        return self._check_value

F
v1.5.0  
frf12 已提交
845 846 847 848 849 850 851
    @property
    def file_map_data(self):
        if self._file_map_data is None:
            with open(self.file_map_path, 'rb') as f:
                self._file_map_data = yaml.load(f)
        return self._file_map_data

R
Rongfeng Fu 已提交
852 853 854 855 856 857 858 859 860 861
    def file_map(self, package_info):
        var = {
            'name': package_info.name,
            'version': package_info.version,
            'release': package_info.release,
            'arch': package_info.arch,
            'md5': package_info.md5,
        }
        key = str(var)
        if not self._file_map.get(key):
O
oceanbase-admin 已提交
862
            try:
R
Rongfeng Fu 已提交
863
                file_map = {}
F
v1.5.0  
frf12 已提交
864 865 866 867 868 869 870 871 872 873 874
                for data in self.file_map_data:
                    k = data['src_path']
                    if k[0] != '.':
                        k = '.%s' % os.path.join('/', k)
                    k = self.var_replace(k, var)
                    file_map[k] = InstallPlugin.FileItem(
                        k,
                        ConfigUtil.get_value_from_dict(data, 'target_path', k),
                        getattr(InstallPlugin.FileItemType, ConfigUtil.get_value_from_dict(data, 'type', 'FILE').upper(), None),
                        getattr(InstallPlugin.InstallMethod, ConfigUtil.get_value_from_dict(data, 'install_method', 'ANY').upper(), None),
                    )
R
Rongfeng Fu 已提交
875
                self._file_map[key] = file_map
O
oceanbase-admin 已提交
876 877
            except:
                pass
R
Rongfeng Fu 已提交
878
        return self._file_map[key]
O
oceanbase-admin 已提交
879

R
Rongfeng Fu 已提交
880 881
    def file_list(self, package_info):
        file_map = self.file_map(package_info)
O
oceanbase-admin 已提交
882 883 884 885
        return [file_map[k] for k in file_map]



R
Rongfeng Fu 已提交
886

O
oceanbase-admin 已提交
887 888 889 890
class ComponentPluginLoader(object):

    PLUGIN_TYPE = None

R
Rongfeng Fu 已提交
891
    def __init__(self, home_path, plugin_type=PLUGIN_TYPE, dev_mode=False, stdio=None):
O
oceanbase-admin 已提交
892 893 894 895 896 897 898
        if plugin_type:
            self.PLUGIN_TYPE = plugin_type
        if not self.PLUGIN_TYPE:
            raise NotImplementedError
        self.plguin_cls = getattr(sys.modules[__name__], self.PLUGIN_TYPE.value, False)
        if not self.plguin_cls:
            raise ImportError(self.PLUGIN_TYPE.value)
R
Rongfeng Fu 已提交
899
        self.dev_mode = dev_mode
O
oceanbase-admin 已提交
900 901 902 903 904 905 906 907 908 909 910 911 912
        self.stdio = stdio
        self.path = home_path
        self.component_name = os.path.split(self.path)[1]
        self._plugins = {}

    def get_plugins(self):
        plugins = []
        for flag_path in glob('%s/*/%s' % (self.path, self.plguin_cls.FLAG_FILE)):
            if flag_path in self._plugins:
                plugins.append(self._plugins[flag_path])
            else:
                path, _ = os.path.split(flag_path)
                _, version = os.path.split(path)
R
Rongfeng Fu 已提交
913
                plugin = self.plguin_cls(self.component_name, path, version, self.dev_mode)
O
oceanbase-admin 已提交
914 915 916 917 918
                self._plugins[flag_path] = plugin
                plugins.append(plugin)
        return plugins

    def get_best_plugin(self, version):
R
Rongfeng Fu 已提交
919
        version = Version(version)
O
oceanbase-admin 已提交
920 921 922 923 924 925 926 927
        plugins = []
        for plugin in self.get_plugins():
            if plugin.version == version:
                return plugin
            if plugin.version < version:
                plugins.append(plugin)
        if plugins:
            plugin = max(plugins, key=lambda x: x.version)
R
Rongfeng Fu 已提交
928
            # self.stdio and getattr(self.stdio, 'warn', print)(
R
Rongfeng Fu 已提交
929
            #     '%s %s plugin version %s not found, use the best suitable version %s.\n Use `obd update` to update local plugin repository' %
R
Rongfeng Fu 已提交
930 931
            #     (self.component_name, self.PLUGIN_TYPE.name.lower(), version, plugin.version)
            #     )
O
oceanbase-admin 已提交
932 933 934 935 936 937 938 939 940 941 942 943 944 945
            return plugin
        return None


class PyScriptPluginLoader(ComponentPluginLoader):

    class PyScriptPluginType(object):

        def __init__(self, name, value):
            self.name = name
            self.value = value

    PLUGIN_TYPE = PluginType.PY_SCRIPT

R
Rongfeng Fu 已提交
946
    def __init__(self, home_path, script_name=None, dev_mode=False, stdio=None):
O
oceanbase-admin 已提交
947 948 949 950 951 952 953
        if not script_name:
            raise NotImplementedError
        type_name = 'PY_SCRIPT_%s' % script_name.upper()
        type_value = 'PyScript%sPlugin' % ''.join([word.capitalize() for word in script_name.split('_')])
        self.PLUGIN_TYPE = PyScriptPluginLoader.PyScriptPluginType(type_name, type_value)
        if not getattr(sys.modules[__name__], type_value, False):
            self._create_(script_name)
R
Rongfeng Fu 已提交
954
        super(PyScriptPluginLoader, self).__init__(home_path, dev_mode=dev_mode, stdio=stdio)
O
oceanbase-admin 已提交
955 956 957 958 959 960

    def _create_(self, script_name):
        exec('''
class %s(PyScriptPlugin):

    FLAG_FILE = '%s.py'
R
Rongfeng Fu 已提交
961
    PLUGIN_NAME = '%s'
O
oceanbase-admin 已提交
962

R
Rongfeng Fu 已提交
963 964
    def __init__(self, component_name, plugin_path, version, dev_mode):
        super(%s, self).__init__(component_name, plugin_path, version, dev_mode)
O
oceanbase-admin 已提交
965 966 967 968 969 970

    @staticmethod
    def set_plugin_type(plugin_type):
        %s.PLUGIN_TYPE = plugin_type

    @pyScriptPluginExec
R
Rongfeng Fu 已提交
971 972 973 974
    def %s(
        self, namespace, namespaces, deploy_name,
        repositories, components, clients, cluster_config, cmd,
        options, stdio, *arg, **kwargs):
O
oceanbase-admin 已提交
975 976 977 978 979 980 981 982 983 984 985 986 987
        pass
        ''' % (self.PLUGIN_TYPE.value, script_name, script_name, self.PLUGIN_TYPE.value, self.PLUGIN_TYPE.value, script_name))
        clz = locals()[self.PLUGIN_TYPE.value]
        setattr(sys.modules[__name__], self.PLUGIN_TYPE.value, clz)
        clz.set_plugin_type(self.PLUGIN_TYPE)
        return clz


class PluginManager(Manager):

    RELATIVE_PATH = 'plugins'
    # The directory structure for plugin is ./plugins/{component_name}/{version}

R
Rongfeng Fu 已提交
988
    def __init__(self, home_path, dev_mode=False, stdio=None):
O
oceanbase-admin 已提交
989
        super(PluginManager, self).__init__(home_path, stdio=stdio)
R
Rongfeng Fu 已提交
990
        self.dev_mode = dev_mode
O
oceanbase-admin 已提交
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
        self.component_plugin_loaders = {}
        self.py_script_plugin_loaders = {}
        for plugin_type in PluginType:
            self.component_plugin_loaders[plugin_type] = {}
        # PyScriptPluginLoader is a customized script loader. It needs special processing.
        # Log off the PyScriptPluginLoader in component_plugin_loaders
        del self.component_plugin_loaders[PluginType.PY_SCRIPT]

    def get_best_plugin(self, plugin_type, component_name, version):
        if plugin_type not in self.component_plugin_loaders:
            return None
        loaders = self.component_plugin_loaders[plugin_type]
        if component_name not in loaders:
R
Rongfeng Fu 已提交
1004
            loaders[component_name] = ComponentPluginLoader(os.path.join(self.path, component_name), plugin_type, self.dev_mode, self.stdio)
O
oceanbase-admin 已提交
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
        loader = loaders[component_name]
        return loader.get_best_plugin(version)

    # 主要用于获取自定义Python脚本插件
    # 相比于get_best_plugin,该方法可以获取到未在PluginType中注册的Python脚本插件
    # 这个功能可以快速实现自定义插件,只要在插件仓库创建对应的python文件,并暴露出同名方法即可
    # 使后续进一步实现全部流程可描述更容易实现
    def get_best_py_script_plugin(self, script_name, component_name, version):
        if script_name not in self.py_script_plugin_loaders:
            self.py_script_plugin_loaders[script_name] = {}
        loaders = self.py_script_plugin_loaders[script_name]
        if component_name not in loaders:
R
Rongfeng Fu 已提交
1017
            loaders[component_name] = PyScriptPluginLoader(os.path.join(self.path, component_name), script_name, self.dev_mode, self.stdio)
O
oceanbase-admin 已提交
1018 1019
        loader = loaders[component_name]
        return loader.get_best_plugin(version)