From 09bf790158032f93a6bddb35fa39cbaa29de2be5 Mon Sep 17 00:00:00 2001 From: DongZhi <517341003@qq.com> Date: Tue, 8 Nov 2016 20:34:18 +0800 Subject: [PATCH] Initial Load of Cinder FusionCompute Project Change-Id: I4c54edd5560060fcfda445895921e97764c92224 --- .coveragerc | 6 + .gitignore | 58 + .idea/cinder-fusioncompute.iml | 15 + .idea/misc.xml | 14 + .idea/modules.xml | 8 + .idea/workspace.xml | 42 + .mailmap | 3 + .testr.conf | 7 + CONTRIBUTING.rst | 17 + HACKING.rst | 4 + LICENSE | 176 ++ MANIFEST.in | 6 + README.rst | 19 + babel.cfg | 2 + cinder/__init__.py | 20 + cinder/tests/__init__.py | 0 cinder/tests/units/__init__.py | 0 cinder/tests/units/volume/__init__.py | 0 cinder/tests/units/volume/drivers/__init__.py | 0 .../units/volume/drivers/huawei/__init__.py | 0 .../drivers/huawei/fusioncompute/__init__.py | 0 .../drivers/huawei/fusioncompute/base.py | 23 + .../huawei/fusioncompute/test_driver.py | 28 + cinder/volume/drivers/huawei/__init__.py | 0 .../drivers/huawei/fusioncompute/__init__.py | 18 + .../huawei/fusioncompute/base_proxy.py | 92 + .../huawei/fusioncompute/cluster_proxy.py | 88 + .../drivers/huawei/fusioncompute/conf.py | 110 + .../huawei/fusioncompute/datastore_proxy.py | 114 + .../drivers/huawei/fusioncompute/exception.py | 184 ++ .../huawei/fusioncompute/host_proxy.py | 77 + .../huawei/fusioncompute/http_client.py | 399 +++ .../huawei/fusioncompute/task_proxy.py | 130 + .../drivers/huawei/fusioncompute/utils.py | 92 + .../drivers/huawei/fusioncompute/vm_proxy.py | 1168 ++++++++ .../huawei/fusioncompute/volume_proxy.py | 601 +++++ .../fusioncompute/volume_snapshot_proxy.py | 308 +++ .../huawei/fusioncompute/vrm_commands.py | 441 +++ .../huawei/fusioncompute/vrm_driver.py | 2355 +++++++++++++++++ .../huawei/fusioncompute/vrm_driver_huawei.py | 200 ++ doc/source/conf.py | 75 + doc/source/contributing.rst | 4 + doc/source/index.rst | 25 + doc/source/installation.rst | 12 + doc/source/readme.rst | 1 + doc/source/usage.rst | 7 + releasenotes/notes/.placeholder | 0 releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 275 ++ releasenotes/source/index.rst | 8 + releasenotes/source/unreleased.rst | 5 + requirements.txt | 18 + setup.cfg | 51 + setup.py | 29 + test-requirements.txt | 17 + tox.ini | 40 + 57 files changed, 7392 insertions(+) create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 .idea/cinder-fusioncompute.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/workspace.xml create mode 100644 .mailmap create mode 100644 .testr.conf create mode 100644 CONTRIBUTING.rst create mode 100644 HACKING.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 babel.cfg create mode 100644 cinder/__init__.py create mode 100644 cinder/tests/__init__.py create mode 100644 cinder/tests/units/__init__.py create mode 100644 cinder/tests/units/volume/__init__.py create mode 100644 cinder/tests/units/volume/drivers/__init__.py create mode 100644 cinder/tests/units/volume/drivers/huawei/__init__.py create mode 100644 cinder/tests/units/volume/drivers/huawei/fusioncompute/__init__.py create mode 100644 cinder/tests/units/volume/drivers/huawei/fusioncompute/base.py create mode 100644 cinder/tests/units/volume/drivers/huawei/fusioncompute/test_driver.py create mode 100644 cinder/volume/drivers/huawei/__init__.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/__init__.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/base_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/cluster_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/conf.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/datastore_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/exception.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/host_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/http_client.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/task_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/utils.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/vm_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/volume_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/volume_snapshot_proxy.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/vrm_commands.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/vrm_driver.py create mode 100644 cinder/volume/drivers/huawei/fusioncompute/vrm_driver_huawei.py create mode 100644 doc/source/conf.py create mode 100644 doc/source/contributing.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/installation.rst create mode 100644 doc/source/readme.rst create mode 100644 doc/source/usage.rst create mode 100644 releasenotes/notes/.placeholder create mode 100644 releasenotes/source/_static/.placeholder create mode 100644 releasenotes/source/_templates/.placeholder create mode 100644 releasenotes/source/conf.py create mode 100644 releasenotes/source/index.rst create mode 100644 releasenotes/source/unreleased.rst create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test-requirements.txt create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..eedfbd2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +branch = True +source = cinder-fusioncompute + +[report] +ignore_errors = True diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..963e589 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg* +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +cover/ +.coverage* +!.coveragerc +.tox +nosetests.xml +.testrepository +.venv + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +doc/build + +# pbr generates these +AUTHORS +ChangeLog + +# Editors +*~ +.*.swp +.*sw? + +# Files created by releasenotes build +releasenotes/build \ No newline at end of file diff --git a/.idea/cinder-fusioncompute.iml b/.idea/cinder-fusioncompute.iml new file mode 100644 index 0000000..3c9964a --- /dev/null +++ b/.idea/cinder-fusioncompute.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9eabf49 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d70bc09 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..32d8044 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + 1478503875341 + + + + + + + + + + \ No newline at end of file diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..516ae6f --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +# Format is: +# +# diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..6d83b3c --- /dev/null +++ b/.testr.conf @@ -0,0 +1,7 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..ba5329d --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If you would like to contribute to the development of OpenStack, you must +follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +If you already have a good understanding of how the system works and your +OpenStack accounts are set up, you can skip to the development workflow +section of this documentation to learn how changes to OpenStack should be +submitted for review via the Gerrit tool: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/cinder-fusioncompute diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 0000000..7d5c531 --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,4 @@ +cinder-fusioncompute Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c978a52 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..416d6f2 --- /dev/null +++ b/README.rst @@ -0,0 +1,19 @@ +=============================== +cinder-fusioncompute +=============================== + +Implementation of Cinder driver for Huawei Fusioncompute. + +Please fill here a long description which must be at least 3 lines wrapped on +80 cols, so that distribution package maintainers can use it in their packages. +Note that this is a hard requirement. + +* Free software: Apache license +* Documentation: http://docs.openstack.org/developer/cinder-fusioncompute +* Source: http://git.openstack.org/cgit/openstack/cinder-fusioncompute +* Bugs: http://bugs.launchpad.net/cinder-fusioncompute + +Features +-------- + +* TODO diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..15cd6cb --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: **.py] + diff --git a/cinder/__init__.py b/cinder/__init__.py new file mode 100644 index 0000000..c69534f --- /dev/null +++ b/cinder/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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__ = pbr.version.VersionInfo( + 'cinder-fusioncompute').version_string() diff --git a/cinder/tests/__init__.py b/cinder/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/__init__.py b/cinder/tests/units/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/volume/__init__.py b/cinder/tests/units/volume/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/volume/drivers/__init__.py b/cinder/tests/units/volume/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/volume/drivers/huawei/__init__.py b/cinder/tests/units/volume/drivers/huawei/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/volume/drivers/huawei/fusioncompute/__init__.py b/cinder/tests/units/volume/drivers/huawei/fusioncompute/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/tests/units/volume/drivers/huawei/fusioncompute/base.py b/cinder/tests/units/volume/drivers/huawei/fusioncompute/base.py new file mode 100644 index 0000000..1c30cdb --- /dev/null +++ b/cinder/tests/units/volume/drivers/huawei/fusioncompute/base.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# 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 +# +# 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. + +from oslotest import base + + +class TestCase(base.BaseTestCase): + + """Test case base class for all unit tests.""" diff --git a/cinder/tests/units/volume/drivers/huawei/fusioncompute/test_driver.py b/cinder/tests/units/volume/drivers/huawei/fusioncompute/test_driver.py new file mode 100644 index 0000000..9021825 --- /dev/null +++ b/cinder/tests/units/volume/drivers/huawei/fusioncompute/test_driver.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# 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. + +""" +test_cinder-fusioncompute +---------------------------------- + +Tests for `cinder-fusioncompute` module. +""" + +from cinder.tests.units.volume.drivers.huawei.fusioncompute import base + + +class TestCinder(base.TestCase): + + def test_something(self): + pass diff --git a/cinder/volume/drivers/huawei/__init__.py b/cinder/volume/drivers/huawei/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cinder/volume/drivers/huawei/fusioncompute/__init__.py b/cinder/volume/drivers/huawei/fusioncompute/__init__.py new file mode 100644 index 0000000..ba86124 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +''' + init +''' diff --git a/cinder/volume/drivers/huawei/fusioncompute/base_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/base_proxy.py new file mode 100644 index 0000000..c887bf5 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/base_proxy.py @@ -0,0 +1,92 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import urlparse + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder.volume.drivers.huawei.vrm.conf import FC_DRIVER_CONF +from cinder.volume.drivers.huawei.vrm.http_client import VRMHTTPClient + + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +CONF = cfg.CONF + +LOG = logging.getLogger(__name__) + + +class BaseProxy(object): + '''BaseProxy + + BaseProxy + ''' + def __init__(self): + + self.vrmhttpclient = VRMHTTPClient() + self.site_uri = self.vrmhttpclient.get_siteuri() + self.site_urn = self.vrmhttpclient.get_siteurn() + self.limit = 100 + self.BASIC_URI = '/service' + + def _joined_params(self, params): + '''_joined_params + + :param params: + :return: + ''' + param_str = [] + for k, v in params.items(): + if (k is None) or (v is None) or len(k) == 0: + continue + if k == 'scope' and v == self.site_urn: + continue + param_str.append("%s=%s" % (k, str(v))) + return '&'.join(param_str) + + def _generate_url(self, path, query=None, frag=None): + '''_generate_url + + :param path: + :param query: + :param frag: + :return:url + ''' + if CONF.vrm_ssl: + scheme = 'https' + else: + scheme = 'http' + fc_ip = FC_DRIVER_CONF.fc_ip + + netloc = str(fc_ip) + ':' + str(CONF.vrm_port) + if path.startswith(self.BASIC_URI): + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + else: + url = urlparse.urlunsplit( + (scheme, netloc, self.BASIC_URI + str(path), query, frag)) + return url + + + diff --git a/cinder/volume/drivers/huawei/fusioncompute/cluster_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/cluster_proxy.py new file mode 100644 index 0000000..987248a --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/cluster_proxy.py @@ -0,0 +1,88 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" + +from oslo_log import log as logging + +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy + + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) + + +class ClusterProxy(BaseProxy): + '''ClusterProxy + + ''' + def __init__(self): + super(ClusterProxy, self).__init__() + + def list_cluster(self): + '''list_cluster + + Get ?tag=xxx&clusterUrns=urn1&clusterUrns=urn2 HTTP/1.1 + Host: https://: + Accept: application/json;version=; charset=UTF-8 + X-Auth-Token: + + :param: + :return: + ''' + LOG.info(_("[VRM-CINDER] start list_cluster()")) + uri = '/clusters' + method = 'GET' + path = self.site_uri + uri + + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + clusters = body.get('clusters') + + return clusters + + def list_hosts(self, **kwargs): + '''list_hosts + + Get ?limit=20&offset=0&scope=xxx HTTP/1.1 + Host: https://: + Accept: application/json;version=; charset=UTF-8 + X-Auth-Token: + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start list_host()")) + uri = '/hosts' + method = 'GET' + path = self.site_uri + uri + params = { + 'scope': kwargs.get('clusterUrn'), + } + appendix = self._joined_params(params) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + hosts = body.get('hosts') + return hosts diff --git a/cinder/volume/drivers/huawei/fusioncompute/conf.py b/cinder/volume/drivers/huawei/fusioncompute/conf.py new file mode 100644 index 0000000..b37a47a --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/conf.py @@ -0,0 +1,110 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] CONFIG +""" + +from oslo_config import cfg + + +vrm_http_opts = [ + cfg.StrOpt('vrm_version', + default='v6.0', + help='Management version of VRM'), + cfg.BoolOpt('vrm_ssl', + default=True, + help='Use SSL connection'), + cfg.StrOpt('vrm_port', + default=7443, + help='Management port of VRM'), + cfg.StrOpt('vrm_user', + default='gesysman', + help='User name for the VRM'), + cfg.StrOpt('vrm_password', + default='', + help='Password for the VRM'), + cfg.StrOpt('vrm_authtype', + default='0', + help='User name for the VRM'), + cfg.StrOpt('vrm_usertype', + default='2', + help='User type for the VRM'), + cfg.IntOpt('vrm_retries', + default=3, + help='retry times for http request'), + cfg.IntOpt('vrm_snapshot_sleeptime', + default=300, + help='sleep times for retry request'), + cfg.IntOpt('vrm_vol_snapshot_retries', + default=3, + help='retry times for http request'), + cfg.IntOpt('vrm_timeout', + default=30000, + help='timeout(s) for task result'), + cfg.IntOpt('vrm_limit', + default=100, + help='limit per step for retrive mass reources result set'), + cfg.IntOpt('vrm_sm_periodrate', + default=1, + help='timeout(s) for task result'), +] +VRM_group = cfg.OptGroup(name='VRM', title='VRM config') +VRM_opts = [ + cfg.StrOpt('fc_user', + help='FusionCompute user name'), + cfg.StrOpt('fc_pwd_for_cinder', secret=True, + help='FusionCompute user password'), + cfg.StrOpt('fc_ip', + help='Management IP of FusionCompute'), + cfg.StrOpt('fc_image_path', + help='NFS Image server path'), + cfg.StrOpt('glance_server_ip', + help='FusionSphere glance server ip'), + cfg.StrOpt('s3_store_access_key_for_cinder', secret=True, + help='FusionCompute uds image access key'), + cfg.StrOpt('s3_store_secret_key_for_cinder', secret=True, + help='FusionCompute uds image secret key'), + cfg.StrOpt('export_image_type', + help='FusionCompute export image type : nfs, glance, uds'), + cfg.StrOpt('uds_ip', + help='FusionSphere uds server ip'), + cfg.StrOpt('uds_port', + help='FusionSphere uds server port'), + cfg.StrOpt('uds_bucket_name', + help='FusionSphere uds server bucket name'), + cfg.StrOpt('uds_bucket_type', + help='FusionSphere uds server bucket type fixed or wildcard'), + cfg.ListOpt('vrm_ds_types', + default=['advanceSan', 'DSWARE', 'san', 'NAS', 'LUNPOME', + 'LOCAL', 'LOCALPOME'], + help='Management port of VRM'), + cfg.StrOpt('vrm_ds_hosts_share', + default='false', + help='FusionSphere support all host model'), + cfg.StrOpt('vrm_is_thin', + default='true', + help='FusionSphere create volume default type'), + cfg.StrOpt('export_version', + default='v6.0', + help='export version of VHD'), +] +CONF = cfg.CONF +CONF.register_group(VRM_group) +CONF.register_opts(VRM_opts, VRM_group) +CONF.register_opts(vrm_http_opts) +FC_DRIVER_CONF = CONF.VRM + + diff --git a/cinder/volume/drivers/huawei/fusioncompute/datastore_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/datastore_proxy.py new file mode 100644 index 0000000..b526107 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/datastore_proxy.py @@ -0,0 +1,114 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import json + +from oslo_log import log as logging + +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy +from cinder.volume.drivers.huawei.vrm import exception as driver_exception + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) + + +class DatastoreProxy(BaseProxy): + '''DatastoreProxy + + DatastoreProxy + ''' + def __init__(self): + super(DatastoreProxy, self).__init__() + + def list_datastore(self, **kwargs): + '''list_datastore + + :param kwargs: + :return: + ''' + + # LOG.info(_("[VRM-CINDER] start list_datastore()")) + uri = '/datastores' + method = 'GET' + path = self.site_uri + uri + + offset = 0 + datastores = [] + while True: + parames = {'limit': self.limit, + 'offset': offset, + 'scope': kwargs.get('scope')} + + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + if total > 0: + res = body.get('datastores') + datastores += res + offset += len(res) + if offset >= total or len(datastores) >= total or len( + res) < self.limit: + break + else: + break + + return datastores + + def query_datastore(self, **kwargs): + '''Query DataStore + + :param kwargs: + :return: + ''' + # LOG.info(_("[VRM-CINDER] start list_datastore()")) + uri = '/datastores' + '/' + kwargs.get('id') + method = 'GET' + path = self.site_uri + uri + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + return body + + def check_ds_connect_cluster(self, **kwargs): + '''Check cluster connected to the datastore + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start _check_ds_connect_cluster()")) + uri = '/datastores' + method = 'POST' + body = {'clusterUrn': kwargs.get('cluster_urn')} + path = self.site_uri + uri + '/' + kwargs.get( + 'datastore_id') + '/action/ifconcluster' + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + if_connect_flag = body.get('ifConnectFlag') + if if_connect_flag and if_connect_flag is True: + return + else: + raise driver_exception.NoNeededData() diff --git a/cinder/volume/drivers/huawei/fusioncompute/exception.py b/cinder/volume/drivers/huawei/fusioncompute/exception.py new file mode 100644 index 0000000..f36b88b --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/exception.py @@ -0,0 +1,184 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] EXCEPTION +""" + +from cinder import exception + +""" +Exception definitions. +""" + + +class UnsupportedFeature(exception.CinderException): + '''UnsupportedFeature + + UnsupportedFeature + ''' + pass + + +class UnsupportedVersion(UnsupportedFeature): + '''UnsupportedVersion + + UnsupportedVersion + ''' + pass + + +class UnsupportedCommand(UnsupportedFeature): + '''UnsupportedCommand + + UnsupportedCommand + ''' + pass + + +class AuthorizationFailure(exception.CinderException): + '''AuthorizationFailure + + AuthorizationFailure + ''' + pass + + +class NoNeededData(exception.CinderException): + '''NoNeededData + + NoNeededData + ''' + pass + + +class ClientException(exception.CinderException): + '''ClientException + + ClientException + ''' + + def __init__(self, code=None, message=None, error_code=None, + error_des=None): + + self.code = code + self.errorCode = error_code + self.errorDes = error_des + if message: + self.message = message + else: + self.message = "client exception." + super(ClientException, self).__init__(self.message) + + def __str__(self): + formatted_string = "%s (HTTP %s)" % (self.message, self.code) + if self.errorCode: + formatted_string += " (errorCode: %s)" % self.errorCode + + if self.errorDes: + formatted_string += " (errorDes: %s)" % self.errorDes + + return formatted_string + + +class BadRequest(ClientException): + """BadRequest + + HTTP 400 - Bad request: you sent some malformed data. + """ + http_status = 400 + message = "Bad request" + + +class Unauthorized(ClientException): + """Unauthorized + + HTTP 401 - Unauthorized: bad credentials. + """ + http_status = 401 + message = "Unauthorized" + + +class Forbidden(ClientException): + """Forbidden + + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + http_status = 403 + message = "Forbidden" + + +class NotFound(ClientException): + """NotFound + + HTTP 404 - Not found + """ + http_status = 404 + message = "Not found" + + +class OverLimit(ClientException): + """OverLimit + + HTTP 413 - Over limit: you're over the API limits for this time period. + """ + http_status = 413 + message = "Over limit" + + +class HTTPNotImplemented(ClientException): + """HTTPNotImplemented + + HTTP 501 - Not Implemented: the server does not support this operation. + """ + http_status = 501 + message = "Not Implemented" + + +class FusionComputeDriverException(ClientException): + '''FusionComputeDriverException + + FusionComputeDriverException + ''' + http_status = 500 + message = "FusionCompute driver exception occurred." + + +_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized, + Forbidden, NotFound, + OverLimit, HTTPNotImplemented]) + + +def exception_from_response(response, body): + """exception_from_response + + Return an instance of an ClientException or subclass + based on an requests response. + + Usage:: + + resp, body = requests.request(...) + if resp.status_code != 200: + raise exception_from_response(resp, rest.text) + """ + cls = _code_map.get(response.status_code, ClientException) + if body: + error_code = body.get('errorCode', None) + error_des = body.get('errorDes', None) + return cls(code=response.status_code, errorCode=error_code, + errorDes=error_des) + else: + return cls(code=response.status_code) diff --git a/cinder/volume/drivers/huawei/fusioncompute/host_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/host_proxy.py new file mode 100644 index 0000000..5d2a76f --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/host_proxy.py @@ -0,0 +1,77 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" + +from oslo_log import log as logging + +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy + + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) + + +class HostProxy(BaseProxy): + '''HostProxy + + ''' + def __init__(self): + super(HostProxy, self).__init__() + + def list_host(self, **kwargs): + '''list_host + + :param kwargs: + :return: + ''' + # LOG.info(_("[VRM-CINDER] start list_host()")) + uri = '/hosts' + method = 'GET' + path = self.site_uri + uri + + offset = 0 + hosts = [] + while True: + parameters = {'limit': self.limit, + 'offset': offset, + 'scope': kwargs.get('scope')} + + appendix = self._joined_params(parameters) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + if total > 0: + res = body.get('hosts') + hosts += res + offset += len(res) + if offset >= total or len(hosts) >= total or len( + res) < self.limit: + break + else: + break + + return hosts + + diff --git a/cinder/volume/drivers/huawei/fusioncompute/http_client.py b/cinder/volume/drivers/huawei/fusioncompute/http_client.py new file mode 100644 index 0000000..446deab --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/http_client.py @@ -0,0 +1,399 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import json +import requests +import urlparse + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.conf import FC_DRIVER_CONF +from cinder.volume.drivers.huawei.vrm import exception as driver_exception +from cinder.volume.drivers.huawei.vrm import utils as apiutils + +try: + from eventlet import sleep +except ImportError: + from time import sleep + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +CONF = cfg.CONF + +LOG = logging.getLogger(__name__) + + +class VRMHTTPClient(object): + """Executes volume driver commands on VRM.""" + + USER_AGENT = 'VRM-HTTP-Client for OpenStack' + RESOURCE_URI = 'uri' + TASK_URI = 'taskUri' + BASIC_URI = '/service' + vrm_commands = None + + def __init__(self): + '''VRMHTTPClient init + + __init__ + + :return: + ''' + fc_ip = FC_DRIVER_CONF.fc_ip + fc_image_path = FC_DRIVER_CONF.fc_image_path + fc_user = FC_DRIVER_CONF.fc_user + fc_pwd = FC_DRIVER_CONF.fc_pwd_for_cinder + self.ssl = CONF.vrm_ssl + self.host = fc_ip + self.port = CONF.vrm_port + self.user = fc_user + self.userType = CONF.vrm_usertype + self.password = apiutils.sha256_based_key(fc_pwd) + self.retries = CONF.vrm_retries + self.timeout = CONF.vrm_timeout + self.limit = CONF.vrm_limit + self.image_url = fc_image_path + self.image_type = '.xml' + + self.versions = None + self.version = None + self.auth_uri = None + self.auth_url = None + + self.auth_token = None + + self.sites = None + self.site_uri = None + self.site_urn = None + self.site_url = None + + self.shared_hosts = None + self.shared_datastores = None + self.shared_volumes = None + + def _generate_url(self, path, query=None, frag=None): + '''_generate_url + + _generate_url + + :param path: + :param query: + :param frag: + :return: + ''' + if CONF.vrm_ssl: + scheme = 'https' + else: + scheme = 'http' + fc_ip = FC_DRIVER_CONF.fc_ip + + netloc = str(fc_ip) + ':' + str(CONF.vrm_port) + if path.startswith(self.BASIC_URI): + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + else: + url = urlparse.urlunsplit( + (scheme, netloc, self.BASIC_URI + str(path), query, frag)) + return url + + def _http_log_req(self, args, kwargs): + '''_http_log_req + + _http_log_req + + :param args: + :param kwargs: + :return: + ''' + string_parts = ['\n curl -i'] + for element in args: + if element in ('GET', 'POST', 'DELETE', 'PUT'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + if 'body' in kwargs: + string_parts.append(" -d '%s'" % (kwargs['body'])) + + def _http_log_resp(self, resp): + '''_http_log_resp + + _http_log_resp + + :param resp: + :return: + ''' + try: + if resp.status_code: + if int(resp.status_code) != 200: + LOG.info(_("RESP status_code: [%s]"), resp.status_code) + except Exception: + LOG.info(_("[VRM-CINDER] _http_log_resp exception")) + + def request(self, url, method, **kwargs): + '''request + + request + + :param url: + :param method: + :param kwargs: + :return: + ''' + auth_attempts = 0 + attempts = 0 + step = 1 + while True: + step *= 2 + attempts += 1 + if not self.auth_url or not self.auth_token or not self.site_uri: + LOG.info(_("[VRM-CINDER] auth_url is none. ")) + + kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token + try: + resp, body = self.try_request(url, method, **kwargs) + return resp, body + + except driver_exception.Unauthorized as e: + LOG.error('[VRM-CINDER] error message is :%s' % e) + if e.errorCode == '10000040': + if auth_attempts > 2: + LOG.error("license error.") + raise driver_exception.ClientException(101) + if auth_attempts > 10: + raise driver_exception.ClientException(101) + LOG.info("Unauthorized, reauthenticating.") + attempts -= 1 + auth_attempts += 1 + sleep(step) + self.authenticate() + continue + except driver_exception.ClientException as ex: + if attempts > self.retries: + LOG.info(_("[VRM-CINDER] ClientException ")) + raise ex + if 500 <= ex.code <= 599: + LOG.info(_("[VRM-CINDER] ClientException ")) + else: + LOG.info(_("[VRM-CINDER] ClientException ")) + raise ex + except requests.exceptions.ConnectionError as ex: + LOG.error("Connection Error: %s" % ex) + LOG.error("Connection Error: %s" % ex.message) + raise ex + LOG.info( + "Failed attempt(%s of %s), retrying in %s seconds" % + (attempts, self.retries, step)) + sleep(step) + + def try_request(self, url, method, **kwargs): + '''try_request + + request + + :param url: + :param method: + :param kwargs: + :return: + ''' + + no_version = False + if not self.version: + no_version = True + if url.endswith('session'): + no_version = True + + kwargs.setdefault('headers', kwargs.get('headers', {})) + kwargs['headers']['User-Agent'] = self.USER_AGENT + if no_version: + kwargs['headers']['Accept'] = 'application/json;charset=UTF-8' + else: + version = self.version.lstrip(' v') + if url.endswith('/action/export'): + export_version = FC_DRIVER_CONF.export_version + version = '1.2' if export_version == 'v1.2' else \ + self.version.lstrip(' v') + kwargs['headers']['Accept'] = 'application/json;version=' + \ + version + ';charset=UTF-8' + kwargs['headers']['X-Auth-Token'] = self.auth_token + kwargs['headers']['Accept-Language'] = 'en_US' + if 'body' in kwargs: + if kwargs['body'] and len(kwargs['body']) > 0: + kwargs['headers'][ + 'Content-Type'] = 'application/json;charset=UTF-8' + kwargs['data'] = kwargs['body'] + + body = apiutils.str_drop_password_key(kwargs['body']) + # LOG.info(_("[VRM-CINDER] request body [%s]"), body) + del kwargs['body'] + + self._http_log_req((url, method,), kwargs) + resp = requests.request( + method, + url, + verify=False, + **kwargs) + self._http_log_resp(resp) + + if resp.content: + try: + body = json.loads(resp.content) + except ValueError: + body = None + else: + body = None +# LOG.info(_("[VRM-CINDER] request status_code [%d]"), resp.status_code) + if resp.status_code >= 400: + LOG.error(_("error response, error is %s"), body) + raise driver_exception.exception_from_response(resp, body) + + return resp, body + + def _prepare_version_and_auth_url(self): + '''_prepare_version_and_auth_url + + _prepare_version_and_auth_url + + :return: + ''' + self.version = CONF.vrm_version + self.auth_uri = '/service/session' + self.auth_url = self._generate_url(self.auth_uri) + + def _prepare_auth_token(self): + '''_prepare_auth_token + + _prepare_auth_token + + :return: + ''' + uri = '/service/session' + new_url = self._generate_url(uri) + # self.auth_token = None + headers = {'X-Auth-User': self.user, + 'X-Auth-Key': self.password, + 'X-Auth-UserType': self.userType, } + resp, body = self.try_request(new_url, 'POST', headers=headers) + if resp.status_code in (200, 204): + self.auth_token = resp.headers['x-auth-token'] + + def _prepare_site_uri(self): + '''_prepare_site_uri + + _prepare_site_uri + + :return: + ''' + self.site_uri = self.site_urn = self.site_url = None + url = self._generate_url('/sites') + headers = {'X-Auth-Token': self.auth_token} + + resp, body = self.try_request(url, 'GET', headers=headers) + if resp.status_code in (200, 204): + self.sites = body['sites'] + if len(self.sites) == 1: + self.site_uri = self.sites[0]['uri'] + self.site_urn = self.sites[0]['urn'] + self.site_url = self._generate_url(self.site_uri) + return + else: + for si in self.sites: + if si['urn'] == FC_DRIVER_CONF.vrm_siteurn: + self.site_uri = si['uri'] + self.site_urn = si['urn'] + self.site_url = self._generate_url(self.site_uri) + return + + raise driver_exception.NotFound() + + def authenticate(self): + '''authenticate + + authenticate + + :return: + ''' + self._prepare_version_and_auth_url() + self._prepare_auth_token() + self._prepare_site_uri() + + if not self.version: + LOG.info(_("[VRM-CINDER] (%s)"), 'AuthorizationFailure') + raise driver_exception.AuthorizationFailure + if not self.auth_url: + LOG.info(_("[VRM-CINDER] (%s)"), 'AuthorizationFailure') + raise driver_exception.AuthorizationFailure + if not self.site_uri: + LOG.info(_("[VRM-CINDER] (%s)"), 'AuthorizationFailure') + raise driver_exception.AuthorizationFailure + + def get_version(self): + '''get_version + + get_version + + :return: + ''' + return self.version + + def get_siteurn(self): + '''get_siteurn + + get_siteurn + + :return: + ''' + if self.site_uri is None: + self.init() + return self.site_urn + + def get_siteuri(self): + '''get_siteuri + + get_siteurn + + :return: + ''' + if self.site_uri is None: + self.init() + return self.site_uri + + def init(self): + '''init + + init + + :return: + ''' + LOG.info(_("[VRM-CINDER] start init()")) + self.authenticate() + + + + diff --git a/cinder/volume/drivers/huawei/fusioncompute/task_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/task_proxy.py new file mode 100644 index 0000000..aa9c5da --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/task_proxy.py @@ -0,0 +1,130 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +from oslo_config import cfg +from oslo_log import log as logging + +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy +from cinder.volume.drivers.huawei.vrm import exception as driver_exception +from cinder.volume.drivers.huawei.vrm.utils import Delete_Snapshot_Code + +try: + from eventlet import sleep +except ImportError: + from time import sleep + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +CONF = cfg.CONF + +LOG = logging.getLogger(__name__) + + +class TaskProxy(BaseProxy): + '''TaskProxy + + TaskProxy + ''' + def __init__(self): + super(TaskProxy, self).__init__() + + def wait_task(self, isShortQuery=0, **kwargs): + '''wait_task + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start wait_task()")) + + task_uri = kwargs.get('task_uri') + method = 'GET' + if task_uri is None: + LOG.info(_("[VRM-CINDER] task_uri is none.")) + raise driver_exception.ClientException(101) + else: + new_url = self._generate_url(task_uri) + retry = 0 + error_num = 0 + while retry < int(CONF.vrm_timeout): + if isShortQuery == 0: + if retry > 10: + retry += 10 + sleep(10) + else: + retry += 3 + sleep(3) + else: + if retry > 10: + retry += 10 + sleep(10) + else: + retry += 1 + sleep(1) + try: + resp, body = self.vrmhttpclient.request(new_url, method) + except Exception as ex: + LOG.info(_("[VRM-CINDER] querytask request exception.")) + error_num += 1 + if 30 < error_num: + LOG.info( + _("[VRM-CINDER] querytask request exception.")) + raise ex + else: + continue + + if body: + status = body.get('status') + if status in [TASK_WAITING, TASK_RUNNING]: + + continue + elif status in [TASK_SUCCESS]: + LOG.info( + _("[VRM-CINDER] return TASK_SUCCESS wait_task()")) + return status + elif status in [TASK_FAILED, TASK_CANCELLING]: + LOG.info( + _("[VRM-CINDER] return TASK_FAILED wait_task()")) + if body.get('reason') in Delete_Snapshot_Code: + raise driver_exception.ClientException( + code=101, + message=body.get('reasonDes'), + errorCode=body.get('reason')) + raise driver_exception.ClientException(101) + else: + LOG.info(_("[VRM-CINDER] pass wait_task()")) + error_num += 1 + if 30 < error_num: + raise driver_exception.ClientException(101) + else: + continue + else: + LOG.info(_("[VRM-CINDER] body is none.")) + error_num += 1 + if 30 < error_num: + raise driver_exception.ClientException(101) + else: + continue + + diff --git a/cinder/volume/drivers/huawei/fusioncompute/utils.py b/cinder/volume/drivers/huawei/fusioncompute/utils.py new file mode 100644 index 0000000..42b6d7e --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/utils.py @@ -0,0 +1,92 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" + FC Driver utils function +""" +import hashlib +import sys +import traceback + +from cinder.i18n import _ +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + + +def log_exception(exception=None): + """log_exception + + :param exception: + :return: + """ + + if exception: + pass + + etype, value, track_tb = sys.exc_info() + error_list = traceback.format_exception(etype, value, track_tb) + for error_info in error_list: + LOG.error(error_info) + + +def str_drop_password_key(str_data): + """str_drop_password_key + + remove json password key item + :param data: + :return: + """ + + dict_data = eval(str_data) + if isinstance(dict_data, dict): + drop_password_key(dict_data) + return str(dict_data) + else: + LOG.info( + _("[BRM-DRIVER] str_data can't change to dict, str_data:(%s) "), + str_data) + return + + +def drop_password_key(data): + """remove json password key item + + :param data: + :return: + """ + encrypt_list = ['password', 'vncpassword', 'oldpassword', + 'domainpassword', 'vncoldpassword', 'vncnewpassword', + 'auth_token', 'token', 'fc_pwd', 'accessKey', + 'secretKey'] + for key in data.keys(): + if key in encrypt_list: + del data[key] + elif data[key] and isinstance(data[key], dict): + drop_password_key(data[key]) + + +def sha256_based_key(key): + """sha256_based_key + + generate sha256 based key + :param key: + :return: + """ + hash_ = hashlib.sha256() + hash_.update(key) + return hash_.hexdigest() + +Delete_Snapshot_Code = ['10400004'] diff --git a/cinder/volume/drivers/huawei/fusioncompute/vm_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/vm_proxy.py new file mode 100644 index 0000000..d2ca9df --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/vm_proxy.py @@ -0,0 +1,1168 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import json +import threading + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder import exception +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy +from cinder.volume.drivers.huawei.vrm.conf import FC_DRIVER_CONF +from cinder.volume.drivers.huawei.vrm.task_proxy import TaskProxy + +try: + from eventlet import sleep +except ImportError: + from time import sleep + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF +vrm_template_lock = threading.Lock() + + +class VmProxy(BaseProxy): + '''VmProxy + + VmProxy + ''' + def __init__(self, *args, **kwargs): + super(VmProxy, self).__init__() + self.task_proxy = TaskProxy() + + def query_vm(self, **kwargs): + '''Query VM + + 'query_vm': ('GET', + ('/vms', None, kwargs.get('vm_id'), None), + {}, + {}, + False) + ''' + LOG.info(_("[VRM-CINDER] start query_vm()")) + uri = '/vms' + method = 'GET' + path = self.site_uri + uri + '/' + kwargs.get('vm_id') + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + LOG.info(_("[VRM-CINDER] end ()")) + return body + + def query_vm_by_uri(self, **kwargs): + '''Query VM by Uri + + 'query_vm': ('GET', + ('/vms', None, kwargs.get('vm_id'), None), + {}, + {}, + False) + ''' + LOG.info(_("[VRM-CINDER] start query_vm()")) + method = 'GET' + path = kwargs.get('vm_uri') + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + LOG.info(_("[VRM-CINDER] end ()")) + return body + + def delete_vm(self, **kwargs): + '''Delete VM + + 'delete_vm': ('DELETE', + ('/vms', None, kwargs.get('vm_id'), None), + {}, + {}, + True), + ''' + LOG.info(_("[VRM-CINDER] start delete_vm()")) + uri = '/vms' + method = 'DELETE' + path = self.site_uri + uri + '/' + kwargs.get('vm_id') + if kwargs.get('isReserveDisks') is not None and kwargs.get( + 'isReserveDisks') == 1: + path = path + '?isReserveDisks=1' + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + task_uri = body.get('taskUri') + if kwargs.get('isReserveDisks') is not None and kwargs.get( + 'isReserveDisks') == 1: + self.task_proxy.wait_task(task_uri=task_uri, isShortQuery=1) + else: + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end ()")) + + def detach_vol_from_vm(self, **kwargs): + '''detach_vol_from_vm + + 'detach_vol_from_vm': ('POST', + ('/vms', None, kwargs.get('vm_id'), + 'action/detachvol'), + {}, + {'volUrn': kwargs.get('volUrn')}, + True), + ''' + LOG.info(_("[VRM-CINDER] start detach_vol_from_vm()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/detachvol' + new_url = self._generate_url(path) + body = {'volUrn': kwargs.get('volume_urn')} + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end ()")) + + def attach_vol_to_vm(self, **kwargs): + '''attach_vol_to_vm + + 'attach_vol_to_vm': ('POST', + ('/vms', None, kwargs.get('vm_id'), + 'action/attachvol'), + {}, + {'volUrn': kwargs.get('volUrn')}, + True), + ''' + LOG.info(_("[VRM-CINDER] start attach_vol_to_vm()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/attachvol' + new_url = self._generate_url(path) + body = {'volUrn': kwargs.get('volume_urn')} + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end attach_vol_to_vm()")) + + def stop_vm(self, **kwargs): + '''stop_vm + + 'stop_vm': ('POST', + ('/vms', None, kwargs.get('vm_id'), 'action/stop'), + {}, + {'mode': kwargs.get('mode')}, + True), + ''' + LOG.info(_("[VRM-CINDER] start stop_vm()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get('vm_id') + '/action/stop' + new_url = self._generate_url(path) + body = {'mode': kwargs.get('mode')} + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end ()")) + + def _combine_empty_vmConfig(self, **kwargs): + '''_combine_empty_vmConfig + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start _combine_empty_vmConfig ()")) + cpu_quantity = 2 + mem_quantityMB = 1024 + + cpu = {'quantity': cpu_quantity} + memory = {'quantityMB': mem_quantityMB} + disks = [] + vmConfigBody = { + 'cpu': cpu, + 'memory': memory, + 'disks': disks, + } + LOG.info(_("[VRM-CINDER] _combine_empty_vmConfig end ()")) + return vmConfigBody + + def _combine_vmConfig_4_import(self, **kwargs): + '''_combine_vmConfig_4_import + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start _combine_vmConfig ()")) + cpu_quantity = 2 + mem_quantityMB = 1024 + datastoreUrn = kwargs.get('ds_urn') + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + link = kwargs.get('linkClone') + disk_quantityGB = kwargs.get('volume_size') + if link: + uuid = "" + if kwargs.get('quick_start') is True: + disk_quantityGB = kwargs.get('image_size') + else: + uuid = kwargs.get("volume_id") + + thin = kwargs.get('is_thin') + volumeUrn = kwargs.get('volume_urn') + cpu = {'quantity': cpu_quantity} + memory = {'quantityMB': mem_quantityMB} + disks = [ + { + 'volumeUrn': volumeUrn, + 'datastoreUrn': datastoreUrn, + 'quantityGB': disk_quantityGB, + 'volType': 0, + 'sequenceNum': kwargs.get('volume_sequence_num'), + 'pciType': 'IDE', + 'volumeUuid': uuid, + 'isThin': thin, + 'isCoverData': True + } + ] + vmConfigBody = { + 'cpu': cpu, + 'memory': memory, + 'disks': disks, + } + LOG.info(_("[VRM-CINDER] end ()")) + return vmConfigBody + + def _combine_vmConfig_4_clone(self, **kwargs): + '''_combine_vmConfig_4_clone + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start _combine_vmConfig ()")) + cpu_quantity = 2 + mem_quantityMB = 1024 + datastoreUrn = kwargs.get('ds_urn') + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + disk_quantityGB = kwargs.get('volume_size') + link = kwargs.get('linkClone') + if link: + uuid = "" + else: + uuid = kwargs.get("volume_id") + + thin = kwargs.get('is_thin') + cpu = {'quantity': cpu_quantity} + memory = {'quantityMB': mem_quantityMB} + disks = [ + { + 'datastoreUrn': datastoreUrn, + 'quantityGB': disk_quantityGB, + 'volType': 0, + 'sequenceNum': 1, + 'pciType': 'IDE', + 'volumeUuid': uuid, + 'isThin': thin + } + ] + vmConfigBody = { + 'cpu': cpu, + 'memory': memory, + 'disks': disks, + } + LOG.info(_("[VRM-CINDER] end ()")) + return vmConfigBody + + def _combine_vmConfig_4_export(self, **kwargs): + '''_combine_vmConfig_4_export + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start _combine_vmConfig ()")) + cpu_quantity = 2 + mem_quantityMB = 1024 + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + cpu = {'quantity': cpu_quantity} + memory = {'quantityMB': mem_quantityMB} + disks = [ + { + 'sequenceNum': kwargs.get('volume_sequence_num'), + } + ] + vmConfigBody = { + 'cpu': cpu, + 'memory': memory, + 'disks': disks, + } + LOG.info(_("[VRM-CINDER] end ()")) + return vmConfigBody + + def _combine_os_options(self, **kwargs): + '''_combine_os_options + + :param kwargs: + :return: + ''' + osOptions = { + 'osType': 'Windows', + 'osVersion': 26 + } + LOG.info(_("[VRM-CINDER] end ()")) + return osOptions + + def clone_vm(self, **kwargs): + '''clone_vm + + :param kwargs: + :return: + ''' + + LOG.info(_("[VRM-CINDER] start clone_vm()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'template_id') + '/action/clone' + new_url = self._generate_url(path) + + linked_clone = kwargs.get('linked_clone') + if linked_clone is None: + linked_clone = False + LOG.info(_("[VRM-CINDER] start clone_vm()")) + body = { + 'name': 'cinder-vm-' + kwargs.get('volume_id'), + 'group': 'FSP', + 'description': 'cinder-driver-temp-vm', + 'autoBoot': 'false', + 'isLinkClone': linked_clone, + 'vmConfig': self._combine_vmConfig_4_clone(**kwargs), + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri, isShortQuery=1) + LOG.info(_("[VRM-CINDER] end clone_vm()")) + return body.get('urn') + + def import_vm_from_glance(self, **kwargs): + '''import_vm_from_glance + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start import_vm_from_glance()")) + uri = self.site_uri + '/vms/action/import' + method = 'POST' + new_url = self._generate_url(uri) + link_nfs = kwargs.get('linkClone') + if link_nfs: + name = kwargs.get('image_id') + else: + name = kwargs.get('volume_id') + + is_template = kwargs.get("is_template") + if is_template is None or is_template: + template = 'true' + else: + template = 'false' + + if CONF.glance_host is None or str(CONF.glance_port) is None \ + or FC_DRIVER_CONF.glance_server_ip is None: + raise exception.ParameterNotFound(param='glance_host or ' + 'glance_port or glance_ip') + + endpoint = CONF.glance_host + ":" + str(CONF.glance_port) + token = kwargs.get('auth_token') + serviceIp = FC_DRIVER_CONF.glance_server_ip + body = { + 'name': 'cinder-vm-' + name, + 'group': 'FSP', + 'description': 'cinder-glance-vm', + 'autoBoot': 'false', + 'location': kwargs.get("cluster_urn"), + 'osOptions': self._combine_os_options(**kwargs), + 'protocol': "glance", + 'vmConfig': self._combine_vmConfig_4_import(**kwargs), + 'isTemplate': template, + 'glanceConfig': { + 'imageID': kwargs.get('image_id'), + 'endPoint': endpoint, + 'serverIp': serviceIp, + 'token': token} + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end import_vm_from_glance()")) + return body.get('urn') + + def import_vm_from_uds(self, **kwargs): + '''import_vm_from_uds + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start import_vm_from_uds()")) + uri = self.site_uri + '/vms/action/import' + method = 'POST' + new_url = self._generate_url(uri) + link_nfs = kwargs.get('linkClone') + if link_nfs: + name = kwargs.get('image_id') + else: + name = kwargs.get('volume_id') + + is_template = kwargs.get("is_template") + if is_template is None or is_template: + template = 'true' + else: + template = 'false' + + if FC_DRIVER_CONF.s3_store_access_key_for_cinder is None or \ + FC_DRIVER_CONF.s3_store_secret_key_for_cinder is None: + LOG.error(_("[VRM-CINDER] some params is None, please check: " + "s3_store_access_key_for_cinder, " + "s3_store_secret_key_for_cinder")) + raise exception.ParameterNotFound( + param='s3_store_access_key_for_cinder or ' + 's3_store_secret_key_for_cinder') + + uds_name = FC_DRIVER_CONF.s3_store_access_key_for_cinder + uds_password = FC_DRIVER_CONF.s3_store_secret_key_for_cinder + location = kwargs.get('image_location') + location = location.split(":") + if len(location) != 4: + msg = _('image_location is invalid') + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=kwargs.get('image_id'), + reason=msg) + serverIp = location[0].strip() + port = location[1].strip() + bucketName = location[2].strip() + key = location[3].strip() + + body = { + 'name': 'cinder-vm-' + name, + 'group': 'FSP', + 'description': 'cinder-uds-vm', + 'autoBoot': 'false', + 'location': kwargs.get("cluster_urn"), + 'osOptions': self._combine_os_options(**kwargs), + 'protocol': "uds", + 'vmConfig': self._combine_vmConfig_4_import(**kwargs), + 'isTemplate': template, + 's3Config': { + 'serverIp': serverIp, + 'port': port, + 'accessKey': uds_name, + 'secretKey': uds_password, + 'bucketName': bucketName, + 'key': key + } + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end import_vm_from_uds()")) + return body.get('urn') + + def import_vm_from_nfs(self, **kwargs): + '''import_vm_from_nfs + + 'import_vm_from_image': ('POST', + ('/vms/action/import', None, None, None), + {}, + dict({ + 'name': 'name', + 'location': kwargs.get('cluster_urn'), + 'autoBoot': 'false', + 'url': kwargs.get('url'), + 'protocol': 'nfs', + 'vmConfig': { + 'cpu': { + 'quantity': 1 + }, + 'memory': { + 'quantityMB': 1024 + }, + 'disks': [ + { + 'pciType': 'IDE', + 'datastoreUrn': kwargs.get('ds_urn'), + 'quantityGB': kwargs.get('vol_size'), + 'volType': 0, + 'sequenceNum': 1, + }, + ], + }, + 'osOptions': { + 'osType': 'Windows', + 'osVersion': 32 + } + }), + True), + ''' + + LOG.info(_("[VRM-CINDER] start import_vm_from_nfs()")) + uri = self.site_uri + '/vms/action/import' + method = 'POST' + new_url = self._generate_url(uri) + link_nfs = kwargs.get('linkClone') + if link_nfs: + name = kwargs.get('image_id') + else: + name = kwargs.get('volume_id') + + is_template = kwargs.get("is_template") + if is_template is None or is_template: + template = 'true' + else: + template = 'false' + + body = \ + { + 'name': 'cinder-vm-' + name, + 'group': 'FSP', + 'description': 'cinder-nfs-vm', + 'autoBoot': 'false', + 'location': kwargs.get("cluster_urn"), + 'osOptions': self._combine_os_options(**kwargs), + 'protocol': "nfs", + 'vmConfig': self._combine_vmConfig_4_import(**kwargs), + 'url': kwargs.get('image_location'), + 'isTemplate': template + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end import_vm_from_nfs()")) + return body.get('urn') + + def create_volume_from_extend(self, **kwargs): + '''create_volume_from_extend + + create_linkclone_volume + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start create_volume_from_extend()")) + image_type = kwargs.get('image_type') + + if image_type == "nfs": + LOG.info(_("[VRM-CINDER] start create_volume_from_nfs")) + vm_urn = self.import_vm_from_nfs(**kwargs) + elif image_type == 'uds': + LOG.info(_("[VRM-CINDER] start create_volume_from_uds")) + vm_urn = self.import_vm_from_uds(**kwargs) + else: + LOG.info(_("[VRM-CINDER] start create_volume_from_glance")) + vm_urn = self.import_vm_from_glance(**kwargs) + + vm_id = vm_urn[-10:] + vm = self.query_vm(vm_id=vm_id) + vm_config = vm['vmConfig'] + disks = vm_config['disks'] + volume_urn = None + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + for disk in disks: + if int(disk['sequenceNum']) == int(kwargs['volume_sequence_num']): + volume_urn = disk['volumeUrn'] + break + if volume_urn is None: + msg = (_("[VRM-CINDER] no available disk")) + LOG.error(msg) + self.delete_vm(vm_id=vm_id) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + try: + self.detach_vol_from_vm(vm_id=vm_id, volume_urn=volume_urn) + except Exception as ex: + LOG.error(_('detach volume is failed')) + self.delete_vm(vm_id=vm_id) + raise ex + + self.delete_vm(vm_id=vm_id) + LOG.info(_("[VRM-CINDER] end ()")) + return volume_urn + + def check_template_status(self, vm_id): + LOG.info(_("[VRM-CINDER] start check template status()")) + uri = self.site_uri + '/vms/' + vm_id + '/competition' + method = 'GET' + new_url = self._generate_url(uri) + resp, body = self.vrmhttpclient.request(new_url, method) + LOG.info(_("[VRM-CINDER] template info %s") % body) + body.get('status') + + def check_template(self, **kwargs): + '''check_template + + :param kwargs: + :return: + ''' + template_id = kwargs.get('template_id') + vm_name = kwargs.get('vm_name') + templates = self.get_templates() + id = None + for template in templates: + if template_id is not None: + urn = template.get('urn') + id = urn[-10:] + if id == template_id: + LOG.info(_("[VRM-CINDER] template exists [%s]"), + template_id) + return id + + if vm_name is not None: + name = template.get('name') + if name == vm_name: + urn = template.get('urn') + id = urn[-10:] + LOG.info(_("[VRM-CINDER] vm is exists [%s]"), vm_name) + return id + return id + + def create_volume_from_template(self, **kwargs): + '''create_volume_from_template + + create_linkclone_volume + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start create_volume_from_template()")) + template = kwargs['image_location'] + kwargs['template_id'] = template[-10:] + is_exist = self.check_template(**kwargs) + if is_exist is None: + msg = (_("[VRM-CINDER] no such template %s "), + kwargs.get('template_id')) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + + while True: + template_vm = self.query_vm(vm_id=kwargs['template_id']) + LOG.info(_("[VRM-CINDER] template_vm status is %s"), + template_vm.get('status')) + if 'creating' == template_vm.get('status'): + sleep(10) + elif 'stopped' == template_vm.get('status'): + break + else: + msg = (_("[VRM-CINDER] template isn't available %s "), + kwargs.get('template_id')) + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + + vm_config = template_vm['vmConfig'] + template_disks = vm_config['disks'] + if len(template_disks) != 1: + msg = _("template must have one disk") + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + + vm_urn = self.clone_vm(**kwargs) + vm_id = vm_urn[-10:] + vm = self.query_vm(vm_id=vm_id) + vm_config = vm['vmConfig'] + disks = vm_config['disks'] + volume_urn = None + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + for disk in disks: + if int(disk['sequenceNum']) == int(kwargs['volume_sequence_num']): + volume_urn = disk['volumeUrn'] + break + if volume_urn is None: + msg = (_("[VRM-CINDER] no available disk")) + LOG.error(msg) + self.delete_vm(vm_id=vm_id) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + try: + self.delete_vm(vm_id=vm_id, isReserveDisks=1) + except Exception as ex: + LOG.error(_("delete vm(reserveDisk) is failed ")) + self.delete_vm(vm_id=vm_id) + raise ex + LOG.info(_("[VRM-CINDER] end ()")) + return volume_urn + + def create_linkclone_from_template(self, **kwargs): + '''create_linkclone_from_template + + create_linkclone_volume + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start create_linkclone_from_template()")) + kwargs['linked_clone'] = True + return self.create_volume_from_template(**kwargs) + + def create_linkClone_from_extend(self, **kwargs): + '''create_linkClone_from_extend + + :param kwargs: + :return: + ''' + LOG.debug(_("[VRM-CINDER] start create_linkClone_volume()")) + vrm_template_lock.acquire() + + try: + kwargs['linkClone'] = True + vm_name = 'cinder-vm-' + kwargs.get('image_id') + LOG.info(_("[VRM-CINDER] vm_name is %s"), vm_name) + kwargs['vm_name'] = vm_name + vm_id = self.check_template(**kwargs) + kwargs.pop('vm_name') + LOG.info(_("[VRM-CINDER] vm_id is %s"), vm_id) + image_type = kwargs.get('image_type') + if kwargs.get('volume_sequence_num') is None: + kwargs['volume_sequence_num'] = 1 + if 1 < int(kwargs['volume_sequence_num']): + msg = (_("[VRM-CINDER] volume_sequence_num is %s "), + kwargs['volume_sequence_num']) + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=kwargs['image_id'], + reason=msg) + + if vm_id is None: + kwargs["is_template"] = True + if image_type == 'nfs': + vm_urn = self.import_vm_from_nfs(**kwargs) + kwargs.pop('linkClone') + LOG.info(_("[VRM-CINDER] import_vm_from_nfs vm_urn is %s"), + vm_urn) + vm_id = vm_urn[-10:] + kwargs['image_location'] = vm_id + + elif image_type == 'uds': + vm_urn = self.import_vm_from_uds(**kwargs) + kwargs.pop('linkClone') + LOG.info(_("[VRM-CINDER] import_vm_from_uds vm_urn is %s"), + vm_urn) + vm_id = vm_urn[-10:] + kwargs['image_location'] = vm_id + + else: + vm_urn = self.import_vm_from_glance(**kwargs) + kwargs.pop('linkClone') + LOG.info( + _("[VRM-CINDER] import_vm_from_glance vm_urn is %s"), + vm_urn) + vm_id = vm_urn[-10:] + kwargs['image_location'] = vm_id + + else: + kwargs.pop('linkClone') + kwargs['image_location'] = vm_id + except Exception as ex: + vrm_template_lock.release() + raise ex + + vrm_template_lock.release() + return self.create_linkclone_from_template(**kwargs) + + def get_templates(self, **kwargs): + '''get_templates + + 'list_templates': ('GET', + ('/vms', None, None, None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope'), + 'isTemplate': 'true' + }, + {}, + False), + ''' + + LOG.info(_("[VRM-CINDER] start _get_templates()")) + uri = '/vms' + method = 'GET' + path = self.site_uri + uri + + offset = 0 + templates = [] + while True: + parames = { + 'limit': self.limit, + 'offset': offset, + 'scope': kwargs.get('scope'), + 'isTemplate': 'true' + } + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + if total > 0: + res = body.get('vms') + templates += res + offset += len(res) + if offset >= total or len(templates) >= total or len( + res) < self.limit: + break + else: + break + + LOG.info(_("[VRM-CINDER] end ()")) + return templates + + def create_vm(self, **kwargs): + '''Create Vm + + :param kwargs: + :return: + ''' + + LOG.info(_("[VRM-CINDER] start create_vm()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + + body = { + 'name': 'cinder-driver-temp-' + kwargs.get('volume_id'), + 'group': 'FSP', + 'description': 'cinder-driver-temp-vm', + 'autoBoot': 'false', + 'location': kwargs.get("cluster_urn"), + 'vmConfig': self._combine_empty_vmConfig(**kwargs), + 'osOptions': self._combine_os_options(**kwargs) + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end create_vm()")) + return body + + def export_vm_to_glance(self, **kwargs): + '''export_vm_to_glance + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start export_vm_to_glance")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/export' + new_url = self._generate_url(path) + if CONF.glance_host is None or str(CONF.glance_port) is None \ + or FC_DRIVER_CONF.glance_server_ip is None: + raise exception.ParameterNotFound(param='glance_host or ' + 'glance_port or glance_ip') + + endpoint = CONF.glance_host + ":" + str(CONF.glance_port) + token = kwargs.get('auth_token') + serviceIp = FC_DRIVER_CONF.glance_server_ip + + format = 'xml' if FC_DRIVER_CONF.export_version == 'v1.2' else 'ovf' + body = { + 'name': kwargs.get('image_id'), + 'format': format, + 'protocol': 'glance', + 'isOverwrite': 'false', + 'vmConfig': self._combine_vmConfig_4_export(**kwargs), + 'glanceConfig': { + 'imageID': kwargs.get('image_id'), + 'endPoint': endpoint, + 'serverIp': serviceIp, + 'token': token} + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end export_vm_to_glance")) + return body.get('urn') + + def export_vm_to_uds(self, **kwargs): + '''export_vm_to_uds + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start export_vm_to_uds")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/export' + new_url = self._generate_url(path) + + if FC_DRIVER_CONF.s3_store_access_key_for_cinder is None \ + or FC_DRIVER_CONF.s3_store_secret_key_for_cinder is None \ + or FC_DRIVER_CONF.uds_port is None or FC_DRIVER_CONF.uds_ip \ + is None \ + or FC_DRIVER_CONF.uds_bucket_name is None: + LOG.error(_("[VRM-CINDER] some params is None, please check: " + "s3_store_access_key_for_cinder, " + "s3_store_secret_key_for_cinder, uds_port, uds_ip, " + "uds_bucket_name")) + raise exception.ParameterNotFound( + param='s3_store_access_key_for_cinder ' + 'or s3_store_secret_key_for_cinder or' + 'uds_port or uds_serverIp or uds_bucket_name') + + uds_name = FC_DRIVER_CONF.s3_store_access_key_for_cinder + uds_password = FC_DRIVER_CONF.s3_store_secret_key_for_cinder + port = FC_DRIVER_CONF.uds_port + serverIp = FC_DRIVER_CONF.uds_ip + bucketName = FC_DRIVER_CONF.uds_bucket_name + key = kwargs.get('image_id') + bucket_type = FC_DRIVER_CONF.uds_bucket_type + if bucket_type is not None: + if str(bucket_type) == 'wildcard': + if kwargs.get('project_id') is None: + LOG.error(_("project_id is none ")) + raise exception.ParameterNotFound( + param='project_id is none') + else: + bucketName += kwargs.get('project_id') + + LOG.info(_("[VRM-CINDER] bucketName is %s"), bucketName) + + format = 'xml' if FC_DRIVER_CONF.export_version == 'v1.2' else 'ovf' + body = { + 'name': kwargs.get('image_id'), + 'format': format, + 'protocol': 'uds', + 'isOverwrite': 'false', + 'vmConfig': self._combine_vmConfig_4_export(**kwargs), + 's3Config': { + 'serverIp': serverIp, + 'port': port, + 'accessKey': uds_name, + 'secretKey': uds_password, + 'bucketName': bucketName, + 'key': key} + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end export_vm_to_uds")) + return body.get('urn') + + def export_vm_to_nfs(self, **kwargs): + '''export_vm_to_nfs + + Post //action/export HTTP/1.1 + Host: https://: + Content-Type: application/json; charset=UTF-8 + Accept: application/json;version=; charset=UTF-8 + X-Auth-Token: + { + "name":string, + "url:string, + "username":administrator + "password":string + } + ''' + + LOG.info(_("[VRM-CINDER] start export_vm_to_nfs()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/export' + new_url = self._generate_url(path) + + format = 'xml' if FC_DRIVER_CONF.export_version == 'v1.2' else 'ovf' + body = { + 'name': kwargs.get('image_id'), + 'url': kwargs.get('image_url') + '/' + kwargs.get('image_id'), + 'vmConfig': self._combine_vmConfig_4_export(**kwargs), + 'format': format, + 'protocol': 'nfs', + 'isOverwrite': 'false' + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end export_vm_to_nfs()")) + return body.get('urn') + + def get_volume_sequence_num(self, vm_body, volume_urn): + volume_sequence_num = 1 + if vm_body is not None: + vm_uri = vm_body.get('uri') + volume_body = self.query_vm(vm_id=vm_uri[-10:]) + if volume_body is not None: + for item in volume_body.get('vmConfig').get('disks'): + if item.get('volumeUrn') == volume_urn: + volume_sequence_num = item.get("sequenceNum") + else: + LOG.info(_("[VRM-CINDER] vm_body is null)")) + return volume_sequence_num + + def export_volume_to_image(self, **kwargs): + '''export_volume_to_image + + :param kwargs: + :return: + ''' + LOG.info(_("[VRM-CINDER] start export_volume_to_image()")) + vm_body = self.query_vm_volume(**kwargs) + if vm_body is not None: + kwargs['volume_sequence_num'] = self.get_volume_sequence_num( + vm_body, + kwargs.get('volume_urn')) + vm_uri = vm_body.get('uri') + kwargs['vm_id'] = vm_uri[-10:] + LOG.info(_("[VRM-CINDER] volume is already attached")) + self.export_attached_volume_to_image(**kwargs) + LOG.info(_("[VRM-CINDER] volume_sequence_num %s"), + kwargs['volume_sequence_num']) + return kwargs['volume_sequence_num'] + + vm_body = self.create_vm(**kwargs) + vm_uri = vm_body.get('uri') + + kwargs['vm_id'] = vm_uri[-10:] + + if kwargs.get('shareable') == 'share': + kwargs['volume_sequence_num'] = 2 + else: + kwargs['volume_sequence_num'] = 1 + + try: + self.attach_vol_to_vm(**kwargs) + except Exception as ex: + LOG.error(_("[VRM-CINDER] attach volume is error ")) + self.delete_vm(**kwargs) + raise ex + try: + self.export_attached_volume_to_image(**kwargs) + except Exception as ex: + LOG.error(_("[VRM-CINDER] export vm is error ")) + self.detach_vol_from_vm(**kwargs) + self.delete_vm(**kwargs) + raise ex + + self.detach_vol_from_vm(**kwargs) + + self.delete_vm(**kwargs) + + LOG.info(_("[VRM-CINDER] end export_volume_to_nfs()")) + LOG.info(_("[VRM-CINDER] volume_sequence_num %s"), + kwargs['volume_sequence_num']) + return kwargs['volume_sequence_num'] + + def export_attached_volume_to_image(self, **kwargs): + image_type = kwargs.get('image_type') + if image_type == 'nfs': + LOG.info(_('[VRM-CINDER] export_vm_to_nfs')) + self.export_vm_to_nfs(**kwargs) + elif image_type == 'uds': + LOG.info(_('VRM-CINDER] export_vm_to_uds')) + self.export_vm_to_uds(**kwargs) + else: + LOG.info(_('VRM-CINDER] export_vm_to_glance')) + self.export_vm_to_glance(**kwargs) + + LOG.info(_("[VRM-CINDER] end export_attached_volume_to_image()")) + + def query_vm_volume(self, **kwargs): + '''query_vm_volume + + URL:https://192.168.106.111:8443/OmsPortal/service/sites/4286080F/vms + ?scope=urn%3Asites%3A4286080F%3Avolumes%3A137 + &detail=2 + ''' + + LOG.info(_("[VRM-CINDER] start query_vm_volume()")) + uri = '/vms' + method = 'GET' + path = self.site_uri + uri + parames = { + 'scope': kwargs.get('volume_urn'), + 'detail': 0 + } + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + vm = None + if total >= 1: + vms = body.get('vms') + vm = vms[0] + else: + LOG.info(_("[VRM-CINDER] find no vms()")) + LOG.info(_("[VRM-CINDER] end query_vm_volume()")) + return vm + + def migrate_vm_volume(self, **kwargs): + '''migrate_vm_volume + + Post ///action/migratevol HTTP/1.1 + Host: https://: + Content-Type: application/json; charset=UTF-8 + Accept: application/json;version=; charset=UTF-8 + X-Auth-Token: + { + "disks":[ + { + "volumeUrn":string + "datastoreUrn":string //urn:sites:1:datastores:1 + } + ], + "speed": integer + } + ''' + + LOG.info(_("[VRM-CINDER] start migrate_vm_volume()")) + uri = '/vms' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'vm_id') + '/action/migratevol' + new_url = self._generate_url(path) + + body = { + "disks": [ + { + "volumeUrn": kwargs.get('volume_urn'), + "datastoreUrn": kwargs.get('dest_ds_urn'), + 'migrateType': kwargs.get('migrate_type') + } + ], + "speed": kwargs.get('speed') + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + LOG.info(_("[VRM-CINDER] end migrate_vm_volume()")) + return body.get('urn') diff --git a/cinder/volume/drivers/huawei/fusioncompute/volume_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/volume_proxy.py new file mode 100644 index 0000000..7869dc2 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/volume_proxy.py @@ -0,0 +1,601 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import json + +from cinder import exception as cinder_exception +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy +from cinder.volume.drivers.huawei.vrm import exception as driver_exception +from cinder.volume.drivers.huawei.vrm.task_proxy import TaskProxy + +from oslo_log import log as logging + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) + + +class VolumeProxy(BaseProxy): + def __init__(self): + super(VolumeProxy, self).__init__() + self.task_proxy = TaskProxy() + + def filter_not_none_dict(self, src_dic): + obj_dic = {} + for index_dic in src_dic: + if src_dic.get(index_dic) is not None: + obj_dic.update({index_dic: src_dic.get(index_dic)}) + return obj_dic + + def query_volume(self, **kwargs): + '''query_volume + + 'query_volume': ('GET', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, + kwargs.get('id')), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope') + }, + {}, + False), + ''' + LOG.info(_("[VRM-CINDER] start query_volume()")) + uri = '/volumes' + method = 'GET' + path = self.site_uri + uri + '/' + kwargs.get('id') + + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + return body + + def query_volume_replications(self, **kwargs): + '''query_volume_replications + + 'query_volume_replications': ('GET', + ('/volumes/{volume_id}/action/replications', + kwargs.get(self.RESOURCE_URI), None, + kwargs.get('volume_id')), + False), + ''' + LOG.info(_("[VRM-CINDER] start query_volume_replications()")) + uri = '/volumes' + method = 'GET' + path = self.site_uri + uri + '/' + kwargs.get( + 'volume_id') + '/action/replications' + + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method) + return body + + def list_volumes(self, **kwargs): + '''list_volumes + + 'list_volumes': ('GET', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, + kwargs.get('id')), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope') + }, + {}, + False), + ''' + LOG.info(_("[VRM-CINDER] start query_volumesnapshot()")) + uri = '/volumes/compatibility/discovery ' + method = 'GET' + path = self.site_uri + uri + + offset = 0 + volumes = [] + volumes_map = {} # use map to clean repeat + while True: + parames = { + 'limit': self.limit, + 'offset': offset, + 'scope': kwargs.get('scope'), + 'uuid': kwargs.get('uuid') + } + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + + if total > 0: + res = body.get('volumes') + for volume in res: + volumes_map[volume.get('uuid')] = volume + volumes = volumes_map.values() + volumes += res + offset += len(res) + if offset >= total or len(volumes) >= total or len( + res) < self.limit: + break + if offset > 5: + offset -= 5 + else: + break + + for index_volume in volumes: + if index_volume.get( + 'customProperties') is not None and index_volume.get( + 'customProperties').get('external_uuid') is not None: + index_volume["uuid"] = index_volume["customProperties"][ + "external_uuid"] + return volumes + + def list_volumes_extend(self, **kwargs): + '''list_volumes_extend + + 'list_volumes_extend': ('GET', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, + kwargs.get('id')), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope') + }, + {}, + False), + ''' + LOG.info(_("[VRM-CINDER] start query_volumesnapshot()")) + uri = '/volumes/extend' + method = 'GET' + path = self.site_uri + uri + + offset = 0 + volumes = [] + volumes_map = {} # use map to clean repeat + while True: + parames = { + 'limit': self.limit, + 'offset': offset, + 'scope': kwargs.get('scope'), + 'uuid': kwargs.get('uuid') + } + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + if total > 0: + res = body.get('volumes') + for volume in res: + volumes_map[volume.get('uuid')] = volume + volumes = volumes_map.values() + volumes += res + offset += len(res) + if offset >= total or len(volumes) >= total or len( + res) < self.limit: + break + if offset > 5: + offset -= 5 + else: + break + + for index_volume in volumes: + if index_volume.get( + 'customProperties') is not None and index_volume.get( + 'customProperties').get('external_uuid') is not None: + index_volume["uuid"] = index_volume["customProperties"][ + "external_uuid"] + + return volumes + + def create_volume(self, **kwargs): + '''create_volume + + 'create_volume': ('POST', + ('/volumes', None, None, None), + {}, + {'name': kwargs.get('name'), + 'quantityGB': kwargs.get('quantityGB'), + 'datastoreUrn': kwargs.get('datastoreUrn'), + 'uuid': kwargs.get('uuid'), + 'isThin': kwargs.get('isThin'), + 'type': kwargs.get('type'), + 'indepDisk': kwargs.get('indepDisk'), + 'persistentDisk': + kwargs.get('persistentDisk'), + 'volumeId': kwargs.get('volumeId'), + 'snapshotUuid': kwargs.get('snapshotUuid'), + 'imageUrl': kwargs.get('imageUrl'), + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start create_volume()")) + uri = '/volumes' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + body = { + 'name': kwargs.get('name'), + 'quantityGB': kwargs.get('size'), + 'datastoreUrn': kwargs.get('ds_urn'), + 'uuid': kwargs.get('uuid'), + 'isThin': kwargs.get('is_thin'), + 'type': kwargs.get('type'), + 'indepDisk': kwargs.get('independent'), + 'customProperties': {"external_uuid": kwargs.get('uuid')} + } + support_pvscsi = kwargs.get('support_pvscsi', None) + if support_pvscsi is not None: + body.update({'pvscsiSupport': support_pvscsi}) + + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + return body + + def delete_volume(self, **kwargs): + '''delete_volume + + 'delete_volume': ('DELETE', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, + None), + {}, + {}, + True), + ''' + LOG.info(_("[VRM-CINDER] start delete_volume()")) + method = 'DELETE' + path = kwargs.get('volume_uri') + new_url = self._generate_url(path) + error_busy = ['10300057', '10420005', '10420009', '10410154', + '10420137', '10420138', '10430058'] + try: + resp, body = self.vrmhttpclient.request(new_url, method) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] delete volume (%s)"), ex.errorCode) + if ex.errorCode == "10420004": + return + elif ex.errorCode in error_busy: + LOG.info(_( + "[VRM-CINDER] volume status conflicts, try delete later.")) + raise cinder_exception.VolumeIsBusy(message=ex.errorCode) + else: + raise ex + + def clone_volume(self, **kwargs): + '''clone_volume + + 'clone_volume': ('POST', + ('/volumes', None, kwargs.get('src_name'),'action/copyVol'), + {}, + {'destinationVolumeID': + kwargs.get('dest_name') + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start clone_volume()")) + uri = '/volumes' + method = 'POST' + path = self.site_uri + uri + '/' + kwargs.get( + 'src_volume_id') + '/action/copyVol' + body = {'dstVolUrn': kwargs.get('dest_volume_urn')} + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + + def _copy_nfs_image_to_volume(self, **kwargs): + '''_copy_nfs_image_to_volume + + 'copy_image_to_volume': ('POST', + ('/volumes/imagetovolume', None, None, None), + {}, + { + 'volumePara': { + 'quantityGB': + kwargs.get('volume_size'), + 'urn': kwargs.get('volume_urn') + }, + 'imagePara': { + 'id': kwargs.get('image_id'), + 'url': kwargs.get('image_location') + }, + 'location': kwargs.get('cluster_urn'), + 'needCreateVolume': False + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start copy_image_to_volume()")) + uri = '/volumes/imagetovolume' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + body = { + 'volumePara': { + 'quantityGB': kwargs.get('volume_size'), + 'urn': kwargs.get('volume_urn') + }, + 'imagePara': { + 'id': kwargs.get('image_id'), + 'url': kwargs.get('image_location') + }, + 'location': kwargs.get('cluster_urn'), + 'needCreateVolume': False + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + + def _copy_volume_to_image(self, **kwargs): + '''_copy_volume_to_image + + 'copy_volume_to_image': ('POST', + ('/volumes/volumetoimage', None, None, None), + {}, + { + 'volumePara': {'urn': + kwargs.get('volume_urn'), + 'quantityGB': + kwargs.get('volume_size')}, + 'imagePara': { + 'id': kwargs.get( + 'image_id'), + 'url': kwargs.get( + 'image_url')} + }, + True), + ''' + + LOG.info(_("[VRM-CINDER] start stop_vm()")) + uri = '/volumes/volumetoimage' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + + body = { + 'volumePara': { + 'urn': kwargs.get('volume_urn'), + 'quantityGB': kwargs.get('volume_size')}, + 'imagePara': { + 'id': kwargs.get('image_id'), + 'url': kwargs.get('image_url')} + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + + def manage_existing(self, **kwargs): + '''manage_existing + + 'manage_existing': ('POST', + ('/volumes', None, None, None), + {}, + {'name': kwargs.get('name'), + 'quantityGB': kwargs.get('quantityGB'), + 'datastoreUrn': kwargs.get('datastoreUrn'), + 'uuid': kwargs.get('uuid'), + 'type': kwargs.get('type'), + 'indepDisk': kwargs.get('indepDisk'), + 'persistentDisk': + kwargs.get('persistentDisk'), + 'volumeId': kwargs.get('volumeId'), + 'snapshotUuid': kwargs.get('snapshotUuid'), + 'imageUrl': kwargs.get('imageUrl'), + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start create_volume()")) + uri = '/volumes/registevol' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + body = { + 'name': kwargs.get('name'), + 'quantityGB': kwargs.get('quantityGB'), + 'volInfoUrl': kwargs.get('volInfoUrl'), + 'uuid': kwargs.get('uuid'), + 'customProperties': {"external_uuid": kwargs.get('uuid')}, + 'type': kwargs.get('type'), + 'maxReadBytes': kwargs.get('maxReadBytes'), + 'maxWriteBytes': kwargs.get('maxWriteBytes'), + 'maxReadRequest': kwargs.get('maxReadRequest'), + 'maxWriteRequest': kwargs.get('maxWriteRequest')} + + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + return body + + def unmanage(self, **kwargs): + '''unmanage + + 'unmanage': ('DELETE', + ('/volumes?isOnlyDelDB=1', + kwargs.get(self.RESOURCE_URI), None, None), + {}, + {}, + True), + ''' + LOG.info(_("[VRM-CINDER] start unmanage()")) + method = 'DELETE' + path = kwargs.get('volume_uri') + '?isOnlyDelDB=1' + new_url = self._generate_url(path) + try: + resp, body = self.vrmhttpclient.request(new_url, method) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] unmanage volume (%s)"), ex.errorCode) + if ex.errorCode == "10420004": + return + else: + raise ex + + def migrate_volume(self, **kwargs): + '''migrate_volume + + Post //action/migratevol HTTP/1.1 + Host https://: + Accept application/json;version=; charset=UTF-8 + X-Auth-Token: + { + 'datastoreUrn':string, + 'speed': integer + } + ''' + LOG.info(_("[VRM-CINDER] start migrate_volume()")) + uri = '/volumes' + method = 'POST' + + path = self.site_uri + uri + '/' + kwargs.get( + 'volume_id') + '/action/migratevol' + body = {'datastoreUrn': kwargs.get('dest_ds_urn'), + 'speed': kwargs.get('speed'), + 'migrateType': kwargs.get('migrate_type')} + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + + def modify_volume(self, **kwargs): + LOG.info(_("[VRM-CINDER] start modify_volume()")) + uri = '/volumes' + method = 'PUT' + path = self.site_uri + uri + '/' + kwargs.get('volume_id') + body = {} + if kwargs.get('type') is not None: + body.update({'type': kwargs.get('type')}) + if kwargs.get('name') is not None: + body.update({'name': kwargs.get('name')}) + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + if resp.status_code not in (200, 204): + raise driver_exception.ClientException(101) + + def extend_volume(self, **kwargs): + '''extend_volume + + 'extend_volume': ('POST', + (kwargs.get('volume_uri'),'/action/expandVol', + None, None), + {}, + {}, + True), + ''' + LOG.info(_("[VRM-CINDER] start extend_volume()")) + method = 'POST' + body = {'size': kwargs.get('size')} + volume_uri = kwargs.get('volume_uri') + + path = volume_uri + '/action/expandVol' + new_url = self._generate_url(path) + + try: + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + + self.task_proxy.wait_task(task_uri=task_uri) + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] extend volume (%s)"), ex.errorCode) + raise ex + except Exception as ex: + LOG.info(_("[VRM-CINDER] extend volume (%s)"), ex) + raise ex + + def wait_task(self, **kwargs): + task_uri = kwargs.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + + def query_volume_by_custom_properties(self, **kwargs): + '''query_volume_by_custom_properties + + 'query_volume_by_custom_properties': ('POST', + ('/volumes/queryby/custom-properties', None, None, + None), + {}, + {"condition": {"name": "external_uuid", + "value": "93DF26A9-2D3B-43A6-A7D8-CF5E9D0DC17C" + }, + "offset": 0, + limit": 10 + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start query_volume_by_custom_properties()")) + uri = '/volumes/queryby/custom-properties' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + body = { + 'condition': { + 'name': 'external_uuid', + 'value': kwargs.get('external_uuid') + } + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + volumes = [] + total = int(body.get('total') or 0) + if total > 0: + res = body.get('volumes') + volumes += res + return volumes[0] + return None + + def update_custom_properties(self, **kwargs): + '''update_custom_properties + + 'update_by_custom_properties': ('POST', + ('/volumes/properties/custom', None, None, None), + {}, + {"volumes": [ + 'volumeUrn': kwargs.get('volumeUrn'), + 'customProperties': { + 'external_uuid': kwargs.get('external_uuid') + }] + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start modify_custom_properties()")) + uri = '/volumes/properties/custom' + method = 'POST' + path = self.site_uri + uri + new_url = self._generate_url(path) + body = { + 'volumes': [ + { + 'volumeUrn': kwargs.get('volume_urn'), + 'customProperties': { + 'external_uuid': kwargs.get('external_uuid') + } + } + ] + } + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + return body diff --git a/cinder/volume/drivers/huawei/fusioncompute/volume_snapshot_proxy.py b/cinder/volume/drivers/huawei/fusioncompute/volume_snapshot_proxy.py new file mode 100644 index 0000000..9858610 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/volume_snapshot_proxy.py @@ -0,0 +1,308 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +""" +[VRM DRIVER] VRM CLIENT. + +""" +import json + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder import exception as cinder_exception +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.base_proxy import BaseProxy +from cinder.volume.drivers.huawei.vrm import exception as driver_exception +from cinder.volume.drivers.huawei.vrm.task_proxy import TaskProxy +from cinder.volume.drivers.huawei.vrm.utils import Delete_Snapshot_Code + + +CONF = cfg.CONF +try: + from eventlet import sleep +except ImportError: + from time import sleep + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +LOG = logging.getLogger(__name__) + + +class VolumeSnapshotProxy(BaseProxy): + def __init__(self, *args, **kwargs): + super(VolumeSnapshotProxy, self).__init__() + LOG.info(_("[VRM-CINDER] start __init__()")) + self.task_proxy = TaskProxy() + self.create_retry = ['10000009', '10430056', '10430050', '10300421', + '10430057', '10300318', '10300162', '10300026'] + self.delete_retry = Delete_Snapshot_Code + self.number = CONF.vrm_vol_snapshot_retries + self.sleep_time = CONF.vrm_snapshot_sleeptime + if not self.number: + LOG.error('conf number is None, use 3') + self.number = 3 + + if not self.sleep_time: + LOG.error('conf sleeptime is None, use 300') + self.sleep_time = 300 + + def query_volumesnapshot(self, **kwargs): + '''query_volumesnapshot + + 'list_volumesnapshot': ('GET', + ('/volumesnapshots', None, kwargs.get('uuid'), + None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope') + }, + {}, + False), + ''' + LOG.info(_("[VRM-CINDER] start query_volumesnapshot()")) + uri = '/volumesnapshots' + method = 'GET' + path = self.site_uri + uri + '/' + kwargs.get('uuid') + new_url = self._generate_url(path) + try: + resp, body = self.vrmhttpclient.request(new_url, method) + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] query snapshot (%s)"), ex.errorCode) + if ex.errorCode == "10430051": + return None + else: + raise ex + + ''' + error_code = body.get('errorCode') + if error_code != None: + if '10430010' == error_code: + LOG.info(_("[VRM-CINDER] snapshot not exist")) + return None + ''' + + return body + + def list_snapshot(self, **kwargs): + '''list_snapshot + + 'list_volumesnapshot': ('GET', + ('/volumesnapshots', None, kwargs.get('uuid'), + None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope') + }, + {}, + False), + ''' + LOG.info(_("[VRM-CINDER] start list_snapshot()")) + uri = '/volumesnapshots/queryVolumeSnapshots' + method = 'GET' + path = self.site_uri + uri + body = None + offset = 0 + + snapshots = [] + while True: + parames = { + 'limit': self.limit, + 'offset': offset} + appendix = self._joined_params(parames) + new_url = self._generate_url(path, appendix) + resp, body = self.vrmhttpclient.request(new_url, method) + total = int(body.get('total') or 0) + if total > 0: + res = body.get('snapshots') + snapshots += res + offset += len(res) + if offset >= total or len(snapshots) >= total or len( + res) < self.limit: + break + else: + break + + return snapshots + + def create_volumesnapshot(self, **kwargs): + '''create_volumesnapshot + + 'create_volumesnapshot': ('POST', + ('/volumesnapshots', None, None, None), + {}, + {'volumeUrn': kwargs.get('vol_urn'), + 'snapshotUuid': kwargs.get('uuid'), + }, + False), + ''' + LOG.info(_("[VRM-CINDER] start create_volumesnapshot()")) + uri = '/volumesnapshots' + method = 'POST' + path = self.site_uri + uri + body = { + 'volumeUrn': kwargs.get('vol_urn'), + 'snapshotUuid': kwargs.get('snapshot_uuid') + } + enable_active = kwargs.get('enable_active', None) + if enable_active is not None: + body.update({'enableActive': enable_active}) + new_url = self._generate_url(path) + + number = self.number + sleep_time = self.sleep_time + while(number >= 0): + try: + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + if task_uri is not None: + self.task_proxy.wait_task(task_uri=task_uri) + return body + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] create volumesnapshot (%s)"), + ex.errorCode) + if ex.errorCode not in self.create_retry: + raise ex + LOG.debug( + '[VRM-CINDER] The errorcode is in retry list:%d' % number) + number -= 1 + if number < 0: + raise ex + sleep(sleep_time) + + def active_snapshots(self, **kwargs): + '''active_snapshots + + 'active_snapshots': ('POST', + ('/volumesnapshots/enableSnapshots', + None, None, None), + {}, + {'snapshotList': kwargs.get('snapshot_urns')}, + False), + ''' + LOG.info(_("[VRM-CINDER] start active_snapshots()")) + uri = '/volumesnapshots/enableSnapshots' + method = 'POST' + path = self.site_uri + uri + body = { + 'snapshotUuidList': kwargs.get('snapshot_uuids') + } + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + task_uri = body.get('taskUri') + if task_uri is not None: + self.task_proxy.wait_task(task_uri=task_uri) + return body + + def delete_volumesnapshot(self, **kwargs): + '''delete_volumesnapshot + + 'delete_volumesnapshot': ('DELETE', + ('/volumesnapshots', None, None, + kwargs.get('id')), + {}, + { + 'snapshotUuid': kwargs.get('snapshotUuid'), + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start delete_volumesnapshot()")) + uri = '/volumesnapshots' + method = 'DELETE' + path = self.site_uri + uri + '/' + kwargs.get('id') + body = { + 'volumeUrn': kwargs.get('vol_urn'), + 'snapshotUuid': kwargs.get('snapshot_uuid')} + new_url = self._generate_url(path) + + number = self.number + sleep_time = self.sleep_time + while(number >= 0): + try: + resp, body = self.vrmhttpclient.request(new_url, method) + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + break + except driver_exception.ClientException as ex: + LOG.info(_("[VRM-CINDER] delete volumesnapshot (%s)"), + ex.errorCode) + + normal_errorcode = ['10300026', '10300057', '10430052', + '10430054', '10430056', '10300421', + '10300318', '10300162', '10300153', + '10300170', '10000009', '10300421', + '10420138'] + if ex.errorCode in normal_errorcode: + LOG.error(_( + "[VRM-CINDER] snapshot status conflicts, " + "try delete later.")) + raise cinder_exception.SnapshotIsBusy( + snapshot_name=kwargs.get('snapshot_uuid')) + + if ex.errorCode not in self.delete_retry: + raise ex + LOG.info( + '[VRM-CINDER] The errorcode is in retry list, number:%d' % + number) + number -= 1 + if number < 0: + raise ex + sleep(sleep_time) + + def create_volume_from_snapshot(self, **kwargs): + '''create_volume_from_snapshot + + 'createvolumefromsnapshot': ('POST', + ('/volumesnapshots', None, "createvol", + None), + {}, + {'snapshotUuid': kwargs.get('uuid'), + 'volumeName': kwargs.get('name'), + 'volumeType': normal/share + 'volumeUuid': uuid + 'snapshotVolumeType': 0/1 + }, + True), + ''' + LOG.info(_("[VRM-CINDER] start createvolumefromsnapshot()")) + uri = '/volumesnapshots/createvol' + method = 'POST' + path = self.site_uri + uri + snapshotVolumeType = 0 + if str(kwargs.get('full_clone')) == '0': + snapshotVolumeType = 1 + body = { + 'snapshotUuid': kwargs.get('snapshot_uuid'), + 'volumeName': kwargs.get('volume_name'), + 'volumeType': kwargs.get('type'), + 'volumeUuid': kwargs.get('volume_uuid'), + 'snapshotVolumeType': snapshotVolumeType, + } + if kwargs.get('volume_size') is not None: + body.update({'volumeSize': kwargs.get('volume_size')}) + new_url = self._generate_url(path) + resp, body = self.vrmhttpclient.request(new_url, method, + body=json.dumps(body)) + + task_uri = body.get('taskUri') + self.task_proxy.wait_task(task_uri=task_uri) + return body diff --git a/cinder/volume/drivers/huawei/fusioncompute/vrm_commands.py b/cinder/volume/drivers/huawei/fusioncompute/vrm_commands.py new file mode 100644 index 0000000..e4b4ef5 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/vrm_commands.py @@ -0,0 +1,441 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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 json +import urlparse + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder import exception as cinder_exception +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.conf import FC_DRIVER_CONF +from cinder.volume.drivers.huawei.vrm import exception as driver_exception + +TASK_WAITING = 'waiting' +TASK_RUNNING = 'running' +TASK_SUCCESS = 'success' +TASK_FAILED = 'failed' +TASK_CANCELLING = 'cancelling' +TASK_UNKNOWN = 'unknown' + +CONF = cfg.CONF + + +LOG = logging.getLogger(__name__) + + +class VRM_COMMANDS(object): + USER_AGENT = 'VRM-HTTP-Client for OpenStack' + RESOURCE_URI = 'uri' + TASK_URI = 'taskUri' + BASIC_URI = '/service' + glanceServer_host = '' + glanceServer_port = '' + version = CONF.vrm_version + site_uri = '' + + def __init__(self): + self.site_urn = None + self.site_uri = None + + glanceServer_Info = str(CONF.glance_api_servers).split(':') + LOG.info("glance_api_servers is [%s]", CONF.glance_api_servers) + LOG.info("sxmatch glanceServer_Info is %s", glanceServer_Info) + self.glanceServer_host =\ + glanceServer_Info[0] + ":" + glanceServer_Info[1] + self.glanceServer_host = self.glanceServer_host.replace("['", '') + LOG.info("sxmatch glanceServer_host is %s", self.glanceServer_host) + self.glanceServer_port = glanceServer_Info[1].replace("']", '') + LOG.info("sxmatch glanceServer_port is %s", self.glanceServer_port) + + """Generate command to be sent to VRM.""" + + def _joined_params(self, params): + param_str = [] + for k, v in params.items(): + if (k is None) or (v is None) or len(k) == 0: + continue + if k == 'scope' and v == self.site_urn: + continue + param_str.append("%s=%s" % (k, str(v))) + return '&'.join(param_str) + + def _joined_body(self, params): + param_str = [] + for k, v in params.items(): + if k is None or v is None or \ + len(k) == 0: + continue + + if type(v) in [int]: + param_str.append('"%s":%s' % (k, str(v))) + elif type(v) in [str, unicode]: + param_str.append('"%s":"%s"' % (k, str(v))) + elif type(v) in [bool]: + param_str.append('"%s":%s' % (k, str(v).lower())) + elif type(v) in [dict]: + param_str1 = json.dumps(v) + param_str.append('"%s":%s' % (k, param_str1)) + else: + pass + + if len(param_str) > 0: + return '{' + ','.join(param_str) + '}' + else: + return None + + def _combine_vmConfig(self, **kwargs): + cpu_quantity = 1 + mem_quantityMB = 1024 + datastoreUrn = CONF.vrm_sm_datastoreurn + disk_quantityGB = 10 + image_size = 10 + + if image_size > disk_quantityGB: + LOG.error(_("image is larger than sys-vol.")) + raise cinder_exception.ImageTooLarge + + cpu = {'quantity': cpu_quantity} + memory = {'quantityMB': mem_quantityMB} + disks = [ + { + 'datastoreUrn': datastoreUrn, + 'quantityGB': disk_quantityGB, + 'volType': 0, + 'sequenceNum': 1, + } + ] + properties = { + 'isEnableHa': True, + 'reoverByHost': False, + 'isEnableFt': False + } + + vmConfigBody = { + 'cpu': cpu, + 'memory': memory, + 'disks': disks, + 'properties': properties, + } + return vmConfigBody + + def _combine_os_options(self, **kwargs): + osOptions = { + 'osType': 'Windows', + 'osVersion': 26 + } + return osOptions + + def init_site(self, uri, urn): + self.site_uri = uri + self.site_urn = urn + return + + def _generate_url(self, path, query=None, frag=None): + LOG.info(_("[BRM-DRIVER] call _generate_url() ")) + if CONF.vrm_ssl: + scheme = 'https' + else: + scheme = 'http' + fc_ip = FC_DRIVER_CONF.fc_ip + netloc = str(fc_ip) + ':' + str(CONF.vrm_port) + if path.startswith(self.BASIC_URI): + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + else: + url = urlparse.urlunsplit( + (scheme, netloc, self.BASIC_URI + str(path), query, frag)) + + return url + + def generate_vrm_cmd(self, cmd, **kwargs): + COMMANDS = { + 'v5.1': { + 'list_tasks': ('GET', + ('/tasks', kwargs.get(self.RESOURCE_URI), None, + None), + {}, + {}, + False), + 'list_hosts': ('GET', + ('/hosts', kwargs.get(self.RESOURCE_URI), None, + None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope')}, + {}, + False), + 'list_datastores': ('GET', + ('/datastores', + kwargs.get(self.RESOURCE_URI), None, + None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope')}, + {}, + False), + 'list_volumes': ('GET', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, kwargs.get('id')), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope')}, + {}, + False), + 'create_volume': ('POST', + ('/volumes', None, None, None), + {}, + {'name': kwargs.get('name'), + 'quantityGB': kwargs.get('quantityGB'), + 'datastoreUrn': kwargs.get('datastoreUrn'), + 'uuid': kwargs.get('uuid'), + 'isThin': kwargs.get('isThin'), + 'type': kwargs.get('type'), + 'indepDisk': kwargs.get('indepDisk'), + 'persistentDisk': kwargs.get( + 'persistentDisk'), + 'volumeId': kwargs.get('volumeId')}, + True), + 'delete_volume': ('DELETE', + ('/volumes', kwargs.get(self.RESOURCE_URI), + None, None), + {}, + {}, + True), + + 'list_volumesnapshot': ('GET', + ('/volumesnapshots', None, + kwargs.get('uuid'), None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope')}, + {}, + False), + 'create_volumesnapshot': ('POST', + ('/volumesnapshots', None, None, + None), + {}, + {'volumeUrn': kwargs.get('vol_urn'), + 'snapshotUuid': kwargs.get('uuid')}, + False), + 'delete_volumesnapshot': ('DELETE', + ('/volumesnapshots', None, None, + kwargs.get('id')), + {}, + { + 'snapshotUuid': kwargs.get( + 'snapshotUuid') + }, + True), + 'createvolumefromsnapshot': ('POST', + ('/volumesnapshots', None, + "createvol", None), + {}, + {'snapshotUuid': kwargs.get( + 'uuid'), + 'volumeName': + kwargs.get('name')}, + True), + 'clone_volume': ('POST', + ('/volumes', None, kwargs.get('src_name'), + 'action/copyVol'), + {}, + {'destinationVolumeID': kwargs.get( + 'dest_name')}, + True), + 'copy_image_to_volume': ('POST', + ('/volumes/imagetovolume', None, None, + None), + {}, + { + 'volumePara': { + 'quantityGB': kwargs.get( + 'volume_size'), + 'urn': kwargs.get( + 'volume_urn') + }, + 'imagePara': { + 'id': kwargs.get('image_id'), + 'url': kwargs.get( + 'image_location') + }, + 'location': kwargs.get( + 'host_urn'), + 'needCreateVolume': False + }, + True), + 'copy_volume_to_image': ('POST', + ('/volumes/volumetoimage', None, None, + None), + {}, + { + 'volumePara': {'urn': kwargs.get( + 'volume_urn'), + 'quantityGB': kwargs.get( + 'volume_size')}, + 'imagePara': { + 'id': kwargs.get('image_id'), + 'url': kwargs.get( + 'image_url')} + }, + True), + 'import_vm_from_image': ('POST', + ('/vms/action/import', None, None, + None), + {}, + dict({ + 'name': 'name', + 'location': kwargs.get( + 'host_urn'), + 'autoBoot': 'false', + 'url': kwargs.get('url'), + 'protocol': 'nfs', + 'vmConfig': { + 'cpu': { + 'quantity': 1 + }, + 'memory': { + 'quantityMB': 1024 + }, + 'disks': [ + { + 'pciType': 'IDE', + 'datastoreUrn': + kwargs.get( + 'ds_urn'), + 'quantityGB': + kwargs.get( + 'vol_size'), + 'volType': 0, + 'sequenceNum': 1}, + ] + }, + 'osOptions': { + 'osType': 'Windows', + 'osVersion': 32 + } + }), + True), + 'detach_vol_from_vm': ('POST', + ('/vms', None, kwargs.get('vm_id'), + 'action/detachvol'), + {}, + {'volUrn': kwargs.get('volUrn')}, + True), + 'stop_vm': ('POST', + ('/vms', None, kwargs.get('vm_id'), 'action/stop'), + {}, + {'mode': kwargs.get('mode')}, + True), + 'delete_vm': ('DELETE', + ('/vms', None, kwargs.get('vm_id'), None), + {}, + {}, + True), + 'query_vm': ('GET', + ('/vms', None, kwargs.get('vm_id'), None), + {}, + {}, + False), + 'list_templates': ('GET', + ('/vms', None, None, None), + {'limit': kwargs.get('limit'), + 'offset': kwargs.get('offset'), + 'scope': kwargs.get('scope'), + 'isTemplate': 'true'}, + {}, + False), + 'clone_vm': ('POST', + ('/vms', None, kwargs.get('template_id'), + 'action/clone'), + {}, + dict({ + "name": "cinder-plugin-temp-vm", + "description": "cinder-plugin-temp-vm", + "isLinkClone": kwargs.get('linked_clone'), + 'location': kwargs.get('host_urn'), + 'autoBoot': 'false', + 'vmConfig': + { + 'cpu': { + 'quantity': 2 + }, + 'memory': + { + 'quantityMB': 1024 + }, + 'disks': + [{ + 'pciType': 'IDE', + 'datastoreUrn': kwargs.get('ds_urn'), + 'quantityGB': kwargs.get( + 'volume_size'), + 'volType': 0, + 'sequenceNum': 1, + 'isThin': kwargs.get('is_thin') + }] + }, + }), + True), + }, + + 'v2.0': {} + } + + path = query = body = None + + LOG.info("[BRM-DRIVER] version is [%s]", self.version) + if self.version not in COMMANDS.keys(): + raise driver_exception.UnsupportedVersion() + else: + commands = COMMANDS[self.version] + + if cmd not in commands.keys(): + raise driver_exception.UnsupportedCommand() + else: + (method, pathparams, queryparams, bodyparams, hastask) = commands[ + cmd] + + resource, resource_uri, tag1, tag2 = pathparams + if resource_uri: + path = resource_uri + LOG.info(_("[VRM-CINDER] [%s]"), path) + else: + path = self.site_uri + resource + LOG.info(_("[VRM-CINDER] [%s]"), path) + if tag1: + path += ('/' + str(tag1)) + LOG.info(_("[VRM-CINDER] [%s]"), path) + if tag2: + path += ('/' + str(tag2)) + LOG.info(_("[VRM-CINDER] [%s]"), path) + + if method == 'GET': + query = self._joined_params(queryparams) + elif method == 'DELETE': + query = self._joined_params(queryparams) + elif method == 'POST': + query = self._joined_params(queryparams) + LOG.info("[BRM-DRIVER] _generate_vrm_cmd bodyparams is [%s]", + bodyparams) + body = json.dumps(bodyparams) + LOG.info("[BRM-DRIVER] _generate_vrm_cmd body is [%s]", body) + else: + raise cinder_exception.UnknownCmd(cmd=method) + + url = self._generate_url(path, query) + LOG.info("[BRM-DRIVER] _generate_vrm_cmd url is [%s]", url) + + return (method, url, body, hastask) diff --git a/cinder/volume/drivers/huawei/fusioncompute/vrm_driver.py b/cinder/volume/drivers/huawei/fusioncompute/vrm_driver.py new file mode 100644 index 0000000..7e8daab --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/vrm_driver.py @@ -0,0 +1,2355 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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 json +import math +import os +import random + +from cinder import context as cinder_context +from cinder import exception +from cinder.i18n import _ +from cinder import volume +from cinder.volume import driver +from cinder.volume.drivers.huawei.vrm.cluster_proxy import ClusterProxy +from cinder.volume.drivers.huawei.vrm.conf import FC_DRIVER_CONF +from cinder.volume.drivers.huawei.vrm.datastore_proxy import DatastoreProxy +from cinder.volume.drivers.huawei.vrm import exception as driver_exception +from cinder.volume.drivers.huawei.vrm.host_proxy import HostProxy +from cinder.volume.drivers.huawei.vrm.http_client import VRMHTTPClient +from cinder.volume.drivers.huawei.vrm.vm_proxy import VmProxy +from cinder.volume.drivers.huawei.vrm.volume_proxy import VolumeProxy +from cinder.volume.drivers.huawei.vrm.volume_snapshot_proxy import \ + VolumeSnapshotProxy +from cinder.volume import utils as volume_utils + +from oslo_config import cfg +from oslo_log import log as logging + +backend_opts = [ + cfg.StrOpt('volume_driver', + default='cinder.volume.drivers.huawei.vrm.vrm_driver.VRMDriver', + help='Driver to use for volume creation'), +] + + +def metadata_to_dict(metadata): + result = {} + for item in metadata: + if not item.get('deleted'): + result[item['key']] = item['value'] + return result + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +fc_plugin_conf = [ + cfg.StrOpt('vrm_config_file', + default=None, + help='vrm_config_file'), + cfg.BoolOpt('vrm_thin_provision', + default=False, + help='Switch of thin provisioning support'), + cfg.FloatOpt('vrm_over_ratio', + default=1.0, + help='Ratio of thin provisioning'), + cfg.IntOpt('vrm_reserved_percentage', + default=0, + help='Reserved percentage of the backend volumes'), + cfg.ListOpt('vrm_ds_name', + default=[], + help='vrm_ds_name'), + cfg.ListOpt('current_list', + default=[], + help='current_list'), + cfg.IntOpt('affine_rate', + default=1, + help='affine_rate'), +] + + +class VRMDriver(driver.VolumeDriver): + VENDOR = 'Huawei' + BACKEND = 'VRM' + VERSION = 'v1.1' + + def __init__(self, *args, **kwargs): + '''__init__ + + __init__ + + :param args: + :param kwargs: + :return: + ''' + super(VRMDriver, self).__init__(*args, **kwargs) + LOG.info(_("[VRM-CINDER] start VRMDriver __init__()")) + + self.context = None + self.volume_api = volume.API() + self.SHARED_HOSTS = [] + self.SHARED_DATASTORES = [] + self.SHARED_VOLUMES = [] + + self.LAST_SHARED_HOSTS = self.SHARED_HOSTS + self.LAST_SHARED_DATASTORES = self.SHARED_DATASTORES + self.LAST_SHARED_VOLUMES = self.SHARED_VOLUMES + self.left_periodrate = CONF.vrm_sm_periodrate + + if self.configuration: + LOG.info(_("[VRM-CINDER] append configuration")) + self.configuration.append_config_values(fc_plugin_conf) + else: + LOG.info(_("[VRM-CINDER] no configuration exception")) + raise driver_exception.NoNeededData + + over_ratio = self.configuration.get('vrm_over_ratio') + if over_ratio is None: + self.over_ratio = 1.0 + else: + LOG.info(_("[VRM-CINDER] super_ratio [%s]"), over_ratio) + self.over_ratio = over_ratio + + thin_provision = self.configuration.vrm_thin_provision + if thin_provision is not None: + self.thin_provision = False + else: + LOG.info(_("[VRM-CINDER] thin_provision [%s]"), thin_provision) + self.thin_provision = bool(thin_provision) + + reserved_percentage = self.configuration.get('vrm_reserved_percentage') + if reserved_percentage is None: + self.reserved_percentage = 0 + else: + LOG.info(_("[VRM-CINDER] reserved_percentage [%s]"), + reserved_percentage) + self.reserved_percentage = int(reserved_percentage) + + self.pool_list = self.configuration.get('vrm_ds_name') + if not self.pool_list: + LOG.error(_("[VRM-CINDER] vrm_ds_name is None exception")) + raise driver_exception.NoNeededData + self.pool_list = list(set(self.pool_list)) + + self.current_list = self.configuration.get('current_list') + self.affine_rate = self.configuration.get('affine_rate') + + self.volume_proxy = VolumeProxy() + self.vm_proxy = VmProxy() + self.volume_snapshot_proxy = VolumeSnapshotProxy() + self.host_proxy = HostProxy() + self.cluster_proxy = ClusterProxy() + self.datastore_proxy = DatastoreProxy() + self.vrmhttpclient = VRMHTTPClient() + + self.site_urn = None + self.site_uri = None + self.cluster_urn = None + + self.shared_hosts = [] + self.shared_datastores = [] + self.shared_volumes = [] + self.auth_token = None + self.log_count = 0 + + LOG.info(_("[VRM-CINDER] end __init__()")) + + def _get_host_datastore_vol(self): + '''_get_host_datastore_vol + + get_host_datastore_vol + + :return: + ''' + self.shared_hosts = [] + self.shared_datastores = [] + self.shared_volumes = [] + + self.shared_hosts = self.host_proxy.list_host() + hosturns = sorted([host['urn'] for host in self.shared_hosts]) + hosturns_set = set(hosturns) + + datastores = self.datastore_proxy.list_datastore() + sharetypes = [t.lower() for t in FC_DRIVER_CONF.vrm_ds_types] + for datastore in datastores: + storage_type = datastore['storageType'].lower() + ds_name = datastore['name'] + if self.pool_list is not None: + if ds_name in self.pool_list: + # LOG.info(_("[VRM-CINDER] get the vrm_sm_datastore_name")) + self.shared_datastores.append(datastore) + else: + # LOG.info(_("vrm_sm_datastore_name useless")) + continue + + if storage_type in sharetypes: + hosts = sorted(datastore['hosts']) + hosts_set = set(hosts) + + # LOG.info(_("hosts_set %s") % hosts_set) + if FC_DRIVER_CONF.vrm_ds_hosts_share: + if len(hosturns_set - hosts_set) == 0: + # LOG.info(_("[VRM-CINDER] append ds share")) + self.shared_datastores.append(datastore) + else: + if len(hosturns_set & hosts_set) > 0: + LOG.info(_("[VRM-CINDER] append ds")) + self.shared_datastores.append(datastore) + + if len(self.shared_datastores) <= 0: + LOG.info(_("[VRM-CINDER] can not found any shared datastores ")) + raise driver_exception.NotFound() + +# LOG.info(_("[VRM-CINDER] end get_host_datastore_vol()")) + return ( + self.shared_hosts, self.shared_datastores, self.shared_volumes) + + def _refresh_storage_info(self, refresh=False): + '''_refresh_storage_info + + _refresh_storage_info + + :param refresh: + :return: + ''' +# LOG.info(_("[BRM-DRIVER] start _refresh_storage_info(%s) "), refresh) + if refresh is True: + self.LAST_SHARED_HOSTS = self.SHARED_HOSTS + self.LAST_SHARED_DATASTORES = self.SHARED_DATASTORES + self.LAST_SHARED_VOLUMES = self.SHARED_VOLUMES + + self.SHARED_HOSTS, self.SHARED_DATASTORES, self.SHARED_VOLUMES = \ + self._get_host_datastore_vol() + self.left_periodrate = CONF.vrm_sm_periodrate + + self.log_count = self.log_count + 1 + if self.log_count >= 20: + LOG.info(_("[CINDER-BRM] refreshed shared hosts :[ %s ]"), + self.SHARED_HOSTS) + LOG.info(_("[CINDER-BRM] refreshed shared datastores :[ %s ]"), + self.SHARED_DATASTORES) + self.log_count = 0 +# LOG.info(_("[BRM-DRIVER] end _refresh_storage_info(%s) "), refresh) + + def do_setup(self, context): + '''do_setup + + do_setup + + :param context: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start do_setup() ")) + LOG.info(_("[VRM-CINDER] del environ http_proxy https_proxy")) + if os.getenv('http_proxy'): + LOG.info(_("[VRM-CINDER] del environ http_proxy")) + del os.environ['http_proxy'] + if os.getenv('https_proxy'): + LOG.info(_("[VRM-CINDER] del environ https_proxy")) + del os.environ['https_proxy'] + self.context = context + self.vrmhttpclient.init() + self.site_urn = self.vrmhttpclient.get_siteurn() + self._refresh_storage_info(True) + clusters = self.cluster_proxy.list_cluster() + self.cluster_urn = clusters[0].get('urn') + LOG.info(_("[CINDER-BRM] end do_setup")) + + def check_for_setup_error(self): + '''check_for_setup_error + + check_for_setup_error + + :return: + ''' + # LOG.info(_("[BRM-DRIVER] start check_for_setup_error() ")) + if len(self.SHARED_HOSTS) == 0 or len(self.SHARED_DATASTORES) == 0: + LOG.info(_( + "[CINDER-BRM] check_for_setup_error, " + "shared datasotre not found")) + raise driver_exception.NoNeededData + + def _build_volume_stats(self): + '''_build_volume_stats + + ''' + # LOG.info(_("[BRM-DRIVER] start _build_volume_stats() ")) + stats = {} + stats["pools"] = [] + stats['driver_version'] = self.VERSION + stats['storage_protocol'] = 'VRM' + stats['vendor_name'] = self.VENDOR + + backend = self.configuration.get('volume_backend_name') + if backend is None: + stats['volume_backend_name'] = self.BACKEND + else: + stats['volume_backend_name'] = self.configuration.get( + 'volume_backend_name') + + return stats + + def _try_get_volume_stats(self, refresh=False): + '''_try_get_volume_stats + + _try_get_volume_stats + + :param refresh:If 'refresh' is True, run the update first. + :return:Return the current state of the volume service. + ''' +# LOG.info(_("[BRM-DRIVER] start _try_get_volume_stats() ")) + if refresh: + self.left_periodrate -= 1 + if self.left_periodrate <= 0: + self._refresh_storage_info(refresh) + + stats = self._build_volume_stats() + ds_meta = {} + ds_names = [ds['name'] for ds in self.SHARED_DATASTORES] + for pool in self.pool_list: + if pool not in ds_names: + continue + new_pool = {} + ds_meta['ds_name'] = pool + datastore = self._choose_datastore(ds_meta) + if datastore.get('storageType') == 'advanceSan' and datastore.get( + 'version') is not None: + new_pool.update(dict(consistencygroup_support=True)) + if 'NORMAL' != datastore['status']: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb=0, + reserved_percentage=self.reserved_percentage, + total_capacity_gb=0, + provisioned_capacity_gb=0, + max_over_subscription_ratio=self.over_ratio, + affine_rate=1 + )) + stats["pools"].append(new_pool) + continue + + if self.current_list is not None and pool in self.current_list: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb=datastore['freeSizeGB'], + reserved_percentage=self.reserved_percentage, + total_capacity_gb=datastore['capacityGB'], + provisioned_capacity_gb=datastore['usedSizeGB'], + max_over_subscription_ratio=self.over_ratio, + affine_rate=self.affine_rate + )) + else: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb=datastore['freeSizeGB'], + reserved_percentage=self.reserved_percentage, + total_capacity_gb=datastore['capacityGB'], + provisioned_capacity_gb=datastore['usedSizeGB'], + max_over_subscription_ratio=self.over_ratio, + affine_rate=1 + )) + if self.thin_provision is True: + new_pool.update(dict( + thin_provisioning_support=True, + thick_provisioning_support=False + )) + else: + new_pool.update(dict( + thin_provisioning_support=False, + thick_provisioning_support=True + )) + tier_size = datastore.get('tierSize', None) + type_v3 = [] + if tier_size and len(tier_size) >= 3: + if tier_size[0] > 0: + type_v3.append('ssd') + if tier_size[1] > 0: + type_v3.append('sas') + if tier_size[2] > 0: + type_v3.append('nl_sas') + if len(type_v3) > 0: + type_v3_str = ';'.join(type_v3) + LOG.info(_("[CINDER-BRM] type of v3 is %s"), type_v3_str) + new_pool.update(dict(type=type_v3_str)) + stats["pools"].append(new_pool) + + # LOG.info(_("[CINDER-BRM] (%d)--%s"), self.left_periodrate, stats) + + return stats + + def get_volume_stats(self, refresh=False): + '''get_volume_stats + + get_volume_stats + + :param refresh:If 'refresh' is True, run the update first. + :return:Return the current state of the volume service. + ''' +# LOG.info(_("[BRM-DRIVER] start get_volume_stats() ")) + try: + stats = self._try_get_volume_stats(refresh) + except Exception as ex: + LOG.info(_("[CINDER-BRM] get volume stats Exception (%s)"), ex) + stats = self._build_volume_stats() + return stats + + def check_and_modify_thin(self, ds_urn, thin): + '''check_and_modify_thin + + [ DSWARE] /[LOCAL, SAN, LUN] + :param ds_urn: + :param thin: + :return: + ''' + LOG.info(_("[CINDER-BRM] start check_and_modify_thin (%s)"), ds_urn) + for datastore in self.SHARED_DATASTORES: + # LOG.info(_("[CINDER-BRM] ds_urn (%s)"), ds_urn) + LOG.info(_("[CINDER-BRM] datastore (%s)"), datastore['urn']) + if datastore['urn'] == ds_urn: + ds_type = str(datastore['storageType']).upper() + LOG.info(_("[CINDER-BRM] ds_type (%s)"), ds_type) + if ds_type in ['LOCAL', 'SAN', 'LUN']: + LOG.info(_("[CINDER-BRM] return False (%s)"), ds_urn) + return False + if ds_type in ['DSWARE']: + LOG.info(_("[CINDER-BRM] return True (%s)"), ds_urn) + return True + return thin + + def check_thin(self, datastore, thin): + '''check_thin + + [ DSWARE] /[LOCAL, SAN, LUN] + :param ds_urn: + :param thin: + :return: + ''' + # LOG.info(_("[CINDER-BRM] start check_thin (%s)"), datastore) + ds_type = str(datastore['storageType']).upper() + # LOG.info(_("[CINDER-BRM] ds_type (%s)"), ds_type) + if ds_type in ['LOCAL', 'SAN', 'LUN']: + # LOG.info(_("[CINDER-BRM] return False (%s)"), datastore) + return False + if ds_type in ['DSWARE']: + # LOG.info(_("[CINDER-BRM] return True (%s)"), datastore) + return True + return thin + + def _check_and_choice_datastore(self, ds_meta): + '''_check_and_choice_datastore + + _check_and_choice_datastore + + :param ds_meta: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _check_and_choice_datastore() ")) + datastoreUrn = ds_meta['datastoreUrn'] + hypervisorIp = ds_meta['hypervisorIp'] + quantityGB = ds_meta['quantityGB'] + storageType = ds_meta['storageType'] + isThin = ds_meta['isThin'] + hypervisorUrn = None + if hypervisorIp: + for host in self.SHARED_HOSTS: + if host['ip'].strip() == hypervisorIp.strip(): + hypervisorUrn = host['urn'] + break + if hypervisorUrn is None: + LOG.info(_("[CINDER-BRM] can not found hypervisorip=%s"), + hypervisorIp) + raise exception.HostNotFound(host=hypervisorIp) + + if datastoreUrn: + for datastore in self.SHARED_DATASTORES: + if datastore['urn'] == datastoreUrn: + this_storageType = datastore['storageType'] + this_isThin = datastore['isThin'] + this_freeSizeGB = int(datastore['freeSizeGB']) + this_hosts = datastore['hosts'] + + if this_storageType.lower() == storageType.lower() and str( + this_isThin).lower() == str( + isThin).lower() and this_freeSizeGB > quantityGB: + if hypervisorUrn is None: + return datastore['urn'] + elif hypervisorUrn in this_hosts: + return datastore['urn'] + raise driver_exception.NotFound() + raise driver_exception.NotFound() + ds_hosts = None + ds_urn = None + random.shuffle(self.SHARED_DATASTORES) + for datastore in self.SHARED_DATASTORES: + + this_isThin = datastore['isThin'] + this_freeSizeGB = int(datastore['freeSizeGB']) + ds_hosts = datastore['hosts'] + if this_freeSizeGB < quantityGB: + continue + if isThin is None: + ds_urn = datastore['urn'] + break + + elif str(this_isThin).lower() == str(isThin).lower(): + ds_urn = datastore['urn'] + break + + if ds_urn is None: + raise driver_exception.NotFound() + random.shuffle(ds_hosts) + host_urn = ds_hosts[0] + return ds_urn, host_urn + + def _choose_datastore(self, ds_meta): + '''_choose_datastore + + _check_and_choice_datastore + + :param ds_meta: + :return: + ''' + for datastore in self.SHARED_DATASTORES: + if ds_meta['ds_name'] == datastore['name']: + return datastore + + raise driver_exception.NotFound() + + def _vrm_pack_provider_location(self, volume_body): + '''_vrm_pack_provider_location + + _vrm_pack_provider_location + + :param volume: + :param volume_body: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _vrm_pack_provider_location() ")) + fc_ip = FC_DRIVER_CONF.fc_ip + + provider_location = "" + provider_location += ('addr=' + fc_ip + ':' + str(CONF.vrm_port) + ',') + provider_location += ('uri=' + volume_body.get('uri') + ',') + provider_location += ('urn=' + volume_body.get('urn') + ',') + provider_location += \ + ('datastoreUrn=' + volume_body.get('datastoreUrn') + ',') + provider_location += ('isThin=' + str(volume_body.get('isThin')) + ',') + provider_location += \ + ('storageType=' + volume_body.get('storageType') + ',') + provider_location += ('type=' + volume_body.get('type')) + + return provider_location + + def _vrm_unpack_provider_location(self, provider_location, key=None): + '''_vrm_unpack_provider_location + + :param provider_location: + :param key: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _vrm_unpack_provider_location() ")) + kvalue = None + kvs = {} + if not isinstance(provider_location, None) and len( + provider_location) > 0: + items = provider_location.split(',') + for item in items: + (ki, eqi, vi) = item.partition('=') + kvs[ki] = vi + if key and key == ki: + kvalue = vi + + return kvalue, kvs + + def _vrm_get_volume_meta(self, id): + '''_vrm_create_volume + + :param id: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _vrm_get_volume_meta() ")) + model_update = {} + metadata = {} + + if id: + volume_body = self.volume_proxy.query_volume(id=id) + if volume_body is not None: + model_update[ + 'provider_location'] = self._vrm_pack_provider_location( + volume_body) + sd_sn = volume_body.get('storageSN', None) + lun_id = volume_body.get('lunId', None) + replication_driver_data = {} + if lun_id: + LOG.info(_("[BRM-DRIVER] lun id is %s"), lun_id) + replication_driver_data.update({'lun_id': lun_id}) + if sd_sn: + LOG.info(_("[BRM-DRIVER] sn is %s"), sd_sn) + replication_driver_data.update({'sn': sd_sn}) + if len(replication_driver_data) > 0: + LOG.info(_("[BRM-DRIVER] replication_driver_data is %s"), + sd_sn) + model_update['replication_driver_data'] = json.dumps( + replication_driver_data) + urn = volume_body['urn'] + uri = volume_body['uri'] + storage_type = volume_body.get('storageType') + metadata.update({'urn': urn}) + metadata.update({'uri': uri}) + if storage_type is not None: + LOG.info( + _("[BRM-DRIVER] the storage type of volume is %s"), + storage_type) + storage_type = 'FC_' + storage_type + metadata.update({'StorageType': storage_type}) + volInfoUrl = volume_body.get('volInfoUrl', None) + if volInfoUrl: + metadata.update({'quantityGB': volume_body['quantityGB']}) + metadata.update({'volInfoUrl': volInfoUrl}) + pvscsi_support = volume_body.get('pvscsiSupport') + if pvscsi_support == 1: + metadata.update({'hw:passthrough': 'true'}) + + return model_update, metadata + + def create_consistencygroup(self, context, group): + LOG.info(_("[BRM-DRIVER] create consistencygroup. ")) + return + + def update_consistencygroup(self, context, group, + add_volumes=None, remove_volumes=None): + """Updates a consistency group.""" + return None, None, None + + def _vrm_delete_volume(self, volume): + '''_vrm_delete_volume + + :param volume: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _vrm_delete_volume() ")) + provider_location = volume['provider_location'] + if provider_location is None or provider_location == '': + LOG.error(_("[BRM-DRIVER]provider_location is null ")) + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + vol_uri = vol_meta_dict.get('uri') + if vol_uri is not None: + fc_vol_urn = vol_meta_dict.get('urn') + fc_vol_id = fc_vol_urn[fc_vol_urn.rfind(':') + 1:] + self._check_replications(fc_vol_id) + self._vrm_delete_volume_vm(volume_urn=fc_vol_urn, + volume_uri=vol_uri) + return + fc_volume_urn, items = \ + self._vrm_unpack_provider_location(volume['provider_location'], + 'urn') + fc_vol_id = fc_volume_urn[fc_volume_urn.rfind(':') + 1:] + self._check_replications(fc_vol_id) + volume_uri, items = \ + self._vrm_unpack_provider_location(volume['provider_location'], + 'uri') + self._vrm_delete_volume_vm(volume_urn=fc_volume_urn, + volume_uri=volume_uri) + + def _vrm_delete_volume_vm(self, volume_urn, volume_uri): + '''_vrm_delete_volume_vm + + :param volume_urn: + :param volume_uri: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _vrm_delete_volume_vm() ")) + kwargs = {} + kwargs['volume_urn'] = volume_urn + vm_body = self.vm_proxy.query_vm_volume(**kwargs) + if vm_body is None: + LOG.info(_("[BRM-DRIVER]vm attached is zero ")) + self.volume_proxy.delete_volume(volume_uri=volume_uri) + else: + vm_uri = vm_body.get('uri') + vm_status = vm_body.get('status') + kwargs['vm_id'] = vm_uri[-10:] + kwargs['mode'] = 'force' + if vm_status not in ['stopped']: + self.vm_proxy.stop_vm(**kwargs) + self.vm_proxy.detach_vol_from_vm(**kwargs) + self.volume_proxy.delete_volume(volume_uri=volume_uri) + + def _check_quick_start(self, volume_id): + '''_check_quick_start + + :param volume_id: + :return: + ''' + admin_context = cinder_context.get_admin_context() + admin_metadata = self.db.volume_admin_metadata_get(admin_context, + volume_id) + quick_start = admin_metadata.get('quick_start') + if quick_start is not None and str(quick_start).lower() == 'true': + return True + else: + return False + + def _check_replications(self, fc_vol_id): + + if fc_vol_id: + try: + body = self.volume_proxy.query_volume_replications( + volume_id=fc_vol_id) + LOG.info( + _("[BRM-DRIVER] check replications : replications is %s"), + body.get('replications')) + except Exception as ex: + LOG.error(_("[BRM-DRIVER] get volume replications expection")) + LOG.exception(ex) + return + if body.get('replications'): + LOG.error(_( + "[BRM-DRIVER] The volume %s to deleted has replications, " + "could not be deleted ,extended or migrated."), fc_vol_id) + raise driver_exception.FusionComputeDriverException() + + def delete_consistencygroup(self, context, group): + + LOG.info(_("[BRM-DRIVER] enter delete_consistencygroup() ")) + model_update = {} + model_update['status'] = 'deleted' + volumes = self.db.volume_get_all_by_group(context, group['id']) + if volumes: + for volume_ref in volumes: + try: + self.delete_volume(volume_ref) + volume_ref['status'] = 'deleted' + except Exception as ex: + LOG.error( + _("[BRM-DRIVER] delete_consistencygroup failed. ")) + LOG.exception(ex) + volume_ref['status'] = 'error_deleting' + model_update['status'] = 'error_deleting' + + LOG.info(_("[BRM-DRIVER] end delete_consistencygroup() ")) + + return model_update, volumes + + def create_volume(self, volume): + '''create_volume + + create_volume + { + "name":string, + quantityGB:integer, + datastoreUrn:string, + "isThin":boolean, + "type":string, + indepDisk:boolean, + persistentDisk:boolean + } + + :param volume: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start create_volume() ")) + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + linked_clone = vol_meta_dict.get('linked_clone') + if linked_clone is None: + linked_clone = False + elif str(linked_clone).lower() == 'true': + linked_clone = True + else: + linked_clone = False + + # check quick start + if not linked_clone and self._check_quick_start(volume.get('id')): + linked_clone = True + + if linked_clone: + LOG.info(_("[BRM-DRIVER] linked_clone volume. do nothing. ")) + return + + args_dict = {} + + args_dict['name'] = volume['name'] + args_dict['size'] = int(volume['size']) + args_dict['uuid'] = volume['id'] + shareable = volume.get('shareable') + if shareable and True == shareable: + LOG.info(_("[CINDER-VRM] shareable")) + args_dict['type'] = 'share' + + else: + args_dict['type'] = 'normal' + + is_thin = FC_DRIVER_CONF.vrm_is_thin + + ds_meta = {} + try: + ds_meta['ds_name'] = volume.get('host').split('#')[1] + except exception.CinderException as ex: + LOG.info( + _( + "[CINDER-BRM] host format exception, host is %s ") % + volume.get('host')) + raise ex + + datastore = self._choose_datastore(ds_meta) + if datastore: + LOG.info(_("[CINDER-VRM] datastore [%s],"), datastore) + if str(datastore.get('storageType')).upper() in ['LUN']: + LOG.info(_("[CINDER-VRM] rdm disk [%s]"), volume['id']) + args_dict['size'] = int(datastore.get('capacityGB')) + args_dict['independent'] = True + + args_dict['ds_urn'] = datastore.get('urn') + is_thin = self.check_thin(datastore, is_thin) + args_dict['is_thin'] = is_thin + + context = cinder_context.get_admin_context() + + volume_type_id = volume.get('volume_type_id') + if volume_type_id: + LOG.info(_("[BRM-DRIVER] query volume type id is %s"), + volume_type_id) + volume_type_extra_specs = self.db.volume_type_extra_specs_get( + context, volume_type_id) + LOG.info(_("[BRM-DRIVER] volume_type_extra_specs is %s"), + str(volume_type_extra_specs)) + if volume_type_extra_specs and str(volume_type_extra_specs.get( + 'hw:passthrough')).lower() == 'true': + args_dict[ + 'support_pvscsi'] = 1 + support_pvscsi = vol_meta_dict.get('hw:passthrough') + if support_pvscsi and str(support_pvscsi).lower() == 'true': + args_dict[ + 'support_pvscsi'] = 1 + + body = self.volume_proxy.create_volume(**args_dict) + + temp_str = body.get('urn') + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + LOG.info(_("[CINDER-VRM] fc_vol_id [%s] ") % fc_vol_id) + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + self.db.volume_metadata_update(context, volume['id'], metadata, + False) + + self.volume_proxy.wait_task(**body) + + try: + model_update, metadata = self._vrm_get_volume_meta( + id=fc_vol_id) + except Exception as ex: + LOG.error( + _( + "[CINDER-VRM] get volume information failed after " + "create volume.volume id is [%s] ") % fc_vol_id) + LOG.exception(ex) + return model_update + else: + raise exception.VolumeDriverException + + def _register_volume(self, volume): + '''_register_volume + + _register_volume + + :param volume: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start _register_volume() ")) + volume_metadata = volume['volume_metadata'] + volume_metadata_dict = metadata_to_dict(volume_metadata) + model_update = {} + + volume_urn = volume_metadata_dict.get('volumeUrn') + volume_id = volume_urn.split(':')[-1] + volume_body = self.volume_proxy.get_volume(vol_id=volume_id) + model_update['provider_location'] = self._vrm_pack_provider_location( + volume_body) + model_update['volume_urn'] = volume_body['urn'] + return model_update + + def delete_volume(self, volume): + '''delete_volume + + delete_volume + + :param volume: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start delete_volume() ")) + self._vrm_delete_volume(volume) + + def create_export(self, context, volume): + '''create_export + + create_export + + :param context: + :param volume: + :return: + ''' + pass + + def remove_export(self, context, volume): + '''remove_export + + remove_export + + :param context: + :param volume: + :return: + ''' + pass + + def ensure_export(self, context, volume): + '''ensure_export + + ensure_export + + :param context: + :param volume: + :return: + ''' + pass + + def check_for_export(self, context, volume_id): + '''check_for_export + + check_for_export + + :param context: + :param volume_id: + :return: + ''' + pass + + def create_snapshot(self, snapshot): + '''create_snapshot + + create_snapshot + + :param snapshot: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start create_snapshot() ")) + model_update = {} + volume = self.db.volume_get(self.context, snapshot['volume_id']) + vol_meta = volume['volume_metadata'] + vol_meta_dict = metadata_to_dict(vol_meta) + vol_urn = vol_meta_dict.get('urn') + if vol_urn is None: + LOG.error(_("vol_urn is null.")) + + def volume_uri_to_number(uri): + hi, si, ti = uri.rpartition('/') + return ti + + snapshot_id = snapshot['id'] + snapshot_uuid = str(snapshot_id).replace('-', '') + body = self.volume_snapshot_proxy.create_volumesnapshot( + snapshot_uuid=snapshot_uuid, vol_urn=vol_urn) + + if body['urn'] is None: + LOG.error(_("Trying to create snapshot failed, volume id is: %s"), + snapshot['volume_id']) + raise driver_exception.FusionComputeDriverException() + + return model_update + + def delete_snapshot(self, snapshot): + '''delete_snapshot + + delete_snapshot + + :param snapshot: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start delete_snapshot() ")) + model_update = {} + snapshot_id = snapshot['id'] + snapshot_uuid = str(snapshot_id).replace('-', '') + body = self.volume_snapshot_proxy.query_volumesnapshot( + uuid=snapshot_uuid) + if body is None: + return model_update + self.volume_snapshot_proxy.delete_volumesnapshot(id=snapshot_uuid) + + return model_update + + def create_volume_from_snapshot(self, volume, snapshot): + '''create_volume_from_snapshot + + :param volume: + :param snapshot: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start create_volume_from_snapshot()")) + args_dict = {} + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + linked_clone = vol_meta_dict.get('linked_clone') + if linked_clone is None: + linked_clone = False + elif str(linked_clone).lower() == 'true': + linked_clone = True + else: + linked_clone = False + if linked_clone is True: + LOG.warn(_("[BRM-DRIVER] linked_clone volume not support!!")) + raise exception.CinderException + + full_clone = vol_meta_dict.get('full_clone') + if full_clone is None: + full_clone = '1' + elif str(full_clone) == '0': + full_clone = '0' + else: + full_clone = '1' + args_dict['full_clone'] = full_clone + + model_update = {} + snapshot_id = snapshot['id'] + os_vol_id = volume['id'] + shareable = volume.get('shareable') + if shareable and True == shareable: + LOG.info(_("[CINDER-VRM] shareable")) + voltype = 'share' + else: + voltype = 'normal' + snapshot_uuid = str(snapshot_id).replace('-', '') + + volume_size = int(volume.get('size')) + snapshot_size = int(snapshot.get('volume_size')) + + if volume_size != snapshot_size: + args_dict['volume_size'] = volume_size + + args_dict['snapshot_uuid'] = snapshot_uuid + args_dict['volume_name'] = volume['name'] + args_dict['volume_uuid'] = os_vol_id + args_dict['type'] = voltype + + body = self.volume_snapshot_proxy.create_volume_from_snapshot( + **args_dict) + vol_urn = body['urn'] + fc_vol_id = vol_urn.split(':')[-1] + + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + context = cinder_context.get_admin_context() + self.db.volume_metadata_update(context, os_vol_id, metadata, False) + + return model_update + + def create_cloned_volume(self, volume, src_volume): + LOG.info(_("[BRM-DRIVER] start create_cloned_volume()")) + volume['is_thin'] = True + LOG.info(_("[BRM-DRIVER] start create_cloned_volume()")) + model_update = self.create_volume(volume) + dest_volume_size = volume['size'] + src_volume_size = src_volume['size'] + if dest_volume_size != src_volume_size: + raise exception.InvalidParameterValue(err=_('valid volume size')) + + dest_volume_uri, items = self._vrm_unpack_provider_location( + model_update['provider_location'], 'uri') + dest_volume_urn, items = self._vrm_unpack_provider_location( + model_update['provider_location'], 'urn') + src_vol_meta = src_volume.get('volume_metadata') + src_vol_meta_dict = metadata_to_dict(src_vol_meta) + src_volume_uri = src_vol_meta_dict.get('uri') + src_volume_id = src_volume_uri.split('/')[ + len(src_volume_uri.split('/')) - 1] + args_dict = {} + args_dict['src_volume_id'] = src_volume_id + args_dict['dest_volume_urn'] = dest_volume_urn + + try: + self.volume_proxy.clone_volume(**args_dict) + except exception.CinderException as ex: + volume['provider_location'] = model_update['provider_location'] + LOG.info( + _("[CINDER-BRM] clone_volume exception , delete (%s)") % + model_update['provider_location']) + self.delete_volume(volume) + raise ex + return model_update + + def clone_image(self, context, volume, image_location, image_meta, + image_service): + '''lone_image + + :param volume: + :param image_location: + :param image_id: + :param image_meta: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start clone_image ")) + + properties = image_meta.get('properties', None) + + # check quick start + if properties is not None: + quick_start = properties.get('quick_start', None) + if quick_start is not None and str(quick_start).lower() == 'true': + LOG.info(_("[BRM-DRIVER] image has quick start property")) + # update quick_start in admin metadata + admin_context = cinder_context.get_admin_context() + self.db.volume_admin_metadata_update(admin_context, + volume.get('id'), + {'quick_start': 'True'}, + False) + return None, False + + if properties is None: + return None, False + elif 'template' != properties.get('__image_source_type', None): + LOG.info(_("[BRM-DRIVER] image_type is not template")) + return None, False + else: + LOG.info(_("[BRM-DRIVER] image_type is template")) + + image_type = 'template' + + context = cinder_context.get_admin_context() + args_dict = {} + vol_size = int(volume.get('size')) + image_id = image_meta['id'] + self._check_image_os(image_id, image_meta, vol_size) + + args_dict['image_id'] = image_id + os_vol_id = volume.get('id') + args_dict['volume_id'] = os_vol_id + args_dict['volume_size'] = vol_size + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + linked_clone = vol_meta_dict.get('linked_clone') + if linked_clone is None: + linked_clone = False + elif str(linked_clone).lower() == 'true': + linked_clone = True + else: + linked_clone = False + + hw_image_location = properties.get('__image_location', None) + if hw_image_location is None or hw_image_location == "": + msg = _('[BRM-DRIVER] hw_image_location is null') + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=image_id, reason=msg) + + args_dict['image_location'] = hw_image_location + LOG.info(_('[BRM-DRIVER] image_location is %s') % args_dict[ + 'image_location']) + + args_dict['image_type'] = image_type + + ds_meta = {} + try: + ds_meta['ds_name'] = volume.get('host').split('#')[1] + except exception.CinderException as ex: + raise ex + + datastore = self._choose_datastore(ds_meta) + if not datastore: + LOG.info(_("[CINDER-VRM] datastore [%s],"), datastore) + raise exception.InvalidParameterValue(err=_('invalid datastore')) + + args_dict['ds_urn'] = datastore.get('urn') + is_thin = FC_DRIVER_CONF.vrm_is_thin + is_thin = self.check_thin(datastore, is_thin) + args_dict['is_thin'] = is_thin + + args_dict['cluster_urn'] = self._get_cluster_by_dsurn( + datastore.get('urn')) + LOG.info(_("[BRM-DRIVER] cluster_urn [%s]") % args_dict['cluster_urn']) + + if args_dict.get('volume_sequence_num') is None: + args_dict['volume_sequence_num'] = 1 + + LOG.info(_("[BRM-DRIVER] %s image_type is template ") % image_id) + if linked_clone: + urn = self.vm_proxy.create_linkclone_from_template(**args_dict) + else: + urn = self.vm_proxy.create_volume_from_template(**args_dict) + + temp_str = str(urn) + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + + share = volume.get('shareable') + LOG.info('[BRM-DRIVER] shareable [%s]', share) + if str(share).lower() == 'true': + try: + self.volume_proxy.modify_volume(volume_id=fc_vol_id, + type='share') + except Exception as ex: + LOG.error(_("modify volume to share is failed ")) + self.delete_volume(volume) + raise ex + + model_update, metadata = self._vrm_get_volume_meta(fc_vol_id) + self.db.volume_metadata_update(context, os_vol_id, metadata, False) + return model_update, True + + def copy_image_to_volume(self, context, volume, image_service, image_id): + '''clone_image + + :param volume: + :param image_location: + :param image_id: + :param image_meta: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start copy_image_to_volume [%s]") % image_id) + image_meta = image_service.show(context, image_id) + args_dict = {} + vol_size = int(volume.get('size')) + self._check_image_os(image_id, image_meta, vol_size) + + args_dict['image_id'] = image_id + + min_disk = image_meta.get('min_disk') + if self._check_quick_start(volume.get('id')): + args_dict['quick_start'] = True + if min_disk: + args_dict['image_size'] = int(min_disk) + else: + msg = _('[BRM-DRIVER] image min_disk is none when create from ' + 'quick start image') + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + + os_vol_id = volume.get('id') + args_dict['volume_id'] = os_vol_id + args_dict['volume_size'] = vol_size + args_dict['is_thin'] = FC_DRIVER_CONF.vrm_is_thin + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + linked_clone = vol_meta_dict.get('linked_clone') + args_dict['volume_urn'] = vol_meta_dict.get('urn') + if linked_clone is None: + linked_clone = False + elif str(linked_clone).lower() == 'true': + linked_clone = True + else: + linked_clone = False + + # check quick start + if not linked_clone and self._check_quick_start(volume.get('id')): + linked_clone = True + properties = image_meta.get('properties', None) + if properties is None or properties == "": + image_type = 'glance' + else: + args_dict['volume_sequence_num'] = properties.get('__sequence_num') + image_type = properties.get('__image_source_type', None) + types = ['template', 'nfs', 'uds', 'glance'] + + if image_type is not None and image_type not in types: + msg = _('[BRM-DRIVER] image type is not support ') + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=image_id, + reason=msg) + + if image_type is None: + image_type = 'glance' + + if image_type != 'glance': + hw_image_location = properties.get('__image_location', None) + if hw_image_location is None or hw_image_location == "": + msg = _('[BRM-DRIVER] hw_image_location is null') + LOG.error(msg) + raise exception.ImageUnacceptable(image_id=image_id, + reason=msg) + + args_dict['image_location'] = hw_image_location + LOG.info(_('[BRM-DRIVER] image_location is %s') % args_dict[ + 'image_location']) + + args_dict['image_type'] = image_type + ds_meta = {} + try: + ds_meta['ds_name'] = volume.get('host').split('#')[1] + except exception.CinderException as ex: + raise ex + + datastore = self._choose_datastore(ds_meta) + if not datastore: + LOG.info(_("[CINDER-VRM] datastore [%s],"), datastore) + raise exception.InvalidParameterValue(err=_('found no datastore')) + + args_dict['ds_urn'] = datastore.get('urn') + is_thin = FC_DRIVER_CONF.vrm_is_thin + is_thin = self.check_thin(datastore, is_thin) + if linked_clone: + if not is_thin: + LOG.error(_("[CINDER-VRM] datastore does support linedclone")) + raise exception.InvalidParameterValue( + err=_('datastore does support linedclone')) + args_dict['is_thin'] = is_thin + + args_dict['cluster_urn'] = self._get_cluster_by_dsurn( + datastore.get('urn')) + LOG.info( + _("[BRM-DRIVER] self.cluster_urn [%s]") % args_dict['cluster_urn']) + + args_dict['auth_token'] = context.auth_token + + if args_dict.get('volume_sequence_num') is None: + args_dict['volume_sequence_num'] = 1 + + if linked_clone: + urn = self.vm_proxy.create_linkClone_from_extend(**args_dict) + else: + try: + temp_str = str(vol_meta_dict.get('urn')) + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + self.volume_proxy.modify_volume(volume_id=fc_vol_id, + type='normal') + except Exception as ex: + LOG.error(_("modify volume to normal is failed ")) + self.delete_volume(volume) + raise ex + urn = self.vm_proxy.create_volume_from_extend(**args_dict) + + temp_str = str(urn) + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + model_update, metadata = self._vrm_get_volume_meta(fc_vol_id) + + share = volume.get('shareable') + LOG.info('[BRM-DRIVER] shareable [%s]', share) + if str(share).lower() == 'true': + try: + self.volume_proxy.modify_volume(volume_id=fc_vol_id, + type='share') + except Exception as ex: + LOG.error(_("modify volume to share is failed ")) + self.delete_volume(volume) + raise ex + + self.db.volume_metadata_update(context, os_vol_id, metadata, False) + + def _check_image_os(self, image_id, image_meta, vol_size): + min_disk = image_meta.get('min_disk') + if min_disk: + min_disk = int(min_disk) + if min_disk < 4 and vol_size != min_disk: + prop = image_meta.get('properties') + if prop: + os_type = prop.get('__os_type') + os_version = prop.get('__os_version') + if os_type and os_version: + if os_type == 'Windows' and \ + (os_version.startswith( + 'Windows XP') or os_version.startswith( + 'Windows Server 2003')): + msg = _( + "[BRM-DRIVER] volume size must equal image " + "min disk size while image min disk size is " + "smaller than 4G and os is Windows XP or " + "Windows Server 2003") + LOG.error(msg) + raise exception.ImageUnacceptable( + image_id=image_id, reason=msg) + + def _generate_image_metadata(self, min_disk, location, + volume_sequence_num, os_option, instance): + """_generate_image_metadata + + :param name: image name + :param location: image location + :param os_option: os type and version + :param instance: + :return: + """ + if volume_sequence_num is None: + LOG.info(_("volume_sequence_num is None")) + volume_sequence_num = 1 + metadata = {'__image_location': location or '', + '__image_source_type': FC_DRIVER_CONF.export_image_type, + '__sequence_num': volume_sequence_num} + if os_option is not None: + if os_option.get('__os_version') is not None: + metadata['__os_version'] = os_option.get('__os_version') + + if os_option.get('__os_type') is not None: + metadata['__os_type'] = os_option.get('__os_type') + + LOG.info(_("image metadata is: %s"), json.dumps(metadata)) + return {'properties': metadata, 'min_disk': min_disk} + + def _generate_image_location(self, image_id, context): + """_generate_image_location + + generate image location: '172.17.1.30:/image/base/uuid/uuid.ovf' + :param image_id: + :return: + """ + if FC_DRIVER_CONF.export_image_type == 'nfs': + fc_image_path = FC_DRIVER_CONF.fc_image_path + if fc_image_path: + format = 'xml' if FC_DRIVER_CONF.export_version == 'v1.2' else\ + 'ovf' + return '%s/%s/%s.%s' % (fc_image_path, image_id, image_id, + format) + else: + LOG.info(_("fc_image_path is null")) + elif FC_DRIVER_CONF.export_image_type == 'uds': + if FC_DRIVER_CONF.uds_ip is not None and FC_DRIVER_CONF.uds_port\ + is not None and FC_DRIVER_CONF.uds_bucket_name is not None: + uds_bucket_name = FC_DRIVER_CONF.uds_bucket_name + bucket_type = FC_DRIVER_CONF.uds_bucket_type + + LOG.info(str(bucket_type)) + LOG.info(context.tenant) + + if bucket_type is not None: + if str(bucket_type) == 'wildcard': + project_tmp_id = context.to_dict()['project_id'] + LOG.info(project_tmp_id) + if project_tmp_id is None: + LOG.error(_("project_id is none ")) + raise exception.ParameterNotFound( + param='project_id is none') + else: + uds_bucket_name += project_tmp_id + ret = '%s:%s:%s:%s' % ( + FC_DRIVER_CONF.uds_ip, FC_DRIVER_CONF.uds_port, + uds_bucket_name, + image_id) + return ret + else: + return None + + def copy_volume_to_image(self, context, volume, image_service, image_meta): + '''copy_volume_to_image + + :param context: + :param volume: + :param image_service: + :param image_meta: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start copy_volume_to_image() ")) + + fc_image_path = FC_DRIVER_CONF.fc_image_path + image_id = image_meta.get('id') + if '/' in str(image_id): + image_id = image_id.split('/')[-1] + + metadata = {'__image_location': '', + '__image_source_type': FC_DRIVER_CONF.export_image_type} + image_property = {'properties': metadata} + image_service.update(context, image_id, image_property, + purge_props=False) + + volume_id = volume.get('id') + vol_size = int(volume.get('size')) + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + + vol_image_meta = None + try: + vol_image_meta = self.volume_api.get_volume_image_metadata( + context, volume) + except exception.CinderException as ex: + LOG.error(_('[BRM-DRIVER] get_volume_image_metadata is error')) + + vol_image_meta_dic = None + if vol_image_meta: + vol_image_meta_dic = dict(vol_image_meta.iteritems()) + + args_dict = {} + args_dict['volume_id'] = volume_id + args_dict['volume_size'] = vol_size + args_dict['image_id'] = image_id + args_dict['image_url'] = fc_image_path + args_dict['project_id'] = context.to_dict()['project_id'] + + share = volume.get('shareable') + LOG.info('[BRM-DRIVER] shareable [%s]', share) + if str(share).lower() == 'false': + args_dict['shareable'] = 'normal' + else: + args_dict['shareable'] = 'share' + + if vol_meta_dict.get('urn') is None: + msg = _('[BRM-DRIVER] urn is null') + LOG.error(msg) + raise exception.InvalidVolumeMetadata(reason=msg) + + args_dict['volume_urn'] = str(vol_meta_dict.get('urn')) + LOG.info(_("volume_urn is %s") % args_dict['volume_urn']) + + if self.SHARED_HOSTS is None or len(self.SHARED_HOSTS) == 0: + msg = _('[BRM-DRIVER] SHARED_HOSTS is none') + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) + + fc_vol_id = \ + args_dict['volume_urn'][args_dict['volume_urn'].rfind(':') + 1:] + volume_body = self.volume_proxy.query_volume(id=fc_vol_id) + source_ds_urn = volume_body['datastoreUrn'] + LOG.info(_("[BRM-DRIVER] datastoreUrn [%s]") % source_ds_urn) + args_dict['cluster_urn'] = self._get_cluster_by_dsurn(source_ds_urn) + LOG.info( + _("[BRM-DRIVER] cluster_urn [%s]") % args_dict['cluster_urn']) + + # args_dict['cluster_urn'] = self.cluster_urn + name = image_service.show(context, image_id).get('name') + args_dict['image_type'] = FC_DRIVER_CONF.export_image_type + args_dict['auth_token'] = context.auth_token + + location = self._generate_image_location(image_id, context) + metadata = {'__image_location': location or '', + '__image_source_type': FC_DRIVER_CONF.export_image_type} + image_property = {'properties': metadata} + image_service.update(context, image_id, image_property, + purge_props=False) + + try: + LOG.info(_('location %s ') % location) + volume_sequence_num = self.vm_proxy.export_volume_to_image( + **args_dict) + except Exception as ex: + LOG.info(_("[BRM-DRIVER] deletedelete image id:[%s]") % image_id) + image_service.delete(context, image_id) + raise ex + + metadata = self._generate_image_metadata(vol_size, location, + volume_sequence_num, + vol_image_meta_dic, None) + if 'glance' != args_dict.get('image_type'): + image_service.update(context, image_id, {}, + data='/home/vhd/G1-1.vhd') + image_service.update(context, image_id, metadata, purge_props=False) + LOG.info(_('image %s create success') % name) + + def attach_volume(self, context, volume_id, instance_uuid, + host_name_sanitized, mountpoint): + '''attach_volume + + :param context: + :param volume_id: + :param instance_uuid: + :param host_name_sanitized: + :param mountpoint: + :return: + ''' + pass + + def detach_volume(self, context, volume): + '''detach_volume + + :param context: + :param volume: + :return: + ''' + pass + + def initialize_connection(self, volume, connector): + '''initialize_connection + + :param volume: + :param connector: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start initialize_connection() ")) + connection = {'data': {}} + LOG.info('volume: %s', volume) + admin_context = cinder_context.get_admin_context() + vol_meta = self.db.volume_metadata_get(admin_context, volume['id']) + connection['vol_urn'] = vol_meta['urn'] + return connection + + def terminate_connection(self, volume, connector, **kwargs): + '''terminate_connection + + :param volume: + :param connector: + :param force: + :return: + ''' + pass + + def retype(self, context, volume, new_type, diff, host): + '''retype + + :param context: + :param volume: + :param new_type: + :param diff: + :param host: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start retype() ")) + LOG.info(_(" new volume type [%s]"), new_type) + + args_dict = {} + is_thin = FC_DRIVER_CONF.vrm_is_thin + if str(is_thin).lower() == 'true': + args_dict['migrate_type'] = 1 + else: + args_dict['migrate_type'] = 2 + shareable = volume.get('shareable') + if shareable is True: + LOG.info(_("[BRM-DRIVER] shareable")) + + if diff and diff.get('extra_specs'): + pvscsi_support = diff.get('extra_specs').get('hw:passthrough') + if pvscsi_support and pvscsi_support[0] != pvscsi_support[1]: + msg = (_('rdm volume can only retyped to rdm volume type.')) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + extra_specs = new_type.get('extra_specs') + if extra_specs is None: + LOG.info(_("[BRM-DRIVER] extra_specs is None")) + return True, None + new_backend_name = extra_specs.get('volume_backend_name') + if new_backend_name is None: + LOG.info(_("[BRM-DRIVER] new_backend_name is None")) + return True, None + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + linked_clone = vol_meta_dict.get('linked_clone') + if linked_clone is not None: + if str(linked_clone).lower() == 'true': + msg = (_('linked volume can not be retyped. ')) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + snapshots = self.db.snapshot_get_all_for_volume(context, + volume.get('id')) + if len(snapshots): + msg = _("Volume still has %d dependent snapshots") % len(snapshots) + raise exception.InvalidVolume(reason=msg) + + try: + source_ds_name = volume.get('host').split('#')[1] + except exception.CinderException as ex: + LOG.info(_("[CINDER-BRM] host format exception, host is %s ") % + volume.get('host')) + raise ex + + LOG.info(_(" source_ds_name [%s]"), source_ds_name) + + try: + new_ds_name = volume_utils.extract_host(host['host'], 'pool') + except exception.CinderException as ex: + LOG.info( + _("[CINDER-BRM] host format exception, host is %s ") % + volume.get('host')) + raise ex + + LOG.info(_(" new_ds_name [%s]"), new_ds_name) + + if source_ds_name == new_ds_name: + LOG.info(_("[CINDER-BRM] source ds_name == dest ds_name")) + return True, None + + datastores = self.datastore_proxy.list_datastore() + for datastore in datastores: + ds_name = datastore.get('name') + if ds_name is not None: + LOG.info(_(" ds_name [%s]"), ds_name) + if new_ds_name == ds_name: + args_dict['dest_ds_urn'] = datastore.get('urn') + LOG.info(_(" new_ds_name [%s]"), new_ds_name) + break + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + volume_urn = vol_meta_dict.get('urn') + args_dict['volume_urn'] = volume_urn + fc_vol_id = volume_urn[volume_urn.rfind(':') + 1:] + volume_body = self.volume_proxy.query_volume(id=fc_vol_id) + source_ds_urn = volume_body['datastoreUrn'] + + args_dict['volume_id'] = fc_vol_id + args_dict['speed'] = 30 + + if None == args_dict.get('dest_ds_urn'): + LOG.info(_("[BRM-DRIVER] no dest_ds_urn")) + return False, None + else: + if source_ds_urn == args_dict['dest_ds_urn']: + LOG.info(_("[BRM-DRIVER] same ds [%s]"), source_ds_urn) + return True, None + vm_body = self.vm_proxy.query_vm_volume(**args_dict) + self._check_replications(fc_vol_id) + if vm_body is None: + self.volume_proxy.migrate_volume(**args_dict) + else: + vm_urn = vm_body.get('urn') + vm_id = vm_urn[-10:] + args_dict['vm_id'] = vm_id + self.vm_proxy.migrate_vm_volume(**args_dict) + + LOG.info(_("[CINDER-VRM] fc_vol_id [%s] ") % fc_vol_id) + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + self.db.volume_metadata_update(context, volume['id'], metadata, False) + LOG.info(_("[BRM-DRIVER] retype return")) + return True, None + + def manage_existing(self, volume, existing_ref): + """Brings an existing backend storage object under Cinder management. + + existing_ref is passed straight through from the API request's + manage_existing_ref value, and it is up to the driver how this should + be interpreted. It should be sufficient to identify a storage object + that the driver should somehow associate with the newly-created cinder + volume structure. + """ + + LOG.info(_("[BRM-DRIVER] start manage_existing() ")) + metadata = dict( + (item['key'], item['value']) for item in volume['volume_metadata']) + volInfoUrl = metadata.get('volInfoUrl', None) + if volInfoUrl is None: + LOG.info(_("manage_existing: volInfoUrl is None")) + raise driver_exception.FusionComputeDriverException() + + name = volume['name'] + uuid = volume['id'] + shareable = volume.get('shareable') + if shareable is True: + voltype = 'share' + else: + voltype = 'normal' + + quantity_GB = int(volume['size']) + + args_dict = {} + args_dict['name'] = name + args_dict['quantityGB'] = quantity_GB + args_dict['type'] = voltype + args_dict['volInfoUrl'] = volInfoUrl + args_dict['uuid'] = uuid + + args_dict['maxReadBytes'] = 0 + args_dict['maxWriteBytes'] = 0 + args_dict['maxReadRequest'] = 0 + args_dict['maxWriteRequest'] = 0 + body = self.volume_proxy.manage_existing(**args_dict) + + model_update = {} + temp_str = str(body.get('urn')) + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + context = cinder_context.get_admin_context() + self.db.volume_metadata_update(context, uuid, metadata, False) + if existing_ref: + LOG.info("manage_existing: existing_ref[%s]", existing_ref) + for key, value in existing_ref.items(): + self.db.volume_glance_metadata_create(context, + uuid, + key, value) + + return model_update + + def manage_existing_get_size(self, volume, existing_ref): + """Return size of volume to be managed by manage_existing. + + When calculating the size, round up to the next GB. + """ + vol_size = 0 + for meta in volume['volume_metadata']: + LOG.error("meta: %s" % str(meta)) + if meta.key == 'quantityGB': + vol_size = int(meta.value) + break + + volume['size'] = vol_size + + return vol_size + + def unmanage(self, volume): + """Removes the specified volume from Cinder management. + + Does not delete the underlying backend storage object. + + For most drivers, this will not need to do anything. However, some + drivers might use this call as an opportunity to clean up any + Cinder-specific configuration that they have associated with the + backend storage object. + + :param volume: + :return: + """ + + LOG.info(_("[BRM-DRIVER] start unmanage() ")) + + provider_location = volume['provider_location'] + if provider_location is None or provider_location == '': + LOG.error(_("[BRM-DRIVER]provider_location is null ")) + return + volume_uri, items = \ + self._vrm_unpack_provider_location(volume['provider_location'], + 'uri') + self.volume_proxy.unmanage(volume_uri=volume_uri) + + def migrate_volume(self, context, volume, host): + '''migrate_volume + + :param context: + :param volume: + :param host: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start migrate_volume() ")) + + raise NotImplementedError() + + def copy_volume_data(self, context, src_vol, dest_vol, remote=None): + '''copy_volume_data + + :param context: + :param src_vol: + :param dest_vol: + :param remote: + :return: + ''' + msg = (_('copy_volume_data. ')) + LOG.error(msg) + raise NotImplementedError() + + def extend_volume(self, volume, new_size): + '''extend_volume + + :param volume: + :param new_size: + :return: + ''' + LOG.info(_("[BRM-DRIVER] start extend_volume() ")) + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + vol_uri = vol_meta_dict.get('uri') + fc_vol_urn = vol_meta_dict.get('urn') + if fc_vol_urn is not None: + fc_vol_id = fc_vol_urn[fc_vol_urn.rfind(':') + 1:] + self._check_replications(fc_vol_id) + + LOG.info(_("[CINDER-VRM]extend_volume fc_vol_id [%s] ") % fc_vol_id) + + if vol_uri is not None: + temp_str = str(vol_meta_dict.get('urn')) + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + vrm_volume = self.volume_proxy.query_volume(id=fc_vol_id) + sizeGB = vrm_volume.get('quantityGB') + if int(sizeGB) < int(new_size): + self._check_datastore_capacity(vrm_volume, volume['size'], + new_size) + self.volume_proxy.extend_volume(volume_uri=vol_uri, + size=new_size * 1024) + else: + LOG.info(_("[CINDER-VRM]sizeGB[%s] avoid extend") % sizeGB) + else: + raise exception.ExtendVolumeError + + fc_vol_id = fc_vol_urn[fc_vol_urn.rfind(':') + 1:] + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + context = cinder_context.get_admin_context() + self.db.volume_metadata_update(context, volume['id'], metadata, False) + + def _check_datastore_capacity(self, fc_volume, old_size, new_size): + # keep same strage with capacity_filter + datastore_urn = fc_volume.get('datastoreUrn') + extra_size = new_size - old_size + if datastore_urn: + datastore_id = datastore_urn[datastore_urn.rfind(':') + 1:] + datastore = self.datastore_proxy.query_datastore(id=datastore_id) + + if 'NORMAL' != datastore['status']: + msg = _('Datastore status is abnormal.') + raise exception.ExtendVolumeError(message=msg) + + reserved = float(self.reserved_percentage) / 100 + total = float(datastore['capacityGB']) + free_space = datastore['freeSizeGB'] + free = free_space - math.floor(total * reserved) + + if self.thin_provision and self.over_ratio >= 1: + provisioned_ratio = ((datastore['usedSizeGB'] + + extra_size) / total) + if provisioned_ratio >= self.over_ratio: + LOG.warning(_( + "Insufficient free space for thin provisioning. " + "The ratio of provisioned capacity over total " + "capacity " + "%(provisioned_ratio).2f has exceeded the maximum " + "over " + "subscription ratio %(oversub_ratio).2f."), + {"provisioned_ratio": provisioned_ratio, + "oversub_ratio": self.over_ratio}) + msg = _('Insufficient free space for thin provisioning.') + raise exception.ExtendVolumeError(message=msg) + else: + free_virtual = free * self.over_ratio + if free_virtual < extra_size: + msg = _( + 'Insufficient free space for thin provisioning.') + raise exception.ExtendVolumeError(message=msg) + else: + return True + + if free < extra_size: + msg = _( + 'The new size of the volume exceed the capacity of the ' + 'datastore.') + raise exception.ExtendVolumeError(message=msg) + + def backup_volume(self, context, backup, backup_service): + """Create a new backup from an existing volume. + + backup['status'] + backup['object_count'] + backup['_sa_instance_state'] + backup['user_id'] + backup['service']:q + + backup['availability_zone'] + backup['deleted'] + backup['created_at'] + backup['updated_at'] + backup['display_description'] + backup['project_id'] + backup['host'] + backup['container'] + backup['volume_id'] + backup['display_name'] + backup['fail_reason'] + backup['deleted_at'] + backup['service_metadata'] + backup['id'] + backup['size'] + -------- -------- -------- -------- + backup['backup_type'] + backup['volume_name'] + backup['snap_name'] + backup['snap_id'] + backup['snap_parent_name'] + backup['snap_last_name'] + backup['clone_volume_name'] + backup['storage_ip'] + backup['storage_pool_id'] + backup['volume_offset'] + backup['incremental'] + backup['is_close_volume'] + backup['is_bootable'] + backup['image_id'] + + backup['volume_size'] + volume_file + backup_metadata + +backup db:CREATED_AT | UPDATED_AT | DELETED_AT | DELETED +| ID | VOLUME_ID | USER_ID | PROJECT_ID | HOST | AVAILABILITY_ZONE +| DISPLAY_NAME | DISPLAY_DESCRIPTION | CONTAINER | STATUS | FAIL_REASON +| SERVICE_METADATA | SERVICE | SIZE | OBJECT_COUNT + + +snapshot db: CREATED_AT|UPDATED_AT|DELETED_AT| DELETED |ID |VOLUME_ID|USER_ID + |PROJECT_ID| STATUS | PROGRESS | VOLUME_SIZE | SCHEDULED_AT | DISPLAY_NAME + | DISPLAY_DESCRIPTION | PROVIDER_LOCATION | ENCRYPTION_KEY_ID | + VOLUME_TYPE_ID | CGSNAPSHOT_ID + + """ + vol_second_os = self.db.volume_get(context, backup['volume_id']) + + LOG.info( + ('Creating a new backup for volume %s.') % vol_second_os['name']) + volume_file = {} + if vol_second_os.get('snapshot_id') != backup['container']: + LOG.error( + _("snapshot id is %s") % vol_second_os.get('snapshot_id')) + LOG.error(_("backup container is %s") % backup['container']) + raise exception.InvalidSnapshot( + reason="snapshot id not equal backup container") + + vol_meta = vol_second_os.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + volume_urn = vol_meta_dict.get('urn') + fc_vol_id = volume_urn[volume_urn.rfind(':') + 1:] + vol_second_fc = self.volume_proxy.query_volume(id=fc_vol_id) + + volume_file['volume_name'] = vol_second_fc['volNameOnDev'] + last_snap_id_os = vol_second_os['snapshot_id'] + last_snap_id_fc = str(vol_second_os['snapshot_id']).replace('-', '') + last_snap_os = self.db.snapshot_get(context, last_snap_id_os) + vol_first_id = last_snap_os['volume_id'] + volume_file['source_volume_id'] = vol_first_id + + ext_params = vol_second_fc.get('drExtParams') + LOG.info(_("[VRM-CINDER] ext_params [%s]"), ext_params) + if ext_params: + ext_params_dic = json.loads(ext_params) + + storage_type = vol_second_fc.get('storageType') + sd_sn = vol_second_fc.get('storageSN') + if storage_type == 'advanceSan': + volume_file['storage_type'] = 4 # advanceSan v3 volume + support_pvscsi = vol_second_fc.get('pvscsiSupport') + if support_pvscsi is not None and support_pvscsi == 1: + volume_file['storage_type'] = 5 # RDM : direct io volume + else: + volume_file['storage_type'] = 0 + LOG.info(_("[VRM-CINDER] ext_params [%s]"), ext_params_dic) + volume_file['storage_ip'] = ext_params_dic['dsMgntIp'] + volume_file['storage_pool_id'] = ext_params_dic['dsResourceId'] + + LOG.info(_("[VRM-CINDER] volume_file [%s]"), volume_file) + backup_list = self.db.backup_get_by_volume_id(context, vol_first_id) + + last_backup = None + if backup_list is not None: + for back_tmp in backup_list: + if back_tmp['status'] != "available" and back_tmp[ + 'status'] != "restoring": + continue + if last_backup is None: + last_backup = back_tmp + else: + if last_backup['created_at'] < back_tmp['created_at']: + last_backup = back_tmp + + if last_backup is None: + volume_file['backup_type'] = 0 + volume_file['parent_id'] = None + volume_file['parent_snapshot_url'] = None + else: + LOG.info(_("last_backup %s") % last_backup['id']) + volume_file['backup_type'] = 1 + volume_file['parent_id'] = last_backup['id'] + + if last_backup['service_metadata'] is None: + raise exception.InvalidVolumeMetadata( + reason="backup service_metadata is none") + + service_meta = last_backup['service_metadata'] + str_service_meta = json.loads(service_meta) + parent_snapshot_id_os = str_service_meta.get('snap_id') + if parent_snapshot_id_os is not None: + parent_snapshot_id_fc = \ + str(parent_snapshot_id_os).replace('-', '') + + if storage_type == 'advanceSan': + parent_snapshot_fc = \ + self.volume_snapshot_proxy.query_volumesnapshot( + uuid=parent_snapshot_id_fc) + if parent_snapshot_fc and parent_snapshot_fc.\ + get('snapshotLunId') is not None: + volume_file[ + 'parent_snapshot_url'] = \ + 'http://' + sd_sn + '/' + parent_snapshot_fc.get( + 'snapshotLunId') + else: + volume_file['parent_snapshot_url'] = 'http://' + \ + ext_params_dic[ + 'dsMgntIp'] + \ + '/' + \ + ext_params_dic[ + 'dsResourceId'] +\ + '/' + \ + parent_snapshot_id_fc + else: + raise exception.InvalidVolumeMetadata( + reason="snapshot_id is none") + + LOG.info(_("vol_first_id is %s") % vol_first_id) + vol_first_os = self.db.volume_get(context, vol_first_id) + vol_first_meta = vol_first_os.get('volume_metadata') + vol_first_meta_dict = metadata_to_dict(vol_first_meta) + volume_first_urn = vol_first_meta_dict.get('urn') + fc_first_vol_id = volume_first_urn[volume_urn.rfind(':') + 1:] + LOG.info(_("fc_first_vol_id is %s") % fc_first_vol_id) + + vol_source_fc = self.volume_proxy.query_volume(id=fc_first_vol_id) + LOG.info(_("vol_source_fc linkCloneParent is %s") % vol_source_fc[ + 'linkCloneParent']) + if vol_source_fc['linkCloneParent'] is not None: + volume_file['is_clone_volume'] = True + try: + vol_linked_fc = self.volume_proxy.query_volume( + id=vol_source_fc['linkCloneParent']) + if storage_type == 'advanceSan': + volume_file['clone_volume_url'] = 'http://' + \ + vol_linked_fc[ + 'storageSN'] \ + + '/' + vol_linked_fc[ + 'lunId'] + else: + linked_ext_params_dic = json.loads( + vol_linked_fc.get('drExtParams')) + volume_file['clone_volume_url'] = 'http://' + \ + linked_ext_params_dic[ + 'dsMgntIp'] \ + + '/' + \ + linked_ext_params_dic[ + 'dsResourceId'] \ + + '/' + vol_source_fc[ + 'linkCloneParent'] + except Exception: + LOG.error(_("clone colume not exit")) + volume_file['clone_volume_url'] = None + else: + volume_file['is_clone_volume'] = False + volume_file['clone_volume_url'] = None + + volume_file['snapshot_name'] = last_snap_os['name'] + volume_file['snapshot_id'] = last_snap_id_fc + + LOG.info(_("[VRM-CINDER] volume_file [%s]"), volume_file) + + volume_file['volume_size'] = vol_second_os['size'] + LOG.info(_("[VRM-CINDER] volume_file [%s]"), volume_file) + if storage_type == 'advanceSan': + last_snapshot = self.volume_snapshot_proxy.query_volumesnapshot( + uuid=last_snap_id_fc) + if last_snapshot and last_snapshot.get( + 'snapshotLunId') is not None: + volume_file['snapshot_url'] = 'http://' + sd_sn \ + + '/' + last_snapshot.get( + 'snapshotLunId') + else: + volume_file['snapshot_url'] = \ + 'http://' + ext_params_dic['dsMgntIp'] + '/' + \ + ext_params_dic['dsResourceId'] + '/' + last_snap_id_fc + + volume_file['bootable'] = False + volume_file['image_id'] = None + try: + vol_image_meta = \ + self.volume_api.get_volume_image_metadata(context, + vol_second_os) + if vol_image_meta: + vol_image_meta_dic = dict(vol_image_meta.iteritems()) + volume_file['image_id'] = vol_image_meta_dic.get('image_id') + volume_file['bootable'] = True + + except exception.CinderException as ex: + LOG.error( + _('[BRM-DRIVER] get_volume_image_metadata is error [%s]'), ex) + + if vol_first_os.get('bootable') is True: + volume_file['bootable'] = True + + LOG.info(_("[VRM-CINDER] volume_file [%s]"), volume_file) + + try: + backup_service.backup(backup, volume_file) + finally: + LOG.info(('cleanup for volume %s.') % vol_second_os['name']) + + def restore_backup(self, context, backup, volume, backup_service): + """Restore an existing backup to a new or existing volume.""" + + LOG.info(('restore_backup for volume %s.') % volume['name']) + volume_file = {} + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + volume_urn = vol_meta_dict.get('urn') + fc_vol_id = volume_urn[volume_urn.rfind(':') + 1:] + vol_second_fc = self.volume_proxy.query_volume(id=fc_vol_id) + ext_params = vol_second_fc.get('drExtParams') + if ext_params: + LOG.info(_("[VRM-CINDER] ext_params [%s]"), ext_params) + ext_params_dic = json.loads(vol_second_fc.get('drExtParams')) + LOG.info(_("[VRM-CINDER] ext_params [%s]"), ext_params_dic) + storage_type = vol_second_fc.get('storageType') + volume_file['restore_type'] = 0 + if backup['volume_id'] == volume['id']: + volume_file['restore_type'] = 1 + service_meta = backup['service_metadata'] + str_service_meta = json.loads(service_meta) + last_snapshot_id_openstack = str_service_meta.get('snap_id') + last_snapshot_id_fc = str(last_snapshot_id_openstack).replace('-', + '') + last_snapshot_fc = self.volume_snapshot_proxy.query_volumesnapshot( + uuid=last_snapshot_id_fc) + if last_snapshot_fc: # only the last backup has snapshot + if storage_type == 'advanceSan': + volume_file['lastest_snapshot_url'] = 'http://' + \ + vol_second_fc[ + 'storageSN'] +\ + '/' + \ + last_snapshot_fc[ + 'snapshotLunId'] + else: + volume_file['lastest_snapshot_url'] = 'http://' + \ + ext_params_dic[ + 'dsMgntIp'] \ + + '/' + \ + ext_params_dic[ + 'dsResourceId'] \ + + '/' + \ + last_snapshot_id_fc + + if storage_type == 'advanceSan': + volume_file['storage_type'] = 4 + pvscsi_support = vol_second_fc.get('pvscsiSupport') + if pvscsi_support is not None and pvscsi_support == 1: + volume_file['storage_type'] = 5 # RDM: direct io volume + volume_file['volume_url'] = 'http://' + vol_second_fc[ + 'storageSN'] + '/' + vol_second_fc['lunId'] + else: + volume_file['storage_type'] = 0 + volume_file['volume_url'] = vol_second_fc['volInfoUrl'] + LOG.info(_("[VRM-CINDER] volume_file [%s]"), volume_file) + volume_file['storage_ip'] = ext_params_dic['dsMgntIp'] + volume_file['storage_pool_id'] = ext_params_dic['dsResourceId'] + volume_file['volume_offset'] = True + volume_file['volume_name'] = vol_second_fc['volNameOnDev'] + + if vol_second_fc['linkCloneParent'] is not None: + try: + vol_linked_fc = self.volume_proxy.query_volume( + id=vol_second_fc['linkCloneParent']) + if vol_second_fc['storageType'] == 'advanceSan': + volume_file['clone_volume_url'] = 'http://' + \ + vol_linked_fc[ + 'storageSN'] + \ + vol_linked_fc['lunId'] + else: + linked_ext_params_dic = json.loads( + vol_linked_fc.get('drExtParams')) + volume_file['clone_volume_url'] = 'http://' + \ + linked_ext_params_dic[ + 'dsMgntIp'] \ + + '/' + \ + linked_ext_params_dic[ + 'dsResourceId'] \ + + '/' + vol_second_fc[ + 'linkCloneParent'] + except Exception: + LOG.error(_("clone colume not exit")) + volume_file['clone_volume_url'] = None + else: + volume_file['clone_volume_url'] = None + + try: + backup_service.restore(backup, volume['id'], volume_file) + finally: + LOG.info(('cleanup for volume %s.') % volume['name']) + + def delete_cgsnapshot(self, context, cgsnapshot): + """Delete a cgsnapshot.""" + model_update = {} + model_update['status'] = cgsnapshot['status'] + cgsnapshot_id = cgsnapshot['id'] + LOG.info(_('[BRM-DRIVER] start to delete cgsnapshot [%s]'), + cgsnapshot_id) + + snapshots = self.db.snapshot_get_all_for_cgsnapshot(context, + cgsnapshot_id) + if snapshots and len(snapshots) > 0: + for snapshot in snapshots: + snapshot_id = snapshot['id'] + snapshot_uuid = str(snapshot_id).replace('-', '') + body = self.volume_snapshot_proxy.query_volumesnapshot( + uuid=snapshot_uuid) + if body is None: + snapshot['status'] = 'deleted' + continue + try: + self.volume_snapshot_proxy.delete_volumesnapshot( + id=snapshot_uuid) + snapshot['status'] = 'deleted' + except Exception as ex: + LOG.exception(ex) + snapshot['status'] = 'error_deleting' + model_update['status'] = 'error_deleting' + return model_update, snapshots + + def create_cgsnapshot(self, context, cgsnapshot): + model_update = {} + consistencygroup_id = cgsnapshot['consistencygroup_id'] + cgsnapshot_id = cgsnapshot['id'] + snapshots = self.db.snapshot_get_all_for_cgsnapshot(context, + cgsnapshot_id) + volumes = self.db.volume_get_all_by_group(context, consistencygroup_id) + snapshot_uuids = [] + if volumes is None or len(volumes) <= 0: + return model_update + else: + for volume_ref in volumes: + for snapshot in snapshots: + if snapshot['volume_id'] == volume_ref['id']: + vol_meta = volume_ref['volume_metadata'] + vol_meta_dict = metadata_to_dict(vol_meta) + vol_urn = vol_meta_dict.get('urn') + if vol_urn is None: + LOG.warn(_( + "vol_urn is null while creating cgsnapshot.")) + snapshot_id = snapshot['id'] + snapshot_uuid = str(snapshot_id).replace('-', '') + try: + body = self.volume_snapshot_proxy.\ + create_volumesnapshot( + snapshot_uuid=snapshot_uuid, + vol_urn=vol_urn, enable_active=False) + except Exception as ex: + LOG.exception(ex) + snapshot['status'] = 'error' + model_update['status'] = 'error' + + if body['urn'] is None: + snapshot['status'] = 'error' + model_update['status'] = 'error' + snapshot_uuids.append(snapshot_uuid) + try: + self.volume_snapshot_proxy.active_snapshots( + snapshot_uuids=snapshot_uuids) + except Exception as ex: + LOG.exception(ex) + for snapshot in snapshots: + snapshot['status'] = 'error' + model_update['status'] = 'error' + + return model_update, snapshots + + def _get_cluster_by_dsurn(self, ds_urn): + """Return clusterUrn by dsurn""" + # datastore_id = ds_urn[ds_urn.rfind(':') + 1:] + clusters = self.cluster_proxy.list_cluster() + random.shuffle(clusters) + for cluster in clusters: + cluster_urn = cluster.get('urn') + args_dict = {} + args_dict['scope'] = cluster_urn + try: + datastores = self.datastore_proxy.list_datastore(**args_dict) + for datastore in datastores: + if datastore['urn'] == ds_urn: + hosts = self.cluster_proxy.list_hosts( + clusterUrn=cluster_urn) + for host in hosts: + if host["status"] == "normal" and host[ + "isMaintaining"] is False: + return cluster_urn + except Exception as ex: + LOG.error( + _('[BRM-DRIVER] get_volume_image_metadata is error [%s]'), + ex) + msg = _('[BRM-DRIVER] get cluster is none') + LOG.error(msg) + raise exception.VolumeDriverException(message=msg) diff --git a/cinder/volume/drivers/huawei/fusioncompute/vrm_driver_huawei.py b/cinder/volume/drivers/huawei/fusioncompute/vrm_driver_huawei.py new file mode 100644 index 0000000..1076df2 --- /dev/null +++ b/cinder/volume/drivers/huawei/fusioncompute/vrm_driver_huawei.py @@ -0,0 +1,200 @@ +# Copyright 2016 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +from oslo_config import cfg +from oslo_log import log as logging + +from cinder import context as cinder_context +from cinder.i18n import _ +from cinder.volume.drivers.huawei.vrm.vrm_driver import VRMDriver + + +def metadata_to_dict(metadata): + result = {} + for item in metadata: + if not item.get('deleted'): + result[item['key']] = item['value'] + return result + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + + +class VRMDriverHuawei(VRMDriver): + VENDOR = 'Huawei' + BACKEND = 'VRM' + VERSION = 'v1.1' + + def __init__(self, *args, **kwargs): + '''__init__ + + :param args: + :param kwargs: + :return: + ''' + super(VRMDriverHuawei, self).__init__(*args, **kwargs) + + def create_volume(self, volume): + '''create_volume + + create_volume + { + "name":string, + quantityGB:integer, + datastoreUrn:string, + "isThin":boolean, + "type":string, + indepDisk:boolean, + persistentDisk:boolean + } + + :param volume: + :return: + ''' + LOG.info(_("[CINDER-VRM] start create_volume() ")) + + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + context = cinder_context.get_admin_context() + + temp_str = vol_meta_dict.get('urn') + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + + name = volume.get('name') + model_update, metadata = self._vrm_get_volume_meta(id=fc_vol_id) + self.db.volume_metadata_update(context, volume['id'], metadata, False) + try: + self.volume_proxy.update_custom_properties(volume_urn=temp_str, + external_uuid=volume[ + 'id']) + # modify volume name + self.volume_proxy.modify_volume(volume_id=fc_vol_id, name=name) + except Exception as ex: + LOG.error( + _("[CINDER-VRM] modify volume information failed after create " + "volume.volume id is [%s] ") % fc_vol_id) + LOG.exception(ex) + return model_update + + def delete_volume(self, volume): + '''delete_volume + + :param volume: + :return: + ''' + LOG.info(_("[CINDER-VRM] start delete_volume() ")) + vol_meta = volume.get('volume_metadata') + vol_meta_dict = metadata_to_dict(vol_meta) + fc_volume_name = vol_meta_dict.get('fc_volume_name') + temp_str = vol_meta_dict.get('urn') + fc_vol_id = temp_str[temp_str.rfind(':') + 1:] + try: + self.volume_proxy.update_custom_properties(volume_urn=temp_str, + external_uuid="") + # modify volume name + self.volume_proxy.modify_volume(volume_id=fc_vol_id, + name=fc_volume_name) + except Exception as ex: + LOG.error( + _("[CINDER-VRM] modify volume information failed after create" + " volume.volume id is [%s] ") % fc_vol_id) + LOG.exception(ex) + return + + def _try_get_volume_stats(self, refresh=False): + '''_try_get_volume_stats + + :param refresh:If 'refresh' is True, run the update first. + :return:Return the current state of the volume service. + ''' +# LOG.info(_("[BRM-DRIVER] start _try_get_volume_stats() ")) + if refresh: + self.left_periodrate -= 1 + if self.left_periodrate <= 0: + self._refresh_storage_info(refresh) + stats = self._build_volume_stats() + ds_meta = {} + ds_names = [ds['name'] for ds in self.SHARED_DATASTORES] + for pool in self.pool_list: + if pool not in ds_names: + continue + new_pool = {} + ds_meta['ds_name'] = pool + datastore = self._choose_datastore(ds_meta) + if datastore.get('storageType') == 'advanceSan' and datastore.get( + 'version') is not None: + new_pool.update(dict(consistencygroup_support=True)) + if 'NORMAL' != datastore['status']: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb=0, + reserved_percentage=self.reserved_percentage, + total_capacity_gb=0, + provisioned_capacity_gb=0, + max_over_subscription_ratio=self.over_ratio, + affine_rate=1 + )) + stats["pools"].append(new_pool) + continue + + if self.current_list is not None and pool in self.current_list: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb="infinite", + reserved_percentage=self.reserved_percentage, + total_capacity_gb="infinite", + provisioned_capacity_gb=datastore['usedSizeGB'], + max_over_subscription_ratio=self.over_ratio, + affine_rate=self.affine_rate + )) + else: + new_pool.update(dict( + pool_name=pool, + free_capacity_gb="infinite", + reserved_percentage=self.reserved_percentage, + total_capacity_gb="infinite", + provisioned_capacity_gb=datastore['usedSizeGB'], + max_over_subscription_ratio=self.over_ratio, + affine_rate=1 + )) + if self.thin_provision is True: + new_pool.update(dict( + thin_provisioning_support=True, + thick_provisioning_support=False + )) + else: + new_pool.update(dict( + thin_provisioning_support=False, + thick_provisioning_support=True + )) + tier_size = datastore.get('tierSize', None) + type_v3 = [] + if tier_size and len(tier_size) >= 3: + if tier_size[0] > 0: + type_v3.append('ssd') + if tier_size[1] > 0: + type_v3.append('sas') + if tier_size[2] > 0: + type_v3.append('nl_sas') + if len(type_v3) > 0: + type_v3_str = ';'.join(type_v3) + LOG.info(_("[CINDER-BRM] type of v3 is %s"), type_v3_str) + new_pool.update(dict(type=type_v3_str)) + stats["pools"].append(new_pool) + + # LOG.info(_("[CINDER-BRM] (%d)--%s"), self.left_periodrate, stats) + + return stats diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..3ed7474 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# 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 os +import sys + +sys.path.insert(0, os.path.abspath('../..')) +# -- General configuration ---------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + #'sphinx.ext.intersphinx', + 'oslosphinx' +] + +# autodoc generation is a bit aggressive and a nuisance when doing heavy +# text edit cycles. +# execute "export SPHINX_DEBUG=1" in your terminal to disable + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'cinder-fusioncompute' +copyright = u'2016, OpenStack Foundation' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +# html_theme_path = ["."] +# html_theme = '_theme' +# html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack Foundation', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst new file mode 100644 index 0000000..1728a61 --- /dev/null +++ b/doc/source/contributing.rst @@ -0,0 +1,4 @@ +============ +Contributing +============ +.. include:: ../../CONTRIBUTING.rst diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..ed7bf01 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,25 @@ +.. cinder-fusioncompute documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to cinder-fusioncompute's documentation! +======================================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + readme + installation + usage + contributing + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..e67097f --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,12 @@ +============ +Installation +============ + +At the command line:: + + $ pip install cinder-fusioncompute + +Or, if you have virtualenvwrapper installed:: + + $ mkvirtualenv cinder-fusioncompute + $ pip install cinder-fusioncompute diff --git a/doc/source/readme.rst b/doc/source/readme.rst new file mode 100644 index 0000000..a6210d3 --- /dev/null +++ b/doc/source/readme.rst @@ -0,0 +1 @@ +.. include:: ../../README.rst diff --git a/doc/source/usage.rst b/doc/source/usage.rst new file mode 100644 index 0000000..25792e6 --- /dev/null +++ b/doc/source/usage.rst @@ -0,0 +1,7 @@ +======== +Usage +======== + +To use cinder-fusioncompute in a project:: + + import cinder-fusioncompute diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py new file mode 100644 index 0000000..07d2bea --- /dev/null +++ b/releasenotes/source/conf.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- +# 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. + +# Glance Release Notes documentation build configuration file, created by +# sphinx-quickstart on Tue Nov 3 17:40:50 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'oslosphinx', + 'reno.sphinxext', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'cinder-fusioncompute Release Notes' +copyright = u'2016, OpenStack Foundation' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# The full version, including alpha/beta/rc tags. +release = '' +# The short X.Y version. +version = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'GlanceReleaseNotesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'GlanceReleaseNotes.tex', u'Glance Release Notes Documentation', + u'Glance Developers', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'glancereleasenotes', u'Glance Release Notes Documentation', + [u'Glance Developers'], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'GlanceReleaseNotes', u'Glance Release Notes Documentation', + u'Glance Developers', 'GlanceReleaseNotes', + 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst new file mode 100644 index 0000000..b8445c5 --- /dev/null +++ b/releasenotes/source/index.rst @@ -0,0 +1,8 @@ +============================================ + cinder-fusioncompute Release Notes +============================================ + +.. toctree:: + :maxdepth: 1 + + unreleased diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst new file mode 100644 index 0000000..cd22aab --- /dev/null +++ b/releasenotes/source/unreleased.rst @@ -0,0 +1,5 @@ +============================== + Current Series Release Notes +============================== + +.. release-notes:: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..621be2f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pbr>=1.8 # Apache-2.0 +oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 +oslo.cache>=1.5.0 # Apache-2.0 +oslo.concurrency>=3.8.0 # Apache-2.0 +oslo.context>=2.9.0 # Apache-2.0 +oslo.log>=3.11.0 # Apache-2.0 +oslo.reports>=0.6.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 +oslo.db!=4.13.1,!=4.13.2,>=4.11.0 # Apache-2.0 +oslo.rootwrap>=5.0.0 # Apache-2.0 +oslo.messaging>=5.2.0 # Apache-2.0 +oslo.privsep>=1.9.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 +oslo.service>=1.10.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..caa3d5a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,51 @@ +[metadata] +name = cinder-fusioncompute +summary = Implementation of Cinder driver for Huawei Fusioncompute. +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 + Programming Language :: Python :: 3.4 + +[files] +packages = + cinder-fusioncompute + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = cinder-fusioncompute/locale +domain = cinder-fusioncompute + +[update_catalog] +domain = cinder-fusioncompute +output_dir = cinder-fusioncompute/locale +input_file = cinder-fusioncompute/locale/cinder-fusioncompute.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = cinder-fusioncompute/locale/cinder-fusioncompute.pot + +[build_releasenotes] +all_files = 1 +build-dir = releasenotes/build +source-dir = releasenotes/source \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..056c16c --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# 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 +# +# 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..027aaa8 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,17 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +hacking<0.13,>=0.12.0 # Apache-2.0 + +coverage>=4.0 # Apache-2.0 +python-subunit>=0.0.18 # Apache-2.0/BSD +sphinx>=1.2.1,!=1.3b1,<1.4 # BSD +oslosphinx>=4.7.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 +testrepository>=0.0.18 # Apache-2.0/BSD +testscenarios>=0.4 # Apache-2.0/BSD +testtools>=1.4.0 # MIT + +# releasenotes +reno>=1.8.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ec4cd78 --- /dev/null +++ b/tox.ini @@ -0,0 +1,40 @@ +[tox] +minversion = 2.0 +envlist = py34,py27,pypy,pep8 +skipsdist = True + +[testenv] +usedevelop = True +install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} +setenv = + VIRTUAL_ENV={envdir} + PYTHONWARNINGS=default::DeprecationWarning +deps = -r{toxinidir}/test-requirements.txt +commands = python setup.py test --slowest --testr-args='{posargs}' + +[testenv:pep8] +commands = flake8 {posargs} + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = python setup.py test --coverage --testr-args='{posargs}' + +[testenv:docs] +commands = python setup.py build_sphinx + +[testenv:releasenotes] +commands = + sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + +[testenv:debug] +commands = oslo_debug_helper {posargs} + +[flake8] +# E123, E125 skipped as they are invalid PEP-8. + +show-source = True +ignore = E123,E125,N342,H104,W391 +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build