From 3bc9ebbb406620028df534f8527b58c6097d8213 Mon Sep 17 00:00:00 2001 From: zengchen Date: Fri, 23 Jun 2017 14:25:29 +0800 Subject: [PATCH] Implement client and server framework of FlexVolume driver Take the cinder driver for example which the client and server framework to implement each interfaces, such as 'is_attached' Change-Id: Ie4e7de13d0ca5f145161a5ebeb64bcead12108dc reference: https://review.openstack.org/#/c/474038/ --- fuxi_kubernetes/cmd/driver_server.py | 16 ++++- fuxi_kubernetes/common/config.py | 13 ++++ fuxi_kubernetes/common/constants.py | 40 ++++++++++++ fuxi_kubernetes/exceptions.py | 6 ++ .../flex_volume_drivers/drivers/base.py | 37 ++++++++++- .../drivers/cinder/cinder.py | 11 +++- .../drivers/cinder/cinder.sh | 4 +- .../server/cinder/__init__.py | 0 .../server/cinder/cinder.py | 27 ++++++++ .../flex_volume_drivers/server/controller.py | 64 ++++++++++++++++++- fuxi_kubernetes/opts.py | 4 ++ fuxi_kubernetes/tests/unit/base.py | 3 - .../unit/drivers/test_base_volume_driver.py | 2 +- fuxi_kubernetes/version.py | 15 +++++ requirements.txt | 2 + setup.cfg | 3 + setup.py | 2 - 17 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 fuxi_kubernetes/flex_volume_drivers/server/cinder/__init__.py create mode 100644 fuxi_kubernetes/flex_volume_drivers/server/cinder/cinder.py create mode 100644 fuxi_kubernetes/version.py diff --git a/fuxi_kubernetes/cmd/driver_server.py b/fuxi_kubernetes/cmd/driver_server.py index 369390e..b785c4e 100644 --- a/fuxi_kubernetes/cmd/driver_server.py +++ b/fuxi_kubernetes/cmd/driver_server.py @@ -12,6 +12,20 @@ """Start server of FlexVolume driver""" +from oslo_log import log as logging +import sys + +from fuxi_kubernetes.common import config +from fuxi_kubernetes.common import constants +from fuxi_kubernetes.flex_volume_drivers.server import controller + def main(): - raise NotImplemented() + config.init(sys.argv[1:]) + logging.setup(config.CONF, 'fuxi-kubernetes') + + controller.init_volume_drivers() + controller.start( + constants.LOCAL_HOST, + config.CONF[config.flexvolume_driver_group.name].driver_server_port, + debug=config.CONF.debug, threaded=True) diff --git a/fuxi_kubernetes/common/config.py b/fuxi_kubernetes/common/config.py index 8c8d2a6..7eef810 100644 --- a/fuxi_kubernetes/common/config.py +++ b/fuxi_kubernetes/common/config.py @@ -11,8 +11,11 @@ # under the License. from oslo_config import cfg +from oslo_log import log as logging +from fuxi_kubernetes.common import constants from fuxi_kubernetes.i18n import _ +from fuxi_kubernetes.version import version_info flexvolume_driver_group = cfg.OptGroup( @@ -27,8 +30,18 @@ flexvolume_driver_opts = [ cfg.IntOpt('driver_server_port', default=7878, help=_('Port for the server of FlexVolume driver.')), + cfg.StrOpt('host_platform', + default='baremetal', + help=_('The platform on which FlexVolume driver runs. ' + 'Optional values are: baremetal')), ] CONF = cfg.CONF +logging.register_options(CONF) CONF.register_opts(flexvolume_driver_opts, flexvolume_driver_group.name) + + +def init(args, **kwargs): + cfg.CONF(args=args, project=constants.PROJECT_NAME, + version=version_info.release_string(), **kwargs) diff --git a/fuxi_kubernetes/common/constants.py b/fuxi_kubernetes/common/constants.py index 9c202b6..3551433 100644 --- a/fuxi_kubernetes/common/constants.py +++ b/fuxi_kubernetes/common/constants.py @@ -13,6 +13,7 @@ PROJECT_NAME = 'fuxi-kubernetes' +LOCAL_HOST = '0.0.0.0' VOLUME_DRIVER_CMD = ( CMD_INIT, @@ -66,3 +67,42 @@ VOLUME_DRIVER_CMD_RESULT_STATUS = ( 'Failed', 'Not supported', ) + + +VOLUME_DRIVER_TYPE = ( + VOLUME_DRIVER_CINDER, + VOLUME_DRIVER_MANICLA, +) = ( + 'Cinder', + 'Manila' +) + + +VOLUME_DRIVER_SERVER_API = ( + SERVER_API_IS_ATTACHED, + SERVER_API_ATTACH, + SERVER_API_WAIT_FOR_ATTACH, + SERVER_API_MOUNT_DEVICE, + SERVER_API_DETACH, + SERVER_API_WAIT_FOR_DETACH, + SERVER_API_UNMOUNT_DEVICE, + SERVER_API_MOUNT, + SERVER_API_UNMOUNT +) = ( + '/VolumeDriver.is_attached', + '/VolumeDriver.attach', + '/VolumeDriver.wait_for_attach', + '/VolumeDriver.mount_device', + '/VolumeDriver.detach', + '/VolumeDriver.wait_for_detach', + '/VolumeDriver.unmount_device', + '/VolumeDriver.mount', + '/VolumeDriver.unmount', +) + + +CINDER_VOLUME_ATTR_KEY = ( + CINDER_VOLUME_ATTR_VOLUME_ID, +) = ( + 'VolumeID', +) diff --git a/fuxi_kubernetes/exceptions.py b/fuxi_kubernetes/exceptions.py index 78b8cfa..1676d91 100644 --- a/fuxi_kubernetes/exceptions.py +++ b/fuxi_kubernetes/exceptions.py @@ -19,3 +19,9 @@ class InvalidVolumeDriverCmdParameter(FuxiKubernetesException): def __init__(self, reason): super(InvalidVolumeDriverCmdParameter, self).__init__( "Invalid FlexVolume driver cmd parameter, reason:%s" % reason) + + +class LoadVolumeDriverException(FuxiKubernetesException): + def __init__(self, reason): + super(LoadVolumeDriverException, self).__init__( + "Load volume driver failed, reason: %s" % reason) diff --git a/fuxi_kubernetes/flex_volume_drivers/drivers/base.py b/fuxi_kubernetes/flex_volume_drivers/drivers/base.py index b80b954..2985c9c 100644 --- a/fuxi_kubernetes/flex_volume_drivers/drivers/base.py +++ b/fuxi_kubernetes/flex_volume_drivers/drivers/base.py @@ -11,6 +11,7 @@ # under the License. from oslo_serialization import jsonutils +import requests from fuxi_kubernetes.common import constants from fuxi_kubernetes import exceptions @@ -35,6 +36,10 @@ class BaseVolumeDriver(object): default_result = Result(status=constants.STATUS_NOT_SUPPORT) + def __init__(self): + self._driver_server_port = '' + self._driver_name = '' + def init(self): return Result(status=constants.STATUS_SUCCESS) @@ -68,14 +73,40 @@ class BaseVolumeDriver(object): def unmount(self, mount_dir): return self.default_result + def _request_server(self, api, data): + def _send_and_receive(): + try: + url = 'http://%(ip)s:%(port)d%(api)s' % { + 'ip': constants.LOCAL_HOST, + 'port': self._driver_server_port, + 'api': api} + data['driver'] = self._driver_name + response = requests.post(url, json=data) + if not response.ok: + return False, response.text + + return True, response.json() + + except Exception as ex: + return (False, + 'During request to server, ' + 'threw exception:(%s)' % str(ex)) + + ret, info = _send_and_receive() + if ret: + return Result(**info) + return Result(status=constants.STATUS_FAILURE, message=info) + def __call__(self, argv): - if not argv: + if not argv or len(argv) < 2: return self.default_result - cmd = argv[0] + cmd = argv[1] if cmd not in constants.VOLUME_DRIVER_CMD: return self.default_result - argv = argv[1:] + + self._driver_server_port = int(argv[0]) + argv = argv[2:] cmd_info = self._get_cmd_info(cmd) if len(argv) != cmd_info['required_params_num']: diff --git a/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.py b/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.py index dbc19ab..bc84e7d 100644 --- a/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.py +++ b/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from fuxi_kubernetes.common import constants from fuxi_kubernetes.flex_volume_drivers.drivers import base @@ -17,4 +18,12 @@ class DriverCinder(base.BaseVolumeDriver): # TODO(zengchen): implement it. def __init__(self): - raise NotImplemented() + super(DriverCinder, self).__init__() + self._driver_name = constants.VOLUME_DRIVER_CINDER + + def is_attached(self, host_name, **kwargs): + return self._request_server( + constants.SERVER_API_IS_ATTACHED, + {'host_name': host_name, + 'volume_id': kwargs.get(constants.CINDER_VOLUME_ATTR_VOLUME_ID)} + ) diff --git a/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.sh b/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.sh index 96b6ab7..350a2e8 100755 --- a/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.sh +++ b/fuxi_kubernetes/flex_volume_drivers/drivers/cinder/cinder.sh @@ -43,7 +43,9 @@ if [ $# -lt 1 ]; then usage fi -out=$(fuxi-k8s-volume-driver-cinder "$@") +config_file=/etc/fuxi-kubernetes/fuxi_kubernetes.conf +port=$(grep driver_server_port $config_file | awk '{print $NF}') +out=$(fuxi-k8s-volume-driver-cinder $port "$@") code=$? if [ $code -eq 0 ]; then log "$out" diff --git a/fuxi_kubernetes/flex_volume_drivers/server/cinder/__init__.py b/fuxi_kubernetes/flex_volume_drivers/server/cinder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fuxi_kubernetes/flex_volume_drivers/server/cinder/cinder.py b/fuxi_kubernetes/flex_volume_drivers/server/cinder/cinder.py new file mode 100644 index 0000000..a58a66c --- /dev/null +++ b/fuxi_kubernetes/flex_volume_drivers/server/cinder/cinder.py @@ -0,0 +1,27 @@ +# Licensed 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. + + +class ServerCinder(object): + # TODO(zengchen): implement all the interface of driver, such as + # is_attached, attach, detach, mount, unmount etc. + + def __init__(self, host_platform): + self._cinder_client = None + self._host = None + + def is_attached(self, volume_id, host_name, **kwargs): + return False + + @classmethod + def is_support_host_platform(cls, host_platform): + return True diff --git a/fuxi_kubernetes/flex_volume_drivers/server/controller.py b/fuxi_kubernetes/flex_volume_drivers/server/controller.py index ec42b5f..1af779c 100644 --- a/fuxi_kubernetes/flex_volume_drivers/server/controller.py +++ b/fuxi_kubernetes/flex_volume_drivers/server/controller.py @@ -12,4 +12,66 @@ """Server of FlexVolume driver""" -# TODO(zengchen): implement it. +import flask +import functools +from oslo_log import log as logging +from stevedore import extension + +from fuxi_kubernetes.common import config +from fuxi_kubernetes.common import constants +from fuxi_kubernetes import exceptions +from fuxi_kubernetes.flex_volume_drivers.drivers import base + +LOG = logging.getLogger(__name__) + +APP = flask.Flask(__name__) + + +def start(host=None, port=None, debug=None, **options): + APP.run(host, port, debug, **options) + + +def init_volume_drivers(): + mgr = extension.ExtensionManager( + namespace='flex_volume_drivers.server', + ) + host_platform = config.CONF[ + config.flexvolume_driver_group.name].host_platform + APP.volume_drivers = { + e.name: e.plugin(host_platform) + for e in mgr + if e.plugin.is_support_host_platform(host_platform) + } + + if not APP.volume_drivers: + raise exceptions.LoadVolumeDriverException('No driver is loaded') + + +def api_wrapper(f): + def _response(ret, info): + if ret: + info['status'] = constants.STATUS_SUCCESS + else: + info = {'status': constants.STATUS_FAILURE, 'message': info} + return flask.jsonify(base.Result(**info)()) + + @functools.wraps(f) + def wrapper(*args, **kwargs): + data = flask.request.get_json(force=True) + driver = APP.volume_drivers.get(data.get('driver')) + if not driver: + return _response( + False, 'Unknow FlexVolume driver:%s' % data.get('driver')) + + try: + return _response(True, f(driver, data)) + except Exception as ex: + return _response(False, str(ex)) + + return wrapper + + +@APP.route(constants.SERVER_API_IS_ATTACHED, methods=['POST']) +@api_wrapper +def is_attached(driver=None, param=None): + return {'attached': driver.is_attached(**param)} diff --git a/fuxi_kubernetes/opts.py b/fuxi_kubernetes/opts.py index 0100362..ee2a00a 100644 --- a/fuxi_kubernetes/opts.py +++ b/fuxi_kubernetes/opts.py @@ -15,6 +15,7 @@ __all__ = [ ] import itertools +from oslo_log import _options from fuxi_kubernetes.common import config @@ -22,6 +23,9 @@ from fuxi_kubernetes.common import config def list_fuxi_k8s_opts(): return [ + ('DEFAULT', + itertools.chain(_options.list_opts()[0][1],)), + (config.flexvolume_driver_group.name, itertools.chain(config.flexvolume_driver_opts,)), ] diff --git a/fuxi_kubernetes/tests/unit/base.py b/fuxi_kubernetes/tests/unit/base.py index 2159808..bc2d9c8 100644 --- a/fuxi_kubernetes/tests/unit/base.py +++ b/fuxi_kubernetes/tests/unit/base.py @@ -1,6 +1,3 @@ -# Copyright 2010-2011 OpenStack Foundation -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# # Licensed 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 diff --git a/fuxi_kubernetes/tests/unit/drivers/test_base_volume_driver.py b/fuxi_kubernetes/tests/unit/drivers/test_base_volume_driver.py index 721d001..2a14dd6 100644 --- a/fuxi_kubernetes/tests/unit/drivers/test_base_volume_driver.py +++ b/fuxi_kubernetes/tests/unit/drivers/test_base_volume_driver.py @@ -30,6 +30,6 @@ class TestBaseVolumeDriver(base.TestCase): self._driver(['abc']).status) def test_load_json_fail(self): - r = self._driver(['attach', 'abc', 'abc']) + r = self._driver(['123', 'attach', 'abc', 'abc']) self.assertEqual(constants.STATUS_FAILURE, r.status) self.assertIn('can not load json parameter', r.message) diff --git a/fuxi_kubernetes/version.py b/fuxi_kubernetes/version.py new file mode 100644 index 0000000..9a6e179 --- /dev/null +++ b/fuxi_kubernetes/version.py @@ -0,0 +1,15 @@ +# Licensed 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 pbr.version + +version_info = pbr.version.VersionInfo('fuxi-kubernetes') diff --git a/requirements.txt b/requirements.txt index 7f4089a..e67db53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 +Flask!=0.11,<1.0,>=0.10 # BSD diff --git a/setup.cfg b/setup.cfg index d389294..ad85cd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,9 @@ console_scripts = fuxi-k8s-volume-driver-cinder = fuxi_kubernetes.cmd.cinder:main fuxi-k8s-volume-driver-server = fuxi_kubernetes.cmd.driver_server:main +flex_volume_drivers.server = + Cinder = fuxi_kubernetes.flex_volume_drivers.server.cinder.cinder:ServerCinder + [build_sphinx] source-dir = doc/source build-dir = doc/build diff --git a/setup.py b/setup.py index 566d844..d74ff58 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# # Licensed 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