You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/08/13 14:27:32 UTC
[skywalking-python] branch master updated: [Plugin] check supported
version of packages when install plugins (#63)
This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-python.git
The following commit(s) were added to refs/heads/master by this push:
new 518002f [Plugin] check supported version of packages when install plugins (#63)
518002f is described below
commit 518002f0dea88f77657eb17a1cda4cb14a22c97a
Author: Humbertzhang <50...@qq.com>
AuthorDate: Thu Aug 13 22:27:23 2020 +0800
[Plugin] check supported version of packages when install plugins (#63)
---
docs/Developer.md | 15 ++++++
skywalking/plugins/__init__.py | 74 +++++++++++++++++++++++++++++
skywalking/plugins/sw_django.py | 5 ++
skywalking/plugins/sw_pymongo.py | 13 ++---
tests/test_version_check.py | 100 +++++++++++++++++++++++++++++++++++++++
5 files changed, 199 insertions(+), 8 deletions(-)
diff --git a/docs/Developer.md b/docs/Developer.md
index 0051e68..0cf7ba3 100644
--- a/docs/Developer.md
+++ b/docs/Developer.md
@@ -12,6 +12,21 @@
You can always take [the existing plugins](../skywalking/plugins) as examples, while there are some general ideas for all plugins.
1. A plugin is a module under directory [`skywalking/plugins/`](../skywalking/plugins) with an `install` method;
1. Inside the `install` method, you find out the relevant method(s) of the libraries that you plan to instrument, and create/close spans before/after those method(s).
+1. You should also provide version rules in the plugin module, which means the version of package your plugin support. You should init a dict with keys `name` and `rules`. the `name` is your plugin's corresponding package's name, the `rules` is the version rules this package should follow.
+
+ You can use >, >=, ==, <=, <, and != operators in rules.
+
+ The relation between rules element in the rules array is **OR**, which means the version of the package should follow at least one rule in rules array.
+
+ You can set many version rules in one element of rules array, separate each other with a space character, the relation of rules in one rule element is **AND**, which means the version of package should follow all rules in this rule element.
+
+ For example, below `version_rule` indicates that the package version of `django` should `>=2.0 AND <=2.3 AND !=2.2.1` OR `>3.0`.
+ ```python
+ version_rule = {
+ "name": "django",
+ "rules": [">=2.0 <=2.3 !=2.2.1", ">3.0"]
+ }
+ ```
1. Every plugin requires a corresponding test under [`tests/plugin`](../tests/plugin) before it can be merged, refer to [the plugin test guide](PluginTest.md) when writing a plugin test.
1. Update [the supported list](Plugins.md).
1. Add the environment variables to [the list](EnvVars.md) if any.
diff --git a/skywalking/plugins/__init__.py b/skywalking/plugins/__init__.py
index 3995f1d..591cb14 100644
--- a/skywalking/plugins/__init__.py
+++ b/skywalking/plugins/__init__.py
@@ -18,6 +18,9 @@ import inspect
import logging
import pkgutil
import re
+import pkg_resources
+
+from packaging import version
from skywalking import config
@@ -38,7 +41,78 @@ def install():
continue
logger.debug('installing plugin %s', modname)
plugin = importer.find_module(modname).load_module(modname)
+
+ supported = pkg_version_check(plugin)
+ if not supported:
+ logger.debug('check version for plugin %s\'s corresponding package failed, thus '
+ 'won\'t be installed', modname)
+ continue
+
if not hasattr(plugin, 'install') or inspect.ismethod(getattr(plugin, 'install')):
logger.warning('no `install` method in plugin %s, thus the plugin won\'t be installed', modname)
continue
plugin.install()
+
+
+_operators = {
+ '<': lambda cv, ev: cv < ev,
+ '<=': lambda cv, ev: cv < ev or cv == ev,
+ '==': lambda cv, ev: cv == ev,
+ '>=': lambda cv, ev: cv > ev or cv == ev,
+ '>': lambda cv, ev: cv > ev,
+ '!=': lambda cv, ev: cv != ev
+}
+
+
+class VersionRuleException(Exception):
+ def __init__(self, message):
+ self.message = message
+
+
+def pkg_version_check(plugin):
+ supported = True
+
+ # no version rules was set, no checks
+ if not hasattr(plugin, "version_rule"):
+ return supported
+
+ pkg_name = plugin.version_rule.get("name")
+ rules = plugin.version_rule.get("rules")
+
+ try:
+ current_pkg_version = pkg_resources.get_distribution(pkg_name).version
+ except pkg_resources.DistributionNotFound:
+ # when failed to get the version, we consider it as supported.
+ return supported
+
+ current_version = version.parse(current_pkg_version)
+ # pass one rule in rules (OR)
+ for rule in rules:
+ if rule.find(" ") == -1:
+ if check(rule, current_version):
+ return supported
+ else:
+ # have to pass all rule_uint in this rule (AND)
+ rule_units = rule.split(" ")
+ results = [check(unit, current_version) for unit in rule_units]
+ if False in results:
+ # check failed, try to check next rule
+ continue
+ else:
+ return supported
+
+ supported = False
+ return supported
+
+
+def check(rule_unit, current_version):
+ idx = 2 if rule_unit[1] == '=' else 1
+ symbol = rule_unit[0:idx]
+ expect_pkg_version = rule_unit[idx:]
+
+ expect_version = version.parse(expect_pkg_version)
+ f = _operators.get(symbol) or None
+ if not f:
+ raise VersionRuleException("version rule {} error. only allow >,>=,==,<=,<,!= symbols".format(rule_unit))
+
+ return f(current_version, expect_version)
diff --git a/skywalking/plugins/sw_django.py b/skywalking/plugins/sw_django.py
index d920d2f..08681c4 100644
--- a/skywalking/plugins/sw_django.py
+++ b/skywalking/plugins/sw_django.py
@@ -24,6 +24,11 @@ from skywalking.trace.tags import Tag
logger = logging.getLogger(__name__)
+version_rule = {
+ "name": "django",
+ "rules": [">=2.0"]
+}
+
def install():
try:
diff --git a/skywalking/plugins/sw_pymongo.py b/skywalking/plugins/sw_pymongo.py
index 06a8dcb..e371f8e 100644
--- a/skywalking/plugins/sw_pymongo.py
+++ b/skywalking/plugins/sw_pymongo.py
@@ -16,8 +16,6 @@
#
import logging
-import pkg_resources
-from packaging import version
from skywalking import Layer, Component, config
from skywalking.trace import tags
@@ -27,6 +25,11 @@ from skywalking.trace.tags import Tag
logger = logging.getLogger(__name__)
+version_rule = {
+ "name": "pymongo",
+ "rules": [">=3.7.0"]
+}
+
def install():
try:
@@ -34,12 +37,6 @@ def install():
from pymongo.cursor import Cursor
from pymongo.pool import SocketInfo
- # check pymongo version
- pymongo_version = pkg_resources.get_distribution("pymongo").version
- if version.parse(pymongo_version) < version.parse("3.7.0"):
- logger.warning("support pymongo version 3.7.0 or above, current version:" + pymongo_version)
- raise Exception
-
bulk_op_map = {
0: "insert",
1: "update",
diff --git a/tests/test_version_check.py b/tests/test_version_check.py
new file mode 100644
index 0000000..371b14d
--- /dev/null
+++ b/tests/test_version_check.py
@@ -0,0 +1,100 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import unittest
+
+from packaging import version
+
+from skywalking.plugins import _operators, check
+
+
+class TestVersionCheck(unittest.TestCase):
+ def test_operators(self):
+ # <
+ f = _operators.get("<")
+ v1 = version.parse("1.0.0")
+ v2 = version.parse("1.0.1")
+ self.assertTrue(f(v1, v2))
+ self.assertFalse(f(v2, v1))
+
+ v2 = version.parse("1.0.0")
+ self.assertFalse(f(v1, v2))
+
+ # <=
+ f = _operators.get("<=")
+ v1 = version.parse("1.0")
+ v2 = version.parse("1.0")
+ self.assertTrue(v1, v2)
+
+ v2 = version.parse("1.1.0")
+ self.assertTrue(f(v1, v2))
+ self.assertFalse(f(v2, v1))
+
+ # =
+ f = _operators.get("==")
+ v1 = version.parse("1.0.0")
+ v2 = version.parse("1.0.0")
+ self.assertTrue(f(v1, v2))
+
+ v2 = version.parse("1.0.1")
+ self.assertFalse(f(v1, v2))
+
+ # >=
+ f = _operators.get(">=")
+ v1 = version.parse("1.0.0")
+ v2 = version.parse("1.0.0")
+ self.assertTrue(f(v1, v2))
+
+ v2 = version.parse("1.0.1")
+ self.assertFalse(f(v1, v2))
+ self.assertTrue(f(v2, v1))
+
+ # >
+ f = _operators.get(">")
+ v1 = version.parse("1.0.0")
+ v2 = version.parse("1.0.1")
+ self.assertFalse(f(v1, v2))
+ self.assertTrue(f(v2, v1))
+
+ v2 = version.parse("1.0.0")
+ self.assertFalse(f(v1, v2))
+
+ # !=
+ f = _operators.get("!=")
+ v1 = version.parse("1.0.0")
+ v2 = version.parse("1.0.1")
+ self.assertTrue(f(v1, v2))
+
+ v2 = version.parse("1.0.0")
+ self.assertFalse(f(v1, v2))
+
+ def test_version_check(self):
+ current_version = version.parse("1.8.0")
+
+ self.assertTrue(check(">1.1.0", current_version))
+ self.assertTrue(check(">=1.0.0", current_version))
+ self.assertTrue(check("<2.0.0", current_version))
+ self.assertTrue(check("<=1.8.0", current_version))
+ self.assertTrue(check("==1.8.0", current_version))
+ self.assertTrue(check("!=1.6.0", current_version))
+
+ self.assertFalse(check(">1.9.0", current_version))
+ self.assertFalse(check(">=1.8.1", current_version))
+ self.assertFalse(check("<1.8.0", current_version))
+ self.assertFalse(check("<=1.7.0", current_version))
+ self.assertFalse(check("==1.0.0", current_version))
+ self.assertFalse(check("!=1.8.0", current_version))