Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ied3613a9c0bcd2394825d18fdfc92dd8b7b0431b
This commit is contained in:
parent
aced6a97f9
commit
cc877dec6d
@ -1,7 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = os_win
|
||||
omit = os_win/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
54
.gitignore
vendored
54
.gitignore
vendored
@ -1,54 +0,0 @@
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
.eggs
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.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?
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/os-win.git
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
@ -1,7 +0,0 @@
|
||||
[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
|
@ -1,17 +0,0 @@
|
||||
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/os-win
|
@ -1,4 +0,0 @@
|
||||
os-win Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
175
LICENSE
175
LICENSE
@ -1,175 +0,0 @@
|
||||
|
||||
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.
|
@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
14
README
Normal file
14
README
Normal file
@ -0,0 +1,14 @@
|
||||
This project is no longer maintained.
|
||||
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
For ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
89
README.rst
89
README.rst
@ -1,89 +0,0 @@
|
||||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
|
||||
.. image:: https://governance.openstack.org/badges/os-win.svg
|
||||
:target: https://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
======
|
||||
os-win
|
||||
======
|
||||
|
||||
Windows / Hyper-V library for OpenStack projects.
|
||||
|
||||
This library contains Windows / Hyper-V specific code commonly used in
|
||||
OpenStack projects. The library can be used in any other OpenStack projects
|
||||
where it is needed.
|
||||
|
||||
* Free software: Apache license
|
||||
* Documentation: http://docs.openstack.org/developer/os-win
|
||||
* Source: http://git.openstack.org/cgit/openstack/os-win
|
||||
* Bugs: http://bugs.launchpad.net/os-win
|
||||
|
||||
|
||||
How to Install
|
||||
--------------
|
||||
|
||||
os-win is released on Pypi, meaning that it can be installed and upgraded via
|
||||
pip. To install os-win, run the following command:
|
||||
|
||||
::
|
||||
|
||||
pip install os-win
|
||||
|
||||
To upgrade os-win, run the following command:
|
||||
|
||||
::
|
||||
|
||||
pip install -U os-win
|
||||
|
||||
Note that the first OpenStack release to use os-win is Mitaka. Previous
|
||||
releases do not benefit from this library.
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
You will have to install the test dependencies first to be able to run the
|
||||
tests.
|
||||
|
||||
::
|
||||
|
||||
C:\os_win> pip install -r requirements.txt
|
||||
C:\os_win> pip install -r test-requirements.txt
|
||||
|
||||
You can run the unit tests with the following command.
|
||||
|
||||
::
|
||||
|
||||
C:\os_win> nosetests os_win\tests\unit
|
||||
|
||||
|
||||
How to contribute
|
||||
-----------------
|
||||
|
||||
To contribute to this project, please go through the following steps.
|
||||
|
||||
1. Clone the project and keep your working tree updated.
|
||||
2. Make modifications on your working tree.
|
||||
3. Run unit tests.
|
||||
4. If the tests pass, commit your code.
|
||||
5. Submit your code via ``git review``.
|
||||
6. Check that Jenkins and the Microsoft Hyper-V CI pass on your patch.
|
||||
7. If there are issues with your commit, ammend, and submit it again via
|
||||
``git review``.
|
||||
8. Wait for the patch to be reviewed.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
os-win is currently used in the following OpenStack projects:
|
||||
|
||||
* nova
|
||||
* cinder
|
||||
* compute-hyperv
|
||||
* networking-hyperv
|
||||
* ceilometer
|
||||
* os-brick
|
@ -1,75 +0,0 @@
|
||||
# -*- 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 = 'os-win'
|
||||
copyright = '2015, Cloudbase Solutions Srl'
|
||||
|
||||
# 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,
|
||||
'%s Documentation' % project,
|
||||
'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
# intersphinx_mapping = {'http://docs.python.org/': None}
|
@ -1,4 +0,0 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
@ -1,25 +0,0 @@
|
||||
.. os-win documentation master file, created by
|
||||
sphinx-quickstart on Tue Jul 9 22:26:36 2015.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to os-win's documentation!
|
||||
========================================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
@ -1,12 +0,0 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install os-win
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv os-win
|
||||
$ pip install os-win
|
@ -1 +0,0 @@
|
||||
.. include:: ../../README.rst
|
@ -1,7 +0,0 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use os-win in a project::
|
||||
|
||||
import os_win
|
@ -1,35 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 sys
|
||||
|
||||
from eventlet import patcher
|
||||
import pbr.version
|
||||
|
||||
from os_win.utils.winapi import libs as w_libs
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'os_win').version_string()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
# We need to make sure that WMI uses the unpatched threading module.
|
||||
wmi.threading = patcher.original('threading')
|
||||
|
||||
# The following will set the argument and return value types for the
|
||||
# foreign functions used throughout os_win using ctypes.
|
||||
w_libs.register()
|
@ -1,442 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 ast
|
||||
import os
|
||||
import re
|
||||
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
|
||||
"""
|
||||
Guidelines for writing new hacking checks
|
||||
|
||||
- Use only for os_win specific tests. OpenStack general tests
|
||||
should be submitted to the common 'hacking' module.
|
||||
- Pick numbers in the range N3xx. Find the current test with
|
||||
the highest allocated number and then pick the next value.
|
||||
- Keep the test method code in the source file ordered based
|
||||
on the N3xx value.
|
||||
- List the new rule in the top level HACKING.rst file
|
||||
"""
|
||||
|
||||
UNDERSCORE_IMPORT_FILES = []
|
||||
|
||||
cfg_re = re.compile(r".*\scfg\.")
|
||||
asse_trueinst_re = re.compile(
|
||||
r"(.)*assertTrue\(isinstance\((\w|\.|\'|\"|\[|\])+, "
|
||||
"(\w|\.|\'|\"|\[|\])+\)\)")
|
||||
asse_equal_type_re = re.compile(
|
||||
r"(.)*assertEqual\(type\((\w|\.|\'|\"|\[|\])+\), "
|
||||
"(\w|\.|\'|\"|\[|\])+\)")
|
||||
asse_equal_in_end_with_true_or_false_re = re.compile(
|
||||
r"assertEqual\("
|
||||
r"(\w|[][.'\"])+ in (\w|[][.'\", ])+, (True|False)\)")
|
||||
asse_equal_in_start_with_true_or_false_re = re.compile(
|
||||
r"assertEqual\("
|
||||
r"(True|False), (\w|[][.'\"])+ in (\w|[][.'\", ])+\)")
|
||||
asse_equal_end_with_none_re = re.compile(
|
||||
r"assertEqual\(.*?,\s+None\)$")
|
||||
asse_equal_start_with_none_re = re.compile(
|
||||
r"assertEqual\(None,")
|
||||
asse_true_false_with_in_or_not_in = re.compile(
|
||||
r"assert(True|False)\("
|
||||
r"(\w|[][.'\"])+( not)? in (\w|[][.'\",])+(, .*)?\)")
|
||||
asse_true_false_with_in_or_not_in_spaces = re.compile(
|
||||
r"assert(True|False)"
|
||||
r"\((\w|[][.'\"])+( not)? in [\[|'|\"](\w|[][.'\", ])+"
|
||||
r"[\[|'|\"](, .*)?\)")
|
||||
asse_raises_regexp = re.compile(r"assertRaisesRegexp\(")
|
||||
conf_attribute_set_re = re.compile(r"CONF\.[a-z0-9_.]+\s*=\s*\w")
|
||||
_all_log_levels = {'critical', 'error', 'exception', 'info',
|
||||
'warning', 'debug'}
|
||||
# Since _Lx() have been removed, we just need to check _()
|
||||
_log_translation_hint = re.compile(
|
||||
r".*LOG\.(%(levels)s)\(\s*(%(hints)s)\(" % {
|
||||
'levels': '|'.join(_all_log_levels),
|
||||
'hints': '_',
|
||||
})
|
||||
mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
|
||||
string_translation = re.compile(r"[^_]*_\(\s*('|\")")
|
||||
underscore_import_check = re.compile(r"(.)*import _(.)*")
|
||||
import_translation_for_log_or_exception = re.compile(
|
||||
r"(.)*(from\sos_win._i18n\simport)\s_")
|
||||
# We need this for cases where they have created their own _ function.
|
||||
custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*")
|
||||
dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)")
|
||||
ctypes_external_lib_re = re.compile(r"ctypes\.(?:win|c|py|ole)dll",
|
||||
re.IGNORECASE)
|
||||
ctypes_func_typedefs_re = re.compile(
|
||||
r"(?:^|[^\w])(%s)\.(\w+)" % '|'.join(w_lib.libs),
|
||||
re.IGNORECASE)
|
||||
|
||||
_module_src_cache = {}
|
||||
|
||||
|
||||
class BaseASTChecker(ast.NodeVisitor):
|
||||
"""Provides a simple framework for writing AST-based checks.
|
||||
|
||||
Subclasses should implement visit_* methods like any other AST visitor
|
||||
implementation. When they detect an error for a particular node the
|
||||
method should call ``self.add_error(offending_node)``. Details about
|
||||
where in the code the error occurred will be pulled from the node
|
||||
object.
|
||||
|
||||
Subclasses should also provide a class variable named CHECK_DESC to
|
||||
be used for the human readable error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, tree, filename):
|
||||
"""This object is created automatically by pep8.
|
||||
|
||||
:param tree: an AST tree
|
||||
:param filename: name of the file being analyzed
|
||||
(ignored by our checks)
|
||||
"""
|
||||
self._tree = tree
|
||||
self._errors = []
|
||||
|
||||
def run(self):
|
||||
"""Called automatically by pep8."""
|
||||
self.visit(self._tree)
|
||||
return self._errors
|
||||
|
||||
def add_error(self, node, message=None):
|
||||
"""Add an error caused by a node to the list of errors for pep8."""
|
||||
message = message or self.CHECK_DESC
|
||||
error = (node.lineno, node.col_offset, message, self.__class__)
|
||||
self._errors.append(error)
|
||||
|
||||
def _check_call_names(self, call_node, names):
|
||||
if isinstance(call_node, ast.Call):
|
||||
if isinstance(call_node.func, ast.Name):
|
||||
if call_node.func.id in names:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def use_timeutils_utcnow(logical_line, filename):
|
||||
# tools are OK to use the standard datetime module
|
||||
if "/tools/" in filename:
|
||||
return
|
||||
|
||||
msg = "N310: timeutils.utcnow() must be used instead of datetime.%s()"
|
||||
|
||||
datetime_funcs = ['now', 'utcnow']
|
||||
for f in datetime_funcs:
|
||||
pos = logical_line.find('datetime.%s' % f)
|
||||
if pos != -1:
|
||||
yield (pos, msg % f)
|
||||
|
||||
|
||||
def capital_cfg_help(logical_line, tokens):
|
||||
msg = "N313: capitalize help string"
|
||||
|
||||
if cfg_re.match(logical_line):
|
||||
for t in range(len(tokens)):
|
||||
if tokens[t][1] == "help":
|
||||
txt = tokens[t + 2][1]
|
||||
if len(txt) > 1 and txt[1].islower():
|
||||
yield(0, msg)
|
||||
|
||||
|
||||
def assert_true_instance(logical_line):
|
||||
"""Check for assertTrue(isinstance(a, b)) sentences
|
||||
|
||||
N316
|
||||
"""
|
||||
if asse_trueinst_re.match(logical_line):
|
||||
yield (0, "N316: assertTrue(isinstance(a, b)) sentences not allowed")
|
||||
|
||||
|
||||
def assert_equal_type(logical_line):
|
||||
"""Check for assertEqual(type(A), B) sentences
|
||||
|
||||
N317
|
||||
"""
|
||||
if asse_equal_type_re.match(logical_line):
|
||||
yield (0, "N317: assertEqual(type(A), B) sentences not allowed")
|
||||
|
||||
|
||||
def assert_equal_none(logical_line):
|
||||
"""Check for assertEqual(A, None) or assertEqual(None, A) sentences
|
||||
|
||||
N318
|
||||
"""
|
||||
res = (asse_equal_start_with_none_re.search(logical_line) or
|
||||
asse_equal_end_with_none_re.search(logical_line))
|
||||
if res:
|
||||
yield (0, "N318: assertEqual(A, None) or assertEqual(None, A) "
|
||||
"sentences not allowed")
|
||||
|
||||
|
||||
def no_translate_logs(logical_line):
|
||||
"""Check for 'LOG.*(_('
|
||||
|
||||
Starting with the Pike series, OpenStack no longer supports log
|
||||
translation. We shouldn't translate logs.
|
||||
|
||||
- This check assumes that 'LOG' is a logger.
|
||||
- Use filename so we can start enforcing this in specific folders
|
||||
instead of needing to do so all at once.
|
||||
|
||||
C312
|
||||
"""
|
||||
if _log_translation_hint.match(logical_line):
|
||||
yield(0, "C312: Log messages should not be translated!")
|
||||
|
||||
|
||||
def no_import_translation_in_tests(logical_line, filename):
|
||||
"""Check for 'from os_win._i18n import _'
|
||||
|
||||
N337
|
||||
"""
|
||||
|
||||
if 'os_win/tests/' in filename:
|
||||
res = import_translation_for_log_or_exception.match(logical_line)
|
||||
if res:
|
||||
yield(0, "N337 Don't import translation in tests")
|
||||
|
||||
|
||||
def no_setting_conf_directly_in_tests(logical_line, filename):
|
||||
"""Check for setting CONF.* attributes directly in tests
|
||||
|
||||
The value can leak out of tests affecting how subsequent tests run.
|
||||
Using self.flags(option=value) is the preferred method to temporarily
|
||||
set config options in tests.
|
||||
|
||||
N320
|
||||
"""
|
||||
|
||||
if 'os_win/tests/' in filename:
|
||||
res = conf_attribute_set_re.match(logical_line)
|
||||
if res:
|
||||
yield (0, "N320: Setting CONF.* attributes directly in tests is "
|
||||
"forbidden. Use self.flags(option=value) instead")
|
||||
|
||||
|
||||
def no_mutable_default_args(logical_line):
|
||||
msg = "N322: Method's default argument shouldn't be mutable!"
|
||||
if mutable_default_args.match(logical_line):
|
||||
yield (0, msg)
|
||||
|
||||
|
||||
def check_explicit_underscore_import(logical_line, filename):
|
||||
"""Check for explicit import of the _ function
|
||||
|
||||
We need to ensure that any files that are using the _() function
|
||||
to translate logs are explicitly importing the _ function. We
|
||||
can't trust unit test to catch whether the import has been
|
||||
added so we need to check for it here.
|
||||
"""
|
||||
|
||||
# Build a list of the files that have _ imported. No further
|
||||
# checking needed once it is found.
|
||||
if filename in UNDERSCORE_IMPORT_FILES:
|
||||
pass
|
||||
elif (underscore_import_check.match(logical_line) or
|
||||
custom_underscore_check.match(logical_line)):
|
||||
UNDERSCORE_IMPORT_FILES.append(filename)
|
||||
elif string_translation.match(logical_line):
|
||||
yield(0, "N323: Found use of _() without explicit import of _ !")
|
||||
|
||||
|
||||
def use_jsonutils(logical_line, filename):
|
||||
# tools are OK to use the standard json module
|
||||
if "/tools/" in filename:
|
||||
return
|
||||
|
||||
msg = "N324: jsonutils.%(fun)s must be used instead of json.%(fun)s"
|
||||
|
||||
if "json." in logical_line:
|
||||
json_funcs = ['dumps(', 'dump(', 'loads(', 'load(']
|
||||
for f in json_funcs:
|
||||
pos = logical_line.find('json.%s' % f)
|
||||
if pos != -1:
|
||||
yield (pos, msg % {'fun': f[:-1]})
|
||||
|
||||
|
||||
class CheckForStrUnicodeExc(BaseASTChecker):
|
||||
"""Checks for the use of str() or unicode() on an exception.
|
||||
|
||||
This currently only handles the case where str() or unicode()
|
||||
is used in the scope of an exception handler. If the exception
|
||||
is passed into a function, returned from an assertRaises, or
|
||||
used on an exception created in the same scope, this does not
|
||||
catch it.
|
||||
"""
|
||||
|
||||
CHECK_DESC = ('N325 str() and unicode() cannot be used on an '
|
||||
'exception. Remove or use six.text_type()')
|
||||
|
||||
def __init__(self, tree, filename):
|
||||
super(CheckForStrUnicodeExc, self).__init__(tree, filename)
|
||||
self.name = []
|
||||
self.already_checked = []
|
||||
|
||||
def visit_TryExcept(self, node):
|
||||
for handler in node.handlers:
|
||||
if handler.name:
|
||||
self.name.append(handler.name.id)
|
||||
super(CheckForStrUnicodeExc, self).generic_visit(node)
|
||||
self.name = self.name[:-1]
|
||||
else:
|
||||
super(CheckForStrUnicodeExc, self).generic_visit(node)
|
||||
|
||||
def visit_Call(self, node):
|
||||
if self._check_call_names(node, ['str', 'unicode']):
|
||||
if node not in self.already_checked:
|
||||
self.already_checked.append(node)
|
||||
if isinstance(node.args[0], ast.Name):
|
||||
if node.args[0].id in self.name:
|
||||
self.add_error(node.args[0])
|
||||
super(CheckForStrUnicodeExc, self).generic_visit(node)
|
||||
|
||||
|
||||
class CheckForTransAdd(BaseASTChecker):
|
||||
"""Checks for the use of concatenation on a translated string.
|
||||
|
||||
Translations should not be concatenated with other strings, but
|
||||
should instead include the string being added to the translated
|
||||
string to give the translators the most information.
|
||||
"""
|
||||
|
||||
CHECK_DESC = ('N326 Translated messages cannot be concatenated. '
|
||||
'String should be included in translated message.')
|
||||
|
||||
TRANS_FUNC = ['_', '_LI', '_LW', '_LE', '_LC']
|
||||
|
||||
def visit_BinOp(self, node):
|
||||
if isinstance(node.op, ast.Add):
|
||||
if self._check_call_names(node.left, self.TRANS_FUNC):
|
||||
self.add_error(node.left)
|
||||
elif self._check_call_names(node.right, self.TRANS_FUNC):
|
||||
self.add_error(node.right)
|
||||
super(CheckForTransAdd, self).generic_visit(node)
|
||||
|
||||
|
||||
def assert_true_or_false_with_in(logical_line):
|
||||
"""Check for assertTrue/False(A in B), assertTrue/False(A not in B),
|
||||
|
||||
assertTrue/False(A in B, message) or assertTrue/False(A not in B, message)
|
||||
sentences.
|
||||
|
||||
N334
|
||||
"""
|
||||
|
||||
res = (asse_true_false_with_in_or_not_in.search(logical_line) or
|
||||
asse_true_false_with_in_or_not_in_spaces.search(logical_line))
|
||||
if res:
|
||||
yield (0, "N334: Use assertIn/NotIn(A, B) rather than "
|
||||
"assertTrue/False(A in/not in B) when checking collection "
|
||||
"contents.")
|
||||
|
||||
|
||||
def assert_raises_regexp(logical_line):
|
||||
"""Check for usage of deprecated assertRaisesRegexp
|
||||
|
||||
N335
|
||||
"""
|
||||
|
||||
res = asse_raises_regexp.search(logical_line)
|
||||
if res:
|
||||
yield (0, "N335: assertRaisesRegex must be used instead "
|
||||
"of assertRaisesRegexp")
|
||||
|
||||
|
||||
def dict_constructor_with_list_copy(logical_line):
|
||||
msg = ("N336: Must use a dict comprehension instead of a dict constructor"
|
||||
" with a sequence of key-value pairs."
|
||||
)
|
||||
if dict_constructor_with_list_copy_re.match(logical_line):
|
||||
yield (0, msg)
|
||||
|
||||
|
||||
def assert_equal_in(logical_line):
|
||||
"""Check for assertEqual(A in B, True), assertEqual(True, A in B),
|
||||
|
||||
assertEqual(A in B, False) or assertEqual(False, A in B) sentences
|
||||
|
||||
N338
|
||||
"""
|
||||
|
||||
res = (asse_equal_in_start_with_true_or_false_re.search(logical_line) or
|
||||
asse_equal_in_end_with_true_or_false_re.search(logical_line))
|
||||
if res:
|
||||
yield (0, "N338: Use assertIn/NotIn(A, B) rather than "
|
||||
"assertEqual(A in B, True/False) when checking collection "
|
||||
"contents.")
|
||||
|
||||
|
||||
def assert_ctypes_libs_not_used_directly(logical_line, filename):
|
||||
# We allow this only for the modules containing the library definitions.
|
||||
w_lib_path = os.path.join(*w_lib.__name__.split('.'))
|
||||
|
||||
if w_lib_path in filename:
|
||||
return
|
||||
|
||||
res = ctypes_external_lib_re.search(logical_line)
|
||||
if res:
|
||||
yield (0, "O301: Using external libraries via ctypes directly "
|
||||
"is not allowed. Please use the following function to "
|
||||
"retrieve a supported library handle: "
|
||||
"%s.get_shared_lib_handle" % w_lib.__name__)
|
||||
|
||||
|
||||
def _get_module_src(path):
|
||||
if not _module_src_cache.get(path):
|
||||
with open(path, 'r') as f:
|
||||
_module_src_cache[path] = f.read()
|
||||
|
||||
return _module_src_cache[path]
|
||||
|
||||
|
||||
def assert_ctypes_foreign_func_argtypes_defined(logical_line):
|
||||
res = ctypes_func_typedefs_re.findall(logical_line)
|
||||
|
||||
for lib_name, func_name in res:
|
||||
mod_path = "%s.py" % os.path.join(os.path.dirname(w_lib.__file__),
|
||||
lib_name)
|
||||
module_src = _get_module_src(mod_path)
|
||||
|
||||
argtypes_expr = "%s.argtypes =" % func_name
|
||||
restype_expr = "%s.restype =" % func_name
|
||||
|
||||
if not (argtypes_expr in module_src and restype_expr in module_src):
|
||||
yield (0, "O302: Foreign function called using ctypes without "
|
||||
"having its argument and return value types declared "
|
||||
"in %s.%s.py." % (w_lib.__name__, lib_name))
|
||||
|
||||
|
||||
def factory(register):
|
||||
register(use_timeutils_utcnow)
|
||||
register(capital_cfg_help)
|
||||
register(no_import_translation_in_tests)
|
||||
register(assert_true_instance)
|
||||
register(assert_equal_type)
|
||||
register(assert_equal_none)
|
||||
register(assert_raises_regexp)
|
||||
register(no_translate_logs)
|
||||
register(no_setting_conf_directly_in_tests)
|
||||
register(no_mutable_default_args)
|
||||
register(check_explicit_underscore_import)
|
||||
register(use_jsonutils)
|
||||
register(CheckForStrUnicodeExc)
|
||||
register(CheckForTransAdd)
|
||||
register(assert_true_or_false_with_in)
|
||||
register(dict_constructor_with_list_copy)
|
||||
register(assert_equal_in)
|
||||
register(assert_ctypes_libs_not_used_directly)
|
||||
register(assert_ctypes_foreign_func_argtypes_defined)
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='os_win')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
244
os_win/_utils.py
244
os_win/_utils.py
@ -1,244 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 ctypes
|
||||
import time
|
||||
import types
|
||||
|
||||
import eventlet
|
||||
from eventlet import tpool
|
||||
import netaddr
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import reflection
|
||||
import six
|
||||
|
||||
from os_win import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
socket = eventlet.import_patched('socket')
|
||||
synchronized = lockutils.synchronized_with_prefix('oswin-')
|
||||
|
||||
_WBEM_E_NOT_FOUND = 0x80041002
|
||||
|
||||
|
||||
def execute(*cmd, **kwargs):
|
||||
"""Convenience wrapper around oslo's execute() method."""
|
||||
return processutils.execute(*cmd, **kwargs)
|
||||
|
||||
|
||||
def parse_server_string(server_str):
|
||||
"""Parses the given server_string and returns a tuple of host and port.
|
||||
|
||||
If it's not a combination of host part and port, the port element
|
||||
is an empty string. If the input is invalid expression, return a tuple of
|
||||
two empty strings.
|
||||
"""
|
||||
|
||||
try:
|
||||
# First of all, exclude pure IPv6 address (w/o port).
|
||||
if netaddr.valid_ipv6(server_str):
|
||||
return (server_str, '')
|
||||
|
||||
# Next, check if this is IPv6 address with a port number combination.
|
||||
if server_str.find("]:") != -1:
|
||||
(address, port) = server_str.replace('[', '', 1).split(']:')
|
||||
return (address, port)
|
||||
|
||||
# Third, check if this is a combination of an address and a port
|
||||
if server_str.find(':') == -1:
|
||||
return (server_str, '')
|
||||
|
||||
# This must be a combination of an address and a port
|
||||
(address, port) = server_str.split(':')
|
||||
return (address, port)
|
||||
|
||||
except (ValueError, netaddr.AddrFormatError):
|
||||
LOG.error('Invalid server_string: %s', server_str)
|
||||
return ('', '')
|
||||
|
||||
|
||||
def get_wrapped_function(function):
|
||||
"""Get the method at the bottom of a stack of decorators."""
|
||||
if not hasattr(function, '__closure__') or not function.__closure__:
|
||||
return function
|
||||
|
||||
def _get_wrapped_function(function):
|
||||
if not hasattr(function, '__closure__') or not function.__closure__:
|
||||
return None
|
||||
|
||||
for closure in function.__closure__:
|
||||
func = closure.cell_contents
|
||||
|
||||
deeper_func = _get_wrapped_function(func)
|
||||
if deeper_func:
|
||||
return deeper_func
|
||||
elif isinstance(closure.cell_contents, types.FunctionType):
|
||||
return closure.cell_contents
|
||||
|
||||
return _get_wrapped_function(function)
|
||||
|
||||
|
||||
def retry_decorator(max_retry_count=5, timeout=None, inc_sleep_time=1,
|
||||
max_sleep_time=1, exceptions=(), error_codes=(),
|
||||
pass_retry_context=False):
|
||||
"""Retries invoking the decorated method in case of expected exceptions.
|
||||
|
||||
:param max_retry_count: The maximum number of retries performed. If 0, no
|
||||
retry is performed. If None, there will be no limit
|
||||
on the number of retries.
|
||||
:param timeout: The maximum time for which we'll retry invoking the method.
|
||||
If 0 or None, there will be no time limit.
|
||||
:param inc_sleep_time: The time sleep increment used between retries.
|
||||
:param max_sleep_time: The maximum time to wait between retries.
|
||||
:param exceptions: A list of expected exceptions for which retries will be
|
||||
performed.
|
||||
:param error_codes: A list of expected error codes. The error code is
|
||||
retrieved from the 'error_code' exception attribute,
|
||||
for example in case of Win32Exception. If this argument
|
||||
is not passed, retries will be performed for any of the
|
||||
expected exceptions.
|
||||
:param pass_retry_context: Convenient way of letting a method aware of
|
||||
this decorator prevent a retry from being
|
||||
performed. The decorated method must accept an
|
||||
argument called 'retry_context', which will
|
||||
include a dict containing the 'prevent_retry'
|
||||
field. If this field is set, no further retries
|
||||
will be performed.
|
||||
"""
|
||||
|
||||
if isinstance(error_codes, six.integer_types):
|
||||
error_codes = (error_codes, )
|
||||
|
||||
def wrapper(f):
|
||||
def inner(*args, **kwargs):
|
||||
try_count = 0
|
||||
sleep_time = 0
|
||||
time_start = time.time()
|
||||
|
||||
retry_context = dict(prevent_retry=False)
|
||||
if pass_retry_context:
|
||||
kwargs['retry_context'] = retry_context
|
||||
|
||||
while True:
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except exceptions as exc:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
err_code = getattr(exc, 'error_code', None)
|
||||
expected_err_code = (err_code in error_codes or not
|
||||
error_codes)
|
||||
|
||||
time_elapsed = time.time() - time_start
|
||||
time_left = (timeout - time_elapsed
|
||||
if timeout else 'undefined')
|
||||
tries_left = (max_retry_count - try_count
|
||||
if max_retry_count is not None
|
||||
else 'undefined')
|
||||
|
||||
should_retry = (
|
||||
not retry_context['prevent_retry'] and
|
||||
expected_err_code and
|
||||
tries_left and
|
||||
(time_left == 'undefined' or
|
||||
time_left > 0))
|
||||
ctxt.reraise = not should_retry
|
||||
|
||||
if should_retry:
|
||||
try_count += 1
|
||||
func_name = reflection.get_callable_name(f)
|
||||
|
||||
sleep_time = min(sleep_time + inc_sleep_time,
|
||||
max_sleep_time)
|
||||
if timeout:
|
||||
sleep_time = min(sleep_time, time_left)
|
||||
|
||||
LOG.debug("Got expected exception %(exc)s while "
|
||||
"calling function %(func_name)s. "
|
||||
"Retries left: %(retries_left)s. "
|
||||
"Time left: %(time_left)s. "
|
||||
"Time elapsed: %(time_elapsed)s "
|
||||
"Retrying in %(sleep_time)s seconds.",
|
||||
dict(exc=exc,
|
||||
func_name=func_name,
|
||||
retries_left=tries_left,
|
||||
time_left=time_left,
|
||||
time_elapsed=time_elapsed,
|
||||
sleep_time=sleep_time))
|
||||
time.sleep(sleep_time)
|
||||
return inner
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_ips(addr):
|
||||
addr_info = socket.getaddrinfo(addr, None, 0, 0, 0)
|
||||
# Returns IPv4 and IPv6 addresses, ordered by protocol family
|
||||
addr_info.sort()
|
||||
return [a[4][0] for a in addr_info]
|
||||
|
||||
|
||||
def avoid_blocking_call(f, *args, **kwargs):
|
||||
"""Ensures that the invoked method will not block other greenthreads.
|
||||
|
||||
Performs the call in a different thread using tpool.execute when called
|
||||
from a greenthread.
|
||||
"""
|
||||
# Note that eventlet.getcurrent will always return a greenlet object.
|
||||
# In case of a greenthread, the parent greenlet will always be the hub
|
||||
# loop greenlet.
|
||||
if eventlet.getcurrent().parent:
|
||||
return tpool.execute(f, *args, **kwargs)
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
|
||||
def avoid_blocking_call_decorator(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
return avoid_blocking_call(f, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_com_error_hresult(com_error):
|
||||
try:
|
||||
return ctypes.c_uint(com_error.excepinfo[5]).value
|
||||
except Exception:
|
||||
LOG.debug("Unable to retrieve COM error hresult: %s", com_error)
|
||||
|
||||
|
||||
def _is_not_found_exc(exc):
|
||||
hresult = get_com_error_hresult(exc.com_error)
|
||||
return hresult == _WBEM_E_NOT_FOUND
|
||||
|
||||
|
||||
def not_found_decorator(translated_exc=exceptions.NotFound):
|
||||
"""Wraps x_wmi: Not Found exceptions as os_win.exceptions.NotFound."""
|
||||
|
||||
def wrapper(func):
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exceptions.x_wmi as ex:
|
||||
if _is_not_found_exc(ex):
|
||||
LOG.debug('x_wmi: Not Found exception raised while '
|
||||
'running %s', func.__name__)
|
||||
raise translated_exc(message=six.text_type(ex))
|
||||
raise
|
||||
return inner
|
||||
return wrapper
|
@ -1,33 +0,0 @@
|
||||
# Copyright 2017 Cloudbase Solutions Srl
|
||||
# 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
|
||||
|
||||
os_win_opts = [
|
||||
cfg.StrOpt('hbaapi_lib_path',
|
||||
default='hbaapi.dll',
|
||||
help='Fibre Channel hbaapi library path. If no custom hbaapi '
|
||||
'library is requested, the default one will be used.'),
|
||||
cfg.BoolOpt('cache_temporary_wmi_objects',
|
||||
default=True,
|
||||
help='Caches temporary WMI objects in order to increase '
|
||||
'performance. This only affects networkutils, where '
|
||||
'almost all operations require a reference to a '
|
||||
'switch port. The cached objects are no longer valid '
|
||||
'if the VM they are associated with is destroyed.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(os_win_opts, 'os_win')
|
@ -1,214 +0,0 @@
|
||||
# Copyright 2012 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Hyper-V / Windows related constants.
|
||||
"""
|
||||
|
||||
HYPERV_VM_STATE_OTHER = 1
|
||||
HYPERV_VM_STATE_ENABLED = 2
|
||||
HYPERV_VM_STATE_DISABLED = 3
|
||||
HYPERV_VM_STATE_SHUTTING_DOWN = 4
|
||||
HYPERV_VM_STATE_REBOOT = 10
|
||||
HYPERV_VM_STATE_PAUSED = 32768
|
||||
HYPERV_VM_STATE_SUSPENDED = 32769
|
||||
|
||||
|
||||
WMI_JOB_STATUS_STARTED = 4096
|
||||
WMI_JOB_STATE_RUNNING = 4
|
||||
WMI_JOB_STATE_COMPLETED = 7
|
||||
|
||||
VM_SUMMARY_NUM_PROCS = 4
|
||||
VM_SUMMARY_ENABLED_STATE = 100
|
||||
VM_SUMMARY_MEMORY_USAGE = 103
|
||||
VM_SUMMARY_UPTIME = 105
|
||||
|
||||
|
||||
ARCH_I686 = 0
|
||||
ARCH_MIPS = 1
|
||||
ARCH_ALPHA = 2
|
||||
ARCH_PPC = 3
|
||||
ARCH_ARMV7 = 5
|
||||
ARCH_IA64 = 6
|
||||
ARCH_X86_64 = 9
|
||||
|
||||
|
||||
PROCESSOR_FEATURE = {
|
||||
3: 'mmx',
|
||||
6: 'sse',
|
||||
7: '3dnow',
|
||||
8: 'rdtsc',
|
||||
9: 'pae',
|
||||
10: 'sse2',
|
||||
12: 'nx',
|
||||
13: 'sse3',
|
||||
17: 'xsave',
|
||||
20: 'slat',
|
||||
21: 'vmx',
|
||||
}
|
||||
|
||||
|
||||
CTRL_TYPE_IDE = "IDE"
|
||||
CTRL_TYPE_SCSI = "SCSI"
|
||||
|
||||
DISK = "VHD"
|
||||
DISK_FORMAT = DISK
|
||||
DVD = "DVD"
|
||||
DVD_FORMAT = "ISO"
|
||||
VOLUME = "VOLUME"
|
||||
|
||||
DISK_FORMAT_MAP = {
|
||||
DISK_FORMAT.lower(): DISK,
|
||||
DVD_FORMAT.lower(): DVD
|
||||
}
|
||||
|
||||
DISK_FORMAT_VHD = "VHD"
|
||||
DISK_FORMAT_VHDX = "VHDX"
|
||||
|
||||
VHD_TYPE_FIXED = 2
|
||||
VHD_TYPE_DYNAMIC = 3
|
||||
VHD_TYPE_DIFFERENCING = 4
|
||||
|
||||
SCSI_CONTROLLER_SLOTS_NUMBER = 64
|
||||
IDE_CONTROLLER_SLOTS_NUMBER = 2
|
||||
|
||||
_BDI_DEVICE_TYPE_TO_DRIVE_TYPE = {'disk': DISK,
|
||||
'cdrom': DVD}
|
||||
|
||||
|
||||
HOST_POWER_ACTION_SHUTDOWN = "shutdown"
|
||||
HOST_POWER_ACTION_REBOOT = "reboot"
|
||||
HOST_POWER_ACTION_STARTUP = "startup"
|
||||
|
||||
IMAGE_PROP_VM_GEN = "hw_machine_type"
|
||||
IMAGE_PROP_VM_GEN_1 = "hyperv-gen1"
|
||||
IMAGE_PROP_VM_GEN_2 = "hyperv-gen2"
|
||||
|
||||
VM_GEN_1 = 1
|
||||
VM_GEN_2 = 2
|
||||
|
||||
JOB_STATE_COMPLETED = 7
|
||||
JOB_STATE_TERMINATED = 8
|
||||
JOB_STATE_KILLED = 9
|
||||
JOB_STATE_EXCEPTION = 10
|
||||
JOB_STATE_COMPLETED_WITH_WARNINGS = 32768
|
||||
|
||||
# Special vlan_id value in ovs_vlan_allocations table indicating flat network
|
||||
FLAT_VLAN_ID = -1
|
||||
TRUNK_ENDPOINT_MODE = 5
|
||||
|
||||
TYPE_FLAT = 'flat'
|
||||
TYPE_LOCAL = 'local'
|
||||
TYPE_VLAN = 'vlan'
|
||||
|
||||
SERIAL_CONSOLE_BUFFER_SIZE = 4 << 10
|
||||
MAX_CONSOLE_LOG_FILE_SIZE = 1 << 19 # 512kB
|
||||
|
||||
BOOT_DEVICE_FLOPPY = 0
|
||||
BOOT_DEVICE_CDROM = 1
|
||||
BOOT_DEVICE_HARDDISK = 2
|
||||
BOOT_DEVICE_NETWORK = 3
|
||||
|
||||
ISCSI_NO_AUTH_TYPE = 0
|
||||
ISCSI_CHAP_AUTH_TYPE = 1
|
||||
ISCSI_MUTUAL_CHAP_AUTH_TYPE = 2
|
||||
|
||||
REMOTEFX_MAX_RES_1024x768 = "1024x768"
|
||||
REMOTEFX_MAX_RES_1280x1024 = "1280x1024"
|
||||
REMOTEFX_MAX_RES_1600x1200 = "1600x1200"
|
||||
REMOTEFX_MAX_RES_1920x1200 = "1920x1200"
|
||||
REMOTEFX_MAX_RES_2560x1600 = "2560x1600"
|
||||
REMOTEFX_MAX_RES_3840x2160 = "3840x2160"
|
||||
|
||||
IPV4_DEFAULT = '0.0.0.0'
|
||||
|
||||
# The unattended file used when creating the .pdk file may contain substitution
|
||||
# strings. The substitution string along with their corresponding values will
|
||||
# be passed as metadata and added to a fsk file.
|
||||
# FSK_COMPUTERNAME represents the substitution string for ComputerName and will
|
||||
# set the hostname during vm provisioning.
|
||||
FSK_COMPUTERNAME = 'ComputerName'
|
||||
|
||||
VTPM_SUPPORTED_OS = ['windows']
|
||||
|
||||
# DNSUtils constants
|
||||
DNS_ZONE_TYPE_PRIMARY = 0
|
||||
DNS_ZONE_TYPE_SECONDARY = 1
|
||||
DNS_ZONE_TYPE_STUB = 2
|
||||
DNS_ZONE_TYPE_FORWARD = 3
|
||||
|
||||
DNS_ZONE_NO_UPDATES_ALLOWED = 0
|
||||
DNS_ZONE_SECURE_NONSECURE_UPDATES = 1
|
||||
DNS_ZONE_SECURE_UPDATES_ONLY = 2
|
||||
|
||||
DNS_ZONE_DO_NOT_NOTIFY = 0
|
||||
DNS_ZONE_NOTIFY_NAME_SERVERS_TAB = 1
|
||||
DNS_ZONE_NOTIFY_SPECIFIED_SERVERS = 2
|
||||
|
||||
DNS_ZONE_TRANSFER_ALLOWED_ANY_HOST = 0
|
||||
DNS_ZONE_TRANSFER_ALLOWED_NAME_SERVERS = 1
|
||||
DNS_ZONE_TRANSFER_ALLOWED_SECONDARY_SERVERS = 2
|
||||
DNS_ZONE_TRANSFER_NOT_ALLOWED = 3
|
||||
|
||||
CLUSTER_GROUP_STATE_UNKNOWN = -1
|
||||
CLUSTER_GROUP_ONLINE = 0
|
||||
CLUSTER_GROUP_OFFLINE = 1
|
||||
CLUSTER_GROUP_FAILED = 2
|
||||
CLUSTER_GROUP_PARTIAL_ONLINE = 3
|
||||
CLUSTER_GROUP_PENDING = 4
|
||||
|
||||
EXPORT_CONFIG_SNAPSHOTS_ALL = 0
|
||||
EXPORT_CONFIG_NO_SNAPSHOTS = 1
|
||||
EXPORT_CONFIG_ONE_SNAPSHOT = 2
|
||||
|
||||
# ACE inheritance flags
|
||||
ACE_OBJECT_INHERIT = 0x1
|
||||
ACE_CONTAINER_INHERIT = 0x2
|
||||
ACE_NO_PROPAGATE_INHERIT = 0x4
|
||||
ACE_INHERIT_ONLY = 0x8
|
||||
ACE_INHERITED = 0x10
|
||||
|
||||
# ACE access masks
|
||||
ACE_GENERIC_READ = 0x80000000
|
||||
ACE_GENERIC_WRITE = 0x40000000
|
||||
ACE_GENERIC_EXECUTE = 0x20000000
|
||||
ACE_GENERIC_ALL = 0x10000000
|
||||
|
||||
# ACE access modes
|
||||
ACE_NOT_USED_ACCESS = 0
|
||||
ACE_GRANT_ACCESS = 1
|
||||
ACE_SET_ACCESS = 2
|
||||
ACE_DENY_ACCESS = 3
|
||||
ACE_REVOKE_ACCESS = 4
|
||||
ACE_SET_AUDIT_SUCCESS = 5
|
||||
ACE_SET_AUDIT_FAILURE = 6
|
||||
|
||||
# VLAN operation modes
|
||||
VLAN_MODE_ACCESS = 1
|
||||
VLAN_MODE_TRUNK = 2
|
||||
|
||||
# Action that Hyper-V takes on the VM
|
||||
# when the host is shut down.
|
||||
HOST_SHUTDOWN_ACTION_TURN_OFF = 2
|
||||
HOST_SHUTDOWN_ACTION_SAVE = 3
|
||||
HOST_SHUTDOWN_ACTION_SHUTDOWN = 4
|
||||
|
||||
# VM snapshot types
|
||||
VM_SNAPSHOT_TYPE_DISABLED = 2
|
||||
VM_SNAPSHOT_TYPE_PROD_FALLBACK = 3
|
||||
VM_SNAPSHOT_TYPE_PROD_ENFORCED = 4
|
||||
VM_SNAPSHOT_TYPE_STANDARD = 5
|
||||
|
||||
DEFAULT_WMI_EVENT_TIMEOUT_MS = 2000
|
@ -1,253 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Utility class for VM related operations on Hyper-V.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from os_win._i18n import _
|
||||
|
||||
# Define WMI specific exceptions, so WMI won't have to be imported in any
|
||||
# module that expects those exceptions.
|
||||
if sys.platform == 'win32':
|
||||
from six.moves.builtins import WindowsError
|
||||
import wmi
|
||||
|
||||
x_wmi = wmi.x_wmi
|
||||
x_wmi_timed_out = wmi.x_wmi_timed_out
|
||||
else:
|
||||
class WindowsError(Exception):
|
||||
def __init__(self, winerror=None):
|
||||
self.winerror = winerror
|
||||
|
||||
class x_wmi(Exception):
|
||||
def __init__(self, info='', com_error=None):
|
||||
super(x_wmi, self).__init__(info)
|
||||
self.info = info
|
||||
self.com_error = com_error
|
||||
|
||||
class x_wmi_timed_out(x_wmi):
|
||||
pass
|
||||
|
||||
|
||||
class OSWinException(Exception):
|
||||
msg_fmt = 'An exception has been encountered.'
|
||||
|
||||
def __init__(self, message=None, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self.error_code = kwargs.get('error_code')
|
||||
|
||||
if not message:
|
||||
message = self.msg_fmt % kwargs
|
||||
|
||||
self.message = message
|
||||
super(OSWinException, self).__init__(message)
|
||||
|
||||
|
||||
class NotFound(OSWinException):
|
||||
msg_fmt = _("Resource could not be found: %(resource)s")
|
||||
|
||||
|
||||
class PciDeviceNotFound(NotFound):
|
||||
msg_fmt = _("No assignable PCI device with vendor id: %(vendor_id)s and "
|
||||
"product id: %(product_id)s was found.")
|
||||
|
||||
|
||||
class HyperVException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
# TODO(alexpilotti): Add a storage exception base class
|
||||
class VHDResizeException(HyperVException):
|
||||
msg_fmt = _("Exception encountered while resizing the VHD %(vhd_path)s."
|
||||
"Reason: %(reason)s")
|
||||
|
||||
|
||||
class HyperVAuthorizationException(HyperVException):
|
||||
msg_fmt = _("The Windows account running nova-compute on this Hyper-V "
|
||||
"host doesn't have the required permissions to perform "
|
||||
"Hyper-V related operations.")
|
||||
|
||||
|
||||
class HyperVVMNotFoundException(NotFound, HyperVException):
|
||||
msg_fmt = _("VM not found: %(vm_name)s")
|
||||
|
||||
|
||||
class HyperVPortNotFoundException(NotFound, HyperVException):
|
||||
msg_fmt = _("Switch port not found: %(port_name)s")
|
||||
|
||||
|
||||
class HyperVvNicNotFound(NotFound, HyperVException):
|
||||
msg_fmt = _("vNic not found: %(vnic_name)s")
|
||||
|
||||
|
||||
class Invalid(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidParameterValue(Invalid):
|
||||
msg_fmt = _("Invalid parameter value for: "
|
||||
"%(param_name)s=%(param_value)s")
|
||||
|
||||
|
||||
class SMBException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class Win32Exception(OSWinException):
|
||||
msg_fmt = _("Executing Win32 API function %(func_name)s failed. "
|
||||
"Error code: %(error_code)s. "
|
||||
"Error message: %(error_message)s")
|
||||
|
||||
|
||||
class VHDException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class VHDWin32APIException(VHDException, Win32Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FCException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class FCWin32Exception(FCException, Win32Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WMIException(OSWinException):
|
||||
def __init__(self, message=None, wmi_exc=None):
|
||||
if wmi_exc:
|
||||
try:
|
||||
wmi_exc_message = wmi_exc.com_error.excepinfo[2].strip()
|
||||
message = "%s WMI exception message: %s" % (message,
|
||||
wmi_exc_message)
|
||||
except AttributeError:
|
||||
pass
|
||||
except IndexError:
|
||||
pass
|
||||
super(WMIException, self).__init__(message)
|
||||
|
||||
|
||||
class WqlException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class ISCSITargetException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class ISCSITargetWMIException(ISCSITargetException, WMIException):
|
||||
pass
|
||||
|
||||
|
||||
class ISCSIInitiatorAPIException(Win32Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ISCSILunNotAvailable(ISCSITargetException):
|
||||
msg_fmt = _("Could not find lun %(target_lun)s "
|
||||
"for iSCSI target %(target_iqn)s.")
|
||||
|
||||
|
||||
class Win32IOException(Win32Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DiskNotFound(NotFound):
|
||||
pass
|
||||
|
||||
|
||||
class HyperVRemoteFXException(HyperVException):
|
||||
pass
|
||||
|
||||
|
||||
class HyperVClusterException(HyperVException):
|
||||
pass
|
||||
|
||||
|
||||
class DNSException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class DNSZoneNotFound(NotFound, DNSException):
|
||||
msg_fmt = _("DNS Zone not found: %(zone_name)s")
|
||||
|
||||
|
||||
class DNSZoneAlreadyExists(DNSException):
|
||||
msg_fmt = _("DNS Zone already exists: %(zone_name)s")
|
||||
|
||||
|
||||
class WMIJobFailed(HyperVException):
|
||||
msg_fmt = _("WMI job failed with status %(job_state)s. "
|
||||
"Error summary description: %(error_summ_desc)s. "
|
||||
"Error description: %(error_desc)s "
|
||||
"Error code: %(error_code)s.")
|
||||
|
||||
def __init__(self, message=None, **kwargs):
|
||||
self.error_code = kwargs.get('error_code', None)
|
||||
self.job_state = kwargs.get('job_state', None)
|
||||
|
||||
super(WMIJobFailed, self).__init__(message, **kwargs)
|
||||
|
||||
|
||||
class JobTerminateFailed(HyperVException):
|
||||
msg_fmt = _("Could not terminate the requested job(s).")
|
||||
|
||||
|
||||
class ClusterException(OSWinException):
|
||||
pass
|
||||
|
||||
|
||||
class ClusterWin32Exception(ClusterException, Win32Exception):
|
||||
pass
|
||||
|
||||
|
||||
# TODO(lpetrut): Remove this exception in Q. It was never used outside
|
||||
# os-win.
|
||||
class InvalidClusterGroupState(ClusterException):
|
||||
msg_fmt = _("The cluster group %(group_name)s is in an invalid state. "
|
||||
"Expected state %(expected_state)s. Expected owner node: "
|
||||
"%(expected_node)s. Current group state: %(group_state)s. "
|
||||
"Current owner node: %(owner_node)s.")
|
||||
|
||||
|
||||
class ClusterGroupMigrationFailed(ClusterException):
|
||||
msg_fmt = _("Failed to migrate cluster group %(group_name)s. "
|
||||
"Expected state %(expected_state)s. "
|
||||
"Expected owner node: %(expected_node)s. "
|
||||
"Current group state: %(group_state)s. "
|
||||
"Current owner node: %(owner_node)s.")
|
||||
|
||||
|
||||
class ClusterGroupMigrationTimeOut(ClusterGroupMigrationFailed):
|
||||
msg_fmt = _("Cluster group '%(group_name)s' migration "
|
||||
"timed out after %(time_elapsed)0.3fs. ")
|
||||
|
||||
|
||||
class ClusterPropertyRetrieveFailed(ClusterException):
|
||||
msg_fmt = _("Failed to retrieve a cluster property.")
|
||||
|
||||
|
||||
class ClusterPropertyListEntryNotFound(ClusterPropertyRetrieveFailed):
|
||||
msg_fmt = _("The specified cluster property list does not contain "
|
||||
"an entry named '%(property_name)s'")
|
||||
|
||||
|
||||
class ClusterPropertyListParsingError(ClusterPropertyRetrieveFailed):
|
||||
msg_fmt = _("Parsing a cluster property list failed.")
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 os
|
||||
|
||||
from oslotest import base
|
||||
|
||||
|
||||
class OsWinBaseFunctionalTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(OsWinBaseFunctionalTestCase, self).setUp()
|
||||
if not os.name == 'nt':
|
||||
raise self.skipException("os-win functional tests can only "
|
||||
"be run on Windows.")
|
@ -1,81 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win.tests.functional import test_base
|
||||
from os_win import utilsfactory
|
||||
|
||||
|
||||
class PathUtilsTestCase(test_base.OsWinBaseFunctionalTestCase):
|
||||
def setUp(self):
|
||||
super(PathUtilsTestCase, self).setUp()
|
||||
self._pathutils = utilsfactory.get_pathutils()
|
||||
|
||||
def _get_raw_icacls_info(self, path):
|
||||
return _utils.execute("icacls.exe", path)[0]
|
||||
|
||||
def _assert_contains_ace(self, path, access_to, access_flags):
|
||||
raw_out = self._get_raw_icacls_info(path)
|
||||
|
||||
# The flags will be matched regardless of
|
||||
# other flags and their order.
|
||||
escaped_access_flags = access_flags.replace(
|
||||
"(", "(?=.*\(").replace(")", r"\))")
|
||||
pattern = "%s:%s.*" % (access_to, escaped_access_flags)
|
||||
|
||||
match = re.findall(pattern, raw_out,
|
||||
flags=re.IGNORECASE | re.MULTILINE)
|
||||
if not match:
|
||||
fail_msg = ("The file does not contain the expected ACL rules. "
|
||||
"Raw icacls output: %s. Expected access rule: %s")
|
||||
expected_rule = ":".join([access_to, access_flags])
|
||||
self.fail(fail_msg % (raw_out, expected_rule))
|
||||
|
||||
def test_acls(self):
|
||||
tmp_suffix = 'oswin-func-test'
|
||||
tmp_dir = tempfile.mkdtemp(suffix=tmp_suffix)
|
||||
self.addCleanup(self._pathutils.rmtree, tmp_dir)
|
||||
|
||||
tmp_file_paths = []
|
||||
for idx in range(2):
|
||||
tmp_file_path = os.path.join(tmp_dir,
|
||||
'tmp_file_%s' % idx)
|
||||
with open(tmp_file_path, 'w') as f:
|
||||
f.write('test')
|
||||
tmp_file_paths.append(tmp_file_path)
|
||||
|
||||
trustee = "NULL SID"
|
||||
self._pathutils.add_acl_rule(
|
||||
path=tmp_dir,
|
||||
trustee_name=trustee,
|
||||
access_rights=constants.ACE_GENERIC_READ,
|
||||
access_mode=constants.ACE_GRANT_ACCESS,
|
||||
inheritance_flags=(constants.ACE_OBJECT_INHERIT |
|
||||
constants.ACE_CONTAINER_INHERIT))
|
||||
self._pathutils.add_acl_rule(
|
||||
path=tmp_file_paths[0],
|
||||
trustee_name=trustee,
|
||||
access_rights=constants.ACE_GENERIC_WRITE,
|
||||
access_mode=constants.ACE_GRANT_ACCESS)
|
||||
self._pathutils.copy_acls(tmp_file_paths[0], tmp_file_paths[1])
|
||||
|
||||
self._assert_contains_ace(tmp_dir, trustee, "(OI)(CI).*(GR)")
|
||||
for path in tmp_file_paths:
|
||||
self._assert_contains_ace(path, trustee, ("(W,Rc)"))
|
@ -1,49 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
from oslotest import base
|
||||
from six.moves import builtins
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
|
||||
|
||||
class TestingException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FakeWMIExc(exceptions.x_wmi):
|
||||
def __init__(self, hresult=None):
|
||||
super(FakeWMIExc, self).__init__()
|
||||
excepinfo = [None] * 5 + [hresult]
|
||||
self.com_error = mock.Mock(excepinfo=excepinfo)
|
||||
|
||||
|
||||
class OsWinBaseTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(OsWinBaseTestCase, self).setUp()
|
||||
|
||||
self._mock_wmi = mock.MagicMock()
|
||||
baseutils.BaseUtilsVirt._old_wmi = self._mock_wmi
|
||||
|
||||
mock_os = mock.MagicMock(Version='6.3.0')
|
||||
self._mock_wmi.WMI.return_value.Win32_OperatingSystem.return_value = (
|
||||
[mock_os])
|
||||
wmi_patcher = mock.patch.object(builtins, 'wmi', create=True,
|
||||
new=self._mock_wmi)
|
||||
wmi_patcher.start()
|
||||
self.addCleanup(mock.patch.stopall)
|
@ -1,137 +0,0 @@
|
||||
# Copyright 2017 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 os
|
||||
import textwrap
|
||||
|
||||
import mock
|
||||
import pep8
|
||||
|
||||
from os_win._hacking import checks
|
||||
from os_win.tests.unit import test_base
|
||||
|
||||
|
||||
class HackingTestCase(test_base.OsWinBaseTestCase):
|
||||
"""This class tests the hacking checks in os_win.hacking.checks.
|
||||
|
||||
This is accomplished by passing strings to the check methods like the
|
||||
pep8/flake8 parser would. The parser loops over each line in the file and
|
||||
then passes the parameters to the check method. The parameter names in the
|
||||
check method dictate what type of object is passed to the check method.
|
||||
|
||||
The parameter types are:
|
||||
logical_line: A processed line with the following modifications:
|
||||
- Multi-line statements converted to a single line.
|
||||
- Stripped left and right.
|
||||
- Contents of strings replaced with "xxx" of same length.
|
||||
- Comments removed.
|
||||
physical_line: Raw line of text from the input file.
|
||||
lines: a list of the raw lines from the input file
|
||||
tokens: the tokens that contribute to this logical line
|
||||
line_number: line number in the input file
|
||||
total_lines: number of lines in the input file
|
||||
blank_lines: blank lines before this one
|
||||
indent_char: indentation character in this file (" " or "\t")
|
||||
indent_level: indentation (with tabs expanded to multiples of 8)
|
||||
previous_indent_level: indentation on previous line
|
||||
previous_logical: previous logical line
|
||||
filename: Path of the file being run through pep8
|
||||
|
||||
When running a test on a check method the return will be False/None if
|
||||
there is no violation in the sample input. If there is an error a tuple is
|
||||
returned with a position in the line, and a message. So to check the result
|
||||
just assertTrue if the check is expected to fail and assertFalse if it
|
||||
should pass.
|
||||
"""
|
||||
|
||||
def _run_check(self, code, checker, filename=None):
|
||||
# We are patching pep8 so that only the check under test is actually
|
||||
# installed.
|
||||
mock_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}}
|
||||
with mock.patch('pep8._checks', mock_checks):
|
||||
pep8.register_check(checker)
|
||||
|
||||
lines = textwrap.dedent(code).strip().splitlines(True)
|
||||
|
||||
checker = pep8.Checker(filename=filename, lines=lines)
|
||||
# NOTE(sdague): the standard reporter has printing to stdout
|
||||
# as a normal part of check_all, which bleeds through to the
|
||||
# test output stream in an unhelpful way. This blocks that
|
||||
# printing.
|
||||
with mock.patch('pep8.StandardReport.get_file_results'):
|
||||
checker.check_all()
|
||||
checker.report._deferred_print.sort()
|
||||
return checker.report._deferred_print
|
||||
|
||||
def _assert_has_errors(self, code, checker, expected_errors=None,
|
||||
filename=None):
|
||||
actual_errors = [e[:3] for e in
|
||||
self._run_check(code, checker, filename)]
|
||||
self.assertEqual(expected_errors or [], actual_errors)
|
||||
|
||||
def _assert_has_no_errors(self, code, checker, filename=None):
|
||||
self._assert_has_errors(code, checker, filename=filename)
|
||||
|
||||
def test_ctypes_libs_not_used_directly(self):
|
||||
checker = checks.assert_ctypes_libs_not_used_directly
|
||||
errors = [(1, 0, 'O301')]
|
||||
|
||||
code = "ctypes.cdll.hbaapi"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "ctypes.windll.hbaapi.fake_func(fake_arg)"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "fake_var = ctypes.oledll.hbaapi.fake_func(fake_arg)"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "foo(ctypes.pydll.hbaapi.fake_func(fake_arg))"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "ctypes.cdll.LoadLibrary(fake_lib)"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "ctypes.WinDLL('fake_lib_path')"
|
||||
self._assert_has_errors(code, checker, expected_errors=errors)
|
||||
|
||||
code = "ctypes.cdll.hbaapi"
|
||||
filename = os.path.join("os_win", "utils", "winapi",
|
||||
"libs", "hbaapi.py")
|
||||
self._assert_has_no_errors(code, checker, filename=filename)
|
||||
|
||||
def test_ctypes_foreign_func_argtypes_defined(self):
|
||||
checker = checks.assert_ctypes_foreign_func_argtypes_defined
|
||||
errors = [(1, 0, 'O302')]
|
||||
|
||||
code = "kernel32.FakeFunc(fake_arg)"
|
||||
self._assert_has_errors(code, checker, errors)
|
||||
|
||||
code = "fake_func(kernel32.FakeFunc(fake_arg))"
|
||||
self._assert_has_errors(code, checker, errors)
|
||||
|
||||
code = "kernel32.WaitNamedPipeW(x, y)"
|
||||
self._assert_has_no_errors(code, checker)
|
||||
|
||||
code = "_fake_kernel32.WaitNamedPipeW(x, y)"
|
||||
self._assert_has_no_errors(code, checker)
|
||||
|
||||
def test_no_log_translations(self):
|
||||
for log in checks._all_log_levels:
|
||||
bad = 'LOG.%s(_("Bad"))' % log
|
||||
self.assertEqual(1, len(list(checks.no_translate_logs(bad))))
|
||||
# Catch abuses when used with a variable and not a literal
|
||||
bad = 'LOG.%s(_(msg))' % log
|
||||
self.assertEqual(1, len(list(checks.no_translate_logs(bad))))
|
@ -1,272 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions SRL
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for the os_win._utils module.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslotest import base
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class UtilsTestCase(base.BaseTestCase):
|
||||
|
||||
@mock.patch('oslo_concurrency.processutils.execute')
|
||||
def test_execute(self, mock_execute):
|
||||
_utils.execute(mock.sentinel.cmd, kwarg=mock.sentinel.kwarg)
|
||||
mock_execute.assert_called_once_with(mock.sentinel.cmd,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
|
||||
def test_parse_server_string(self):
|
||||
result = _utils.parse_server_string('::1')
|
||||
self.assertEqual(('::1', ''), result)
|
||||
result = _utils.parse_server_string('[::1]:8773')
|
||||
self.assertEqual(('::1', '8773'), result)
|
||||
result = _utils.parse_server_string('2001:db8::192.168.1.1')
|
||||
self.assertEqual(('2001:db8::192.168.1.1', ''), result)
|
||||
result = _utils.parse_server_string('[2001:db8::192.168.1.1]:8773')
|
||||
self.assertEqual(('2001:db8::192.168.1.1', '8773'), result)
|
||||
result = _utils.parse_server_string('192.168.1.1')
|
||||
self.assertEqual(('192.168.1.1', ''), result)
|
||||
result = _utils.parse_server_string('192.168.1.2:8773')
|
||||
self.assertEqual(('192.168.1.2', '8773'), result)
|
||||
result = _utils.parse_server_string('192.168.1.3')
|
||||
self.assertEqual(('192.168.1.3', ''), result)
|
||||
result = _utils.parse_server_string('www.example.com:8443')
|
||||
self.assertEqual(('www.example.com', '8443'), result)
|
||||
result = _utils.parse_server_string('www.example.com')
|
||||
self.assertEqual(('www.example.com', ''), result)
|
||||
# error case
|
||||
result = _utils.parse_server_string('www.exa:mple.com:8443')
|
||||
self.assertEqual(('', ''), result)
|
||||
result = _utils.parse_server_string('')
|
||||
self.assertEqual(('', ''), result)
|
||||
|
||||
def _get_fake_func_with_retry_decorator(self, side_effect,
|
||||
*args, **kwargs):
|
||||
func_side_effect = mock.Mock(side_effect=side_effect)
|
||||
|
||||
@_utils.retry_decorator(*args, **kwargs)
|
||||
def fake_func(*_args, **_kwargs):
|
||||
return func_side_effect(*_args, **_kwargs)
|
||||
|
||||
return fake_func, func_side_effect
|
||||
|
||||
@mock.patch.object(_utils, 'time')
|
||||
def test_retry_decorator(self, mock_time):
|
||||
err_code = 1
|
||||
max_retry_count = 5
|
||||
max_sleep_time = 2
|
||||
timeout = max_retry_count + 1
|
||||
mock_time.time.side_effect = range(timeout)
|
||||
|
||||
raised_exc = exceptions.Win32Exception(message='fake_exc',
|
||||
error_code=err_code)
|
||||
side_effect = [raised_exc] * max_retry_count
|
||||
side_effect.append(mock.sentinel.ret_val)
|
||||
|
||||
(fake_func,
|
||||
fake_func_side_effect) = self._get_fake_func_with_retry_decorator(
|
||||
error_codes=err_code,
|
||||
exceptions=exceptions.Win32Exception,
|
||||
max_retry_count=max_retry_count,
|
||||
max_sleep_time=max_sleep_time,
|
||||
timeout=timeout,
|
||||
side_effect=side_effect)
|
||||
|
||||
ret_val = fake_func(mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
self.assertEqual(mock.sentinel.ret_val, ret_val)
|
||||
fake_func_side_effect.assert_has_calls(
|
||||
[mock.call(mock.sentinel.arg, kwarg=mock.sentinel.kwarg)] *
|
||||
(max_retry_count + 1))
|
||||
self.assertEqual(max_retry_count + 1, mock_time.time.call_count)
|
||||
mock_time.sleep.assert_has_calls(
|
||||
[mock.call(sleep_time)
|
||||
for sleep_time in [1, 2, 2, 2, 1]])
|
||||
|
||||
@mock.patch.object(_utils, 'time')
|
||||
def _test_retry_decorator_exceeded(self, mock_time, expected_try_count,
|
||||
mock_time_side_eff=None,
|
||||
timeout=None, max_retry_count=None):
|
||||
raised_exc = exceptions.Win32Exception(message='fake_exc')
|
||||
mock_time.time.side_effect = mock_time_side_eff
|
||||
|
||||
(fake_func,
|
||||
fake_func_side_effect) = self._get_fake_func_with_retry_decorator(
|
||||
exceptions=exceptions.Win32Exception,
|
||||
timeout=timeout,
|
||||
side_effect=raised_exc)
|
||||
|
||||
self.assertRaises(exceptions.Win32Exception, fake_func)
|
||||
fake_func_side_effect.assert_has_calls(
|
||||
[mock.call()] * expected_try_count)
|
||||
|
||||
def test_retry_decorator_tries_exceeded(self):
|
||||
self._test_retry_decorator_exceeded(
|
||||
max_retry_count=2,
|
||||
expected_try_count=3)
|
||||
|
||||
def test_retry_decorator_time_exceeded(self):
|
||||
self._test_retry_decorator_exceeded(
|
||||
mock_time_side_eff=[0, 1, 4],
|
||||
timeout=3,
|
||||
expected_try_count=1)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def _test_retry_decorator_no_retry(self, mock_sleep,
|
||||
expected_exceptions=(),
|
||||
expected_error_codes=()):
|
||||
err_code = 1
|
||||
raised_exc = exceptions.Win32Exception(message='fake_exc',
|
||||
error_code=err_code)
|
||||
fake_func, fake_func_side_effect = (
|
||||
self._get_fake_func_with_retry_decorator(
|
||||
error_codes=expected_error_codes,
|
||||
exceptions=expected_exceptions,
|
||||
side_effect=raised_exc))
|
||||
|
||||
self.assertRaises(exceptions.Win32Exception,
|
||||
fake_func, mock.sentinel.arg,
|
||||
fake_kwarg=mock.sentinel.kwarg)
|
||||
|
||||
self.assertFalse(mock_sleep.called)
|
||||
fake_func_side_effect.assert_called_once_with(
|
||||
mock.sentinel.arg, fake_kwarg=mock.sentinel.kwarg)
|
||||
|
||||
def test_retry_decorator_unexpected_err_code(self):
|
||||
self._test_retry_decorator_no_retry(
|
||||
expected_exceptions=exceptions.Win32Exception,
|
||||
expected_error_codes=2)
|
||||
|
||||
def test_retry_decorator_unexpected_exc(self):
|
||||
self._test_retry_decorator_no_retry(
|
||||
expected_exceptions=(IOError, AttributeError))
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def test_retry_decorator_explicitly_avoid_retry(self, mock_sleep):
|
||||
# Tests the case when there is a function aware of the retry
|
||||
# decorator and explicitly requests that no retry should be
|
||||
# performed.
|
||||
|
||||
def func_side_effect(fake_arg, retry_context):
|
||||
self.assertEqual(mock.sentinel.arg, fake_arg)
|
||||
self.assertEqual(retry_context, dict(prevent_retry=False))
|
||||
|
||||
retry_context['prevent_retry'] = True
|
||||
raise exceptions.Win32Exception(message='fake_exc',
|
||||
error_code=1)
|
||||
|
||||
fake_func, mock_side_effect = (
|
||||
self._get_fake_func_with_retry_decorator(
|
||||
exceptions=exceptions.Win32Exception,
|
||||
side_effect=func_side_effect,
|
||||
pass_retry_context=True))
|
||||
|
||||
self.assertRaises(exceptions.Win32Exception,
|
||||
fake_func, mock.sentinel.arg)
|
||||
|
||||
self.assertEqual(1, mock_side_effect.call_count)
|
||||
self.assertFalse(mock_sleep.called)
|
||||
|
||||
@mock.patch.object(_utils.socket, 'getaddrinfo')
|
||||
def test_get_ips(self, mock_getaddrinfo):
|
||||
ips = ['1.2.3.4', '5.6.7.8']
|
||||
mock_getaddrinfo.return_value = [
|
||||
(None, None, None, None, (ip, 0)) for ip in ips]
|
||||
|
||||
resulted_ips = _utils.get_ips(mock.sentinel.addr)
|
||||
self.assertEqual(ips, resulted_ips)
|
||||
|
||||
mock_getaddrinfo.assert_called_once_with(
|
||||
mock.sentinel.addr, None, 0, 0, 0)
|
||||
|
||||
@mock.patch('eventlet.tpool.execute')
|
||||
@mock.patch('eventlet.getcurrent')
|
||||
@ddt.data(mock.Mock(), None)
|
||||
def test_avoid_blocking_call(self, gt_parent, mock_get_current_gt,
|
||||
mock_execute):
|
||||
mock_get_current_gt.return_value.parent = gt_parent
|
||||
mock_execute.return_value = mock.sentinel.ret_val
|
||||
|
||||
def fake_blocking_func(*args, **kwargs):
|
||||
self.assertEqual((mock.sentinel.arg, ), args)
|
||||
self.assertEqual(dict(kwarg=mock.sentinel.kwarg),
|
||||
kwargs)
|
||||
return mock.sentinel.ret_val
|
||||
|
||||
fake_blocking_func_decorated = (
|
||||
_utils.avoid_blocking_call_decorator(fake_blocking_func))
|
||||
|
||||
ret_val = fake_blocking_func_decorated(mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
|
||||
self.assertEqual(mock.sentinel.ret_val, ret_val)
|
||||
if gt_parent:
|
||||
mock_execute.assert_called_once_with(fake_blocking_func,
|
||||
mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
else:
|
||||
self.assertFalse(mock_execute.called)
|
||||
|
||||
def test_get_com_error_hresult(self):
|
||||
fake_hres = -5
|
||||
expected_hres = (1 << 32) + fake_hres
|
||||
mock_excepinfo = [None] * 5 + [fake_hres]
|
||||
mock_com_err = mock.Mock(excepinfo=mock_excepinfo)
|
||||
|
||||
ret_val = _utils.get_com_error_hresult(mock_com_err)
|
||||
|
||||
self.assertEqual(expected_hres, ret_val)
|
||||
|
||||
def get_com_error_hresult_missing_excepinfo(self):
|
||||
ret_val = _utils.get_com_error_hresult(None)
|
||||
self.assertIsNone(ret_val)
|
||||
|
||||
@ddt.data(_utils._WBEM_E_NOT_FOUND, mock.sentinel.wbem_error)
|
||||
@mock.patch.object(_utils, 'get_com_error_hresult')
|
||||
def test_is_not_found_exc(self, hresult, mock_get_com_error_hresult):
|
||||
mock_get_com_error_hresult.return_value = hresult
|
||||
exc = mock.MagicMock()
|
||||
|
||||
result = _utils._is_not_found_exc(exc)
|
||||
|
||||
expected = hresult == _utils._WBEM_E_NOT_FOUND
|
||||
self.assertEqual(expected, result)
|
||||
mock_get_com_error_hresult.assert_called_once_with(exc.com_error)
|
||||
|
||||
@mock.patch.object(_utils, 'get_com_error_hresult')
|
||||
def test_not_found_decorator(self, mock_get_com_error_hresult):
|
||||
mock_get_com_error_hresult.side_effect = lambda x: x
|
||||
translated_exc = exceptions.HyperVVMNotFoundException
|
||||
|
||||
@_utils.not_found_decorator(
|
||||
translated_exc=translated_exc)
|
||||
def f(to_call):
|
||||
to_call()
|
||||
|
||||
to_call = mock.Mock()
|
||||
to_call.side_effect = exceptions.x_wmi(
|
||||
'expected error', com_error=_utils._WBEM_E_NOT_FOUND)
|
||||
self.assertRaises(translated_exc, f, to_call)
|
||||
|
||||
to_call.side_effect = exceptions.x_wmi()
|
||||
self.assertRaises(exceptions.x_wmi, f, to_call)
|
@ -1,137 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions SRL
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for the Hyper-V utils factory.
|
||||
"""
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.compute import clusterutils
|
||||
from os_win.utils.compute import livemigrationutils
|
||||
from os_win.utils.compute import migrationutils
|
||||
from os_win.utils.compute import rdpconsoleutils
|
||||
from os_win.utils.compute import vmutils
|
||||
from os_win.utils.dns import dnsutils
|
||||
from os_win.utils import hostutils
|
||||
from os_win.utils.network import networkutils
|
||||
from os_win.utils import pathutils
|
||||
from os_win.utils.storage import diskutils
|
||||
from os_win.utils.storage.initiator import iscsi_utils
|
||||
from os_win.utils.storage import smbutils
|
||||
from os_win.utils.storage.virtdisk import vhdutils
|
||||
from os_win import utilsfactory
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestHyperVUtilsFactory(test_base.OsWinBaseTestCase):
|
||||
|
||||
@mock.patch.object(utilsfactory.utils, 'get_windows_version')
|
||||
def test_get_class_unsupported_win_version(self, mock_get_windows_version):
|
||||
mock_get_windows_version.return_value = '5.2'
|
||||
self.assertRaises(exceptions.HyperVException, utilsfactory._get_class,
|
||||
'hostutils')
|
||||
|
||||
def test_get_class_unsupported_class_type(self):
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
utilsfactory._get_class,
|
||||
'invalid_class_type')
|
||||
|
||||
@mock.patch.object(utilsfactory.utils, 'get_windows_version')
|
||||
def _check_get_class(self, mock_get_windows_version, expected_class,
|
||||
class_type, windows_version='6.2', **kwargs):
|
||||
mock_get_windows_version.return_value = windows_version
|
||||
|
||||
method = getattr(utilsfactory, 'get_%s' % class_type)
|
||||
instance = method(**kwargs)
|
||||
self.assertEqual(expected_class, type(instance))
|
||||
|
||||
return instance
|
||||
|
||||
def test_get_vmutils(self):
|
||||
instance = self._check_get_class(expected_class=vmutils.VMUtils,
|
||||
class_type='vmutils',
|
||||
host=mock.sentinel.host)
|
||||
self.assertEqual(mock.sentinel.host, instance._host)
|
||||
|
||||
def test_get_vhdutils(self):
|
||||
self._check_get_class(expected_class=vhdutils.VHDUtils,
|
||||
class_type='vhdutils')
|
||||
|
||||
def test_get_networkutils(self):
|
||||
self._check_get_class(expected_class=networkutils.NetworkUtils,
|
||||
class_type='networkutils')
|
||||
|
||||
def test_get_networkutilsr2(self):
|
||||
self._check_get_class(expected_class=networkutils.NetworkUtilsR2,
|
||||
class_type='networkutils',
|
||||
windows_version='6.3')
|
||||
|
||||
def test_get_hostutils(self):
|
||||
self._check_get_class(expected_class=hostutils.HostUtils,
|
||||
class_type='hostutils')
|
||||
|
||||
def test_get_pathutils(self):
|
||||
self._check_get_class(expected_class=pathutils.PathUtils,
|
||||
class_type='pathutils')
|
||||
|
||||
def test_get_livemigrationutils(self):
|
||||
self._check_get_class(
|
||||
expected_class=livemigrationutils.LiveMigrationUtils,
|
||||
class_type='livemigrationutils')
|
||||
|
||||
@mock.patch.object(smbutils.SMBUtils, '__init__',
|
||||
lambda *args, **kwargs: None)
|
||||
def test_get_smbutils(self):
|
||||
self._check_get_class(expected_class=smbutils.SMBUtils,
|
||||
class_type='smbutils')
|
||||
|
||||
def test_get_rdpconsoleutils(self):
|
||||
self._check_get_class(expected_class=rdpconsoleutils.RDPConsoleUtils,
|
||||
class_type='rdpconsoleutils')
|
||||
|
||||
def test_get_iscsi_initiator_utils(self):
|
||||
self._check_get_class(expected_class=iscsi_utils.ISCSIInitiatorUtils,
|
||||
class_type='iscsi_initiator_utils')
|
||||
|
||||
@mock.patch('os_win.utils.storage.initiator.fc_utils.FCUtils')
|
||||
def test_get_fc_utils(self, mock_cls_fcutils):
|
||||
self._check_get_class(
|
||||
expected_class=type(mock_cls_fcutils.return_value),
|
||||
class_type='fc_utils')
|
||||
|
||||
def test_get_diskutils(self):
|
||||
self._check_get_class(
|
||||
expected_class=diskutils.DiskUtils,
|
||||
class_type='diskutils')
|
||||
|
||||
def test_get_clusterutils(self):
|
||||
self._check_get_class(
|
||||
expected_class=clusterutils.ClusterUtils,
|
||||
class_type='clusterutils')
|
||||
|
||||
def test_get_dnsutils(self):
|
||||
self._check_get_class(
|
||||
expected_class=dnsutils.DNSUtils,
|
||||
class_type='dnsutils')
|
||||
|
||||
def test_get_migrationutils(self):
|
||||
self._check_get_class(
|
||||
expected_class=migrationutils.MigrationUtils,
|
||||
class_type='migrationutils')
|
@ -1,529 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ctypes
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.compute import _clusapi_utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi.libs import clusapi as clusapi_def
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ClusApiUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
_LIVE_MIGRATION_TYPE = 4
|
||||
|
||||
def setUp(self):
|
||||
super(ClusApiUtilsTestCase, self).setUp()
|
||||
|
||||
self._clusapi = mock.patch.object(
|
||||
_clusapi_utils, 'clusapi', create=True).start()
|
||||
|
||||
self._clusapi_utils = _clusapi_utils.ClusApiUtils()
|
||||
|
||||
self._run_patcher = mock.patch.object(self._clusapi_utils,
|
||||
'_run_and_check_output')
|
||||
self._mock_run = self._run_patcher.start()
|
||||
|
||||
def _mock_ctypes(self):
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
self._ctypes.c_wchar_p = lambda x: (x, 'c_wchar_p')
|
||||
self._ctypes.sizeof = lambda x: (x, 'sizeof')
|
||||
self._ctypes.c_ulong = lambda x: (x, 'c_ulong')
|
||||
|
||||
mock.patch.object(_clusapi_utils, 'ctypes', self._ctypes).start()
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._clusapi_utils._win32utils = mock.Mock()
|
||||
self._clusapi_utils._run_and_check_output = (
|
||||
self._run_patcher.temp_original)
|
||||
|
||||
mock_win32utils_run_and_check_output = (
|
||||
self._clusapi_utils._win32utils.run_and_check_output)
|
||||
|
||||
ret_val = self._clusapi_utils._run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
fake_kwarg=mock.sentinel.kwarg)
|
||||
|
||||
mock_win32utils_run_and_check_output.assert_called_once_with(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
fake_kwarg=mock.sentinel.kwarg,
|
||||
failure_exc=exceptions.ClusterWin32Exception)
|
||||
self.assertEqual(mock_win32utils_run_and_check_output.return_value,
|
||||
ret_val)
|
||||
|
||||
def test_dword_align(self):
|
||||
self.assertEqual(8, self._clusapi_utils._dword_align(5))
|
||||
self.assertEqual(4, self._clusapi_utils._dword_align(4))
|
||||
|
||||
def test_get_clusprop_value_struct(self):
|
||||
val_type = ctypes.c_ubyte * 3
|
||||
expected_padding_sz = 1
|
||||
|
||||
clusprop_val_struct = self._clusapi_utils._get_clusprop_value_struct(
|
||||
val_type)
|
||||
|
||||
expected_fields = [('syntax', wintypes.DWORD),
|
||||
('length', wintypes.DWORD),
|
||||
('value', val_type),
|
||||
('_padding', ctypes.c_ubyte * expected_padding_sz)]
|
||||
self.assertEqual(expected_fields, clusprop_val_struct._fields_)
|
||||
|
||||
def test_get_property_list_entry(self):
|
||||
fake_prop_name = 'fake prop name'
|
||||
fake_prop_syntax = 1
|
||||
fake_prop_val = (ctypes.c_wchar * 10)()
|
||||
fake_prop_val.value = 'fake prop'
|
||||
|
||||
entry = self._clusapi_utils.get_property_list_entry(
|
||||
name=fake_prop_name,
|
||||
syntax=fake_prop_syntax,
|
||||
value=fake_prop_val)
|
||||
|
||||
self.assertEqual(w_const.CLUSPROP_SYNTAX_NAME,
|
||||
entry.name.syntax)
|
||||
self.assertEqual(fake_prop_name,
|
||||
entry.name.value)
|
||||
self.assertEqual(
|
||||
ctypes.sizeof(ctypes.c_wchar) * (len(fake_prop_name) + 1),
|
||||
entry.name.length)
|
||||
|
||||
self.assertEqual(fake_prop_syntax,
|
||||
entry.value.syntax)
|
||||
self.assertEqual(bytearray(fake_prop_val),
|
||||
bytearray(entry.value.value))
|
||||
self.assertEqual(
|
||||
ctypes.sizeof(fake_prop_val),
|
||||
entry.value.length)
|
||||
|
||||
self.assertEqual(w_const.CLUSPROP_SYNTAX_ENDMARK,
|
||||
entry._endmark)
|
||||
|
||||
def test_get_property_list(self):
|
||||
entry_0 = self._clusapi_utils.get_property_list_entry(
|
||||
name='fake prop name',
|
||||
syntax=1,
|
||||
value=ctypes.c_uint(2))
|
||||
entry_1 = self._clusapi_utils.get_property_list_entry(
|
||||
name='fake prop name',
|
||||
syntax=2,
|
||||
value=ctypes.c_ubyte(5))
|
||||
|
||||
prop_list = self._clusapi_utils.get_property_list(
|
||||
[entry_0, entry_1])
|
||||
|
||||
self.assertEqual(2, prop_list.count)
|
||||
self.assertEqual(bytearray(entry_0) + bytearray(entry_1),
|
||||
prop_list.entries_buff)
|
||||
|
||||
@ddt.data('fake cluster name', None)
|
||||
def test_open_cluster(self, cluster_name):
|
||||
self._mock_ctypes()
|
||||
|
||||
handle = self._clusapi_utils.open_cluster(cluster_name)
|
||||
|
||||
expected_handle_arg = (
|
||||
self._ctypes.c_wchar_p(cluster_name)
|
||||
if cluster_name else None)
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.OpenCluster,
|
||||
expected_handle_arg,
|
||||
**self._clusapi_utils._open_handle_check_flags)
|
||||
|
||||
self.assertEqual(self._mock_run.return_value, handle)
|
||||
|
||||
def test_open_cluster_group(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
handle = self._clusapi_utils.open_cluster_group(
|
||||
mock.sentinel.cluster_handle,
|
||||
mock.sentinel.group_name)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.OpenClusterGroup,
|
||||
mock.sentinel.cluster_handle,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.group_name),
|
||||
**self._clusapi_utils._open_handle_check_flags)
|
||||
|
||||
self.assertEqual(self._mock_run.return_value, handle)
|
||||
|
||||
def test_open_cluster_node(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
handle = self._clusapi_utils.open_cluster_node(
|
||||
mock.sentinel.cluster_handle,
|
||||
mock.sentinel.node_name)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.OpenClusterNode,
|
||||
mock.sentinel.cluster_handle,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.node_name),
|
||||
**self._clusapi_utils._open_handle_check_flags)
|
||||
|
||||
self.assertEqual(self._mock_run.return_value, handle)
|
||||
|
||||
def test_close_cluster(self):
|
||||
self._clusapi_utils.close_cluster(mock.sentinel.handle)
|
||||
self._clusapi.CloseCluster.assert_called_once_with(
|
||||
mock.sentinel.handle)
|
||||
|
||||
def test_close_cluster_group(self):
|
||||
self._clusapi_utils.close_cluster_group(mock.sentinel.handle)
|
||||
self._clusapi.CloseClusterGroup.assert_called_once_with(
|
||||
mock.sentinel.handle)
|
||||
|
||||
def test_close_cluster_node(self):
|
||||
self._clusapi_utils.close_cluster_node(mock.sentinel.handle)
|
||||
self._clusapi.CloseClusterNode.assert_called_once_with(
|
||||
mock.sentinel.handle)
|
||||
|
||||
@ddt.data(0, w_const.ERROR_IO_PENDING)
|
||||
def test_cancel_cluster_group_operation(self, cancel_ret_val):
|
||||
self._mock_run.return_value = cancel_ret_val
|
||||
|
||||
expected_ret_val = cancel_ret_val != w_const.ERROR_IO_PENDING
|
||||
ret_val = self._clusapi_utils.cancel_cluster_group_operation(
|
||||
mock.sentinel.group_handle)
|
||||
|
||||
self.assertEqual(expected_ret_val, ret_val)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.CancelClusterGroupOperation,
|
||||
mock.sentinel.group_handle,
|
||||
0,
|
||||
ignored_error_codes=[w_const.ERROR_IO_PENDING])
|
||||
|
||||
@ddt.data(mock.sentinel.prop_list, None)
|
||||
def test_move_cluster_group(self, prop_list):
|
||||
self._mock_ctypes()
|
||||
|
||||
expected_prop_list_arg = (
|
||||
self._ctypes.byref(prop_list) if prop_list else None)
|
||||
expected_prop_list_sz = (
|
||||
self._ctypes.sizeof(prop_list) if prop_list else 0)
|
||||
|
||||
self._clusapi_utils.move_cluster_group(
|
||||
mock.sentinel.group_handle,
|
||||
mock.sentinel.dest_node_handle,
|
||||
mock.sentinel.move_flags,
|
||||
prop_list)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.MoveClusterGroupEx,
|
||||
mock.sentinel.group_handle,
|
||||
mock.sentinel.dest_node_handle,
|
||||
mock.sentinel.move_flags,
|
||||
expected_prop_list_arg,
|
||||
expected_prop_list_sz,
|
||||
ignored_error_codes=[w_const.ERROR_IO_PENDING])
|
||||
|
||||
def test_get_cluster_group_state(self):
|
||||
owner_node = 'fake owner node'
|
||||
|
||||
def fake_get_state(inst,
|
||||
group_handle, node_name_buff, node_name_len,
|
||||
error_ret_vals, error_on_nonzero_ret_val,
|
||||
ret_val_is_err_code):
|
||||
self.assertEqual(mock.sentinel.group_handle, group_handle)
|
||||
|
||||
# Those arguments would not normally get to the ClusApi
|
||||
# function, instead being used by the helper invoking
|
||||
# it and catching errors. For convenience, we validate
|
||||
# those arguments at this point.
|
||||
self.assertEqual([constants.CLUSTER_GROUP_STATE_UNKNOWN],
|
||||
error_ret_vals)
|
||||
self.assertFalse(error_on_nonzero_ret_val)
|
||||
self.assertFalse(ret_val_is_err_code)
|
||||
|
||||
node_name_len_arg = ctypes.cast(
|
||||
node_name_len,
|
||||
wintypes.PDWORD).contents
|
||||
self.assertEqual(w_const.MAX_PATH,
|
||||
node_name_len_arg.value)
|
||||
|
||||
node_name_arg = ctypes.cast(
|
||||
node_name_buff,
|
||||
ctypes.POINTER(
|
||||
ctypes.c_wchar *
|
||||
w_const.MAX_PATH)).contents
|
||||
node_name_arg.value = owner_node
|
||||
return mock.sentinel.group_state
|
||||
|
||||
self._mock_run.side_effect = fake_get_state
|
||||
|
||||
state_info = self._clusapi_utils.get_cluster_group_state(
|
||||
mock.sentinel.group_handle)
|
||||
expected_state_info = dict(state=mock.sentinel.group_state,
|
||||
owner_node=owner_node)
|
||||
self.assertEqual(expected_state_info, state_info)
|
||||
|
||||
@ddt.data({'notif_filters': (clusapi_def.NOTIFY_FILTER_AND_TYPE * 2)(),
|
||||
'exp_notif_filters_len': 2},
|
||||
{'notif_filters': clusapi_def.NOTIFY_FILTER_AND_TYPE(),
|
||||
'notif_port_h': mock.sentinel.notif_port_h,
|
||||
'notif_key': mock.sentinel.notif_key})
|
||||
@ddt.unpack
|
||||
def test_create_cluster_notify_port(self, notif_filters,
|
||||
exp_notif_filters_len=1,
|
||||
notif_port_h=None,
|
||||
notif_key=None):
|
||||
self._mock_ctypes()
|
||||
self._ctypes.Array = ctypes.Array
|
||||
|
||||
self._clusapi_utils.create_cluster_notify_port_v2(
|
||||
mock.sentinel.cluster_handle,
|
||||
notif_filters,
|
||||
notif_port_h,
|
||||
notif_key)
|
||||
|
||||
exp_notif_key_p = self._ctypes.byref(notif_key) if notif_key else None
|
||||
exp_notif_port_h = notif_port_h or w_const.INVALID_HANDLE_VALUE
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._clusapi.CreateClusterNotifyPortV2,
|
||||
exp_notif_port_h,
|
||||
mock.sentinel.cluster_handle,
|
||||
self._ctypes.byref(notif_filters),
|
||||
self._ctypes.c_ulong(exp_notif_filters_len),
|
||||
exp_notif_key_p,
|
||||
**self._clusapi_utils._open_handle_check_flags)
|
||||
|
||||
def test_close_cluster_notify_port(self):
|
||||
self._clusapi_utils.close_cluster_notify_port(mock.sentinel.handle)
|
||||
self._clusapi.CloseClusterNotifyPort.assert_called_once_with(
|
||||
mock.sentinel.handle)
|
||||
|
||||
def test_get_cluster_notify_v2(self):
|
||||
fake_notif_key = 1
|
||||
fake_notif_port_h = 2
|
||||
fake_notif_type = 3
|
||||
fake_filter_flags = 4
|
||||
fake_clus_obj_name = 'fake-changed-clus-object'
|
||||
fake_event_buff = 'fake-event-buff'
|
||||
|
||||
notif_key = ctypes.c_ulong(fake_notif_key)
|
||||
requested_buff_sz = 1024
|
||||
|
||||
def fake_get_cluster_notify(func, notif_port_h, pp_notif_key,
|
||||
p_filter_and_type,
|
||||
p_buff, p_buff_sz,
|
||||
p_obj_id_buff, p_obj_id_buff_sz,
|
||||
p_parent_id_buff, p_parent_id_buff_sz,
|
||||
p_obj_name_buff, p_obj_name_buff_sz,
|
||||
p_obj_type, p_obj_type_sz,
|
||||
timeout_ms):
|
||||
self.assertEqual(self._clusapi.GetClusterNotifyV2, func)
|
||||
self.assertEqual(fake_notif_port_h, notif_port_h)
|
||||
|
||||
obj_name_buff_sz = ctypes.cast(
|
||||
p_obj_name_buff_sz,
|
||||
wintypes.PDWORD).contents
|
||||
buff_sz = ctypes.cast(
|
||||
p_buff_sz,
|
||||
wintypes.PDWORD).contents
|
||||
|
||||
# We'll just request the tested method to pass us
|
||||
# a buffer this large.
|
||||
if (buff_sz.value < requested_buff_sz or
|
||||
obj_name_buff_sz.value < requested_buff_sz):
|
||||
buff_sz.value = requested_buff_sz
|
||||
obj_name_buff_sz.value = requested_buff_sz
|
||||
raise exceptions.ClusterWin32Exception(
|
||||
error_code=w_const.ERROR_MORE_DATA,
|
||||
func_name='GetClusterNotify',
|
||||
error_message='error more data')
|
||||
|
||||
pp_notif_key = ctypes.cast(pp_notif_key, ctypes.c_void_p)
|
||||
p_notif_key = ctypes.c_void_p.from_address(pp_notif_key.value)
|
||||
p_notif_key.value = ctypes.addressof(notif_key)
|
||||
|
||||
filter_and_type = ctypes.cast(
|
||||
p_filter_and_type,
|
||||
ctypes.POINTER(clusapi_def.NOTIFY_FILTER_AND_TYPE)).contents
|
||||
filter_and_type.dwObjectType = fake_notif_type
|
||||
filter_and_type.FilterFlags = fake_filter_flags
|
||||
|
||||
obj_name_buff = ctypes.cast(
|
||||
p_obj_name_buff,
|
||||
ctypes.POINTER(
|
||||
ctypes.c_wchar *
|
||||
(requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
|
||||
obj_name_buff = obj_name_buff.contents
|
||||
ctypes.memset(obj_name_buff, 0, obj_name_buff_sz.value)
|
||||
obj_name_buff.value = fake_clus_obj_name
|
||||
|
||||
buff = ctypes.cast(
|
||||
p_buff,
|
||||
ctypes.POINTER(
|
||||
ctypes.c_wchar *
|
||||
(requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
|
||||
buff = buff.contents
|
||||
ctypes.memset(buff, 0, buff_sz.value)
|
||||
buff.value = fake_event_buff
|
||||
|
||||
self.assertEqual(mock.sentinel.timeout_ms, timeout_ms)
|
||||
|
||||
self._mock_run.side_effect = fake_get_cluster_notify
|
||||
|
||||
event = self._clusapi_utils.get_cluster_notify_v2(
|
||||
fake_notif_port_h, mock.sentinel.timeout_ms)
|
||||
w_event_buff = ctypes.cast(
|
||||
event['buff'],
|
||||
ctypes.POINTER(
|
||||
ctypes.c_wchar *
|
||||
(requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
|
||||
w_event_buff = w_event_buff.contents[:]
|
||||
event['buff'] = w_event_buff.split('\x00')[0]
|
||||
|
||||
expected_event = dict(cluster_object_name=fake_clus_obj_name,
|
||||
object_type=fake_notif_type,
|
||||
filter_flags=fake_filter_flags,
|
||||
buff=fake_event_buff,
|
||||
buff_sz=requested_buff_sz,
|
||||
notif_key=fake_notif_key)
|
||||
self.assertEqual(expected_event, event)
|
||||
|
||||
def _get_fake_prop_list(self):
|
||||
syntax = w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD
|
||||
migr_type = wintypes.DWORD(self._LIVE_MIGRATION_TYPE)
|
||||
|
||||
prop_entries = [
|
||||
self._clusapi_utils.get_property_list_entry(
|
||||
w_const.CLUS_RESTYPE_NAME_VM, syntax, migr_type),
|
||||
self._clusapi_utils.get_property_list_entry(
|
||||
w_const.CLUS_RESTYPE_NAME_VM_CONFIG, syntax, migr_type),
|
||||
self._clusapi_utils.get_property_list_entry(
|
||||
w_const.CLUSREG_NAME_GRP_STATUS_INFORMATION,
|
||||
w_const.CLUSPROP_SYNTAX_LIST_VALUE_ULARGE_INTEGER,
|
||||
ctypes.c_ulonglong(w_const.
|
||||
CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE)) # noqa
|
||||
]
|
||||
|
||||
prop_list = self._clusapi_utils.get_property_list(prop_entries)
|
||||
return prop_list
|
||||
|
||||
def test_get_prop_list_entry_p_not_found(self):
|
||||
prop_list = self._get_fake_prop_list()
|
||||
|
||||
self.assertRaises(exceptions.ClusterPropertyListEntryNotFound,
|
||||
self._clusapi_utils.get_prop_list_entry_p,
|
||||
ctypes.byref(prop_list),
|
||||
ctypes.sizeof(prop_list),
|
||||
'InexistentProperty')
|
||||
|
||||
def test_get_prop_list_entry_p_parsing_error(self):
|
||||
prop_list = self._get_fake_prop_list()
|
||||
|
||||
prop_entry_name_len_addr = ctypes.addressof(
|
||||
prop_list.entries_buff) + ctypes.sizeof(ctypes.c_ulong)
|
||||
prop_entry_name_len = ctypes.c_ulong.from_address(
|
||||
prop_entry_name_len_addr)
|
||||
prop_entry_name_len.value = ctypes.sizeof(prop_list)
|
||||
|
||||
self.assertRaises(exceptions.ClusterPropertyListParsingError,
|
||||
self._clusapi_utils.get_prop_list_entry_p,
|
||||
ctypes.byref(prop_list),
|
||||
ctypes.sizeof(prop_list),
|
||||
w_const.CLUS_RESTYPE_NAME_VM)
|
||||
|
||||
def test_get_prop_list_entry_p(self):
|
||||
prop_list = self._get_fake_prop_list()
|
||||
|
||||
prop_entry = self._clusapi_utils.get_prop_list_entry_p(
|
||||
ctypes.byref(prop_list),
|
||||
ctypes.sizeof(prop_list),
|
||||
w_const.CLUS_RESTYPE_NAME_VM_CONFIG)
|
||||
|
||||
self.assertEqual(
|
||||
w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD,
|
||||
prop_entry['syntax'])
|
||||
self.assertEqual(
|
||||
ctypes.sizeof(ctypes.c_ulong),
|
||||
prop_entry['length'])
|
||||
|
||||
val = ctypes.c_ulong.from_address(prop_entry['val_p'].value).value
|
||||
self.assertEqual(self._LIVE_MIGRATION_TYPE, val)
|
||||
|
||||
def test_cluster_group_control(self):
|
||||
fake_out_buff = 'fake-event-buff'
|
||||
|
||||
requested_buff_sz = 1024
|
||||
|
||||
def fake_cluster_group_ctrl(func, group_handle, node_handle,
|
||||
control_code,
|
||||
in_buff_p, in_buff_sz,
|
||||
out_buff_p, out_buff_sz,
|
||||
requested_buff_sz_p):
|
||||
self.assertEqual(self._clusapi.ClusterGroupControl, func)
|
||||
self.assertEqual(mock.sentinel.group_handle, group_handle)
|
||||
self.assertEqual(mock.sentinel.node_handle, node_handle)
|
||||
self.assertEqual(mock.sentinel.control_code, control_code)
|
||||
self.assertEqual(mock.sentinel.in_buff_p, in_buff_p)
|
||||
self.assertEqual(mock.sentinel.in_buff_sz, in_buff_sz)
|
||||
|
||||
req_buff_sz = ctypes.cast(
|
||||
requested_buff_sz_p,
|
||||
wintypes.PDWORD).contents
|
||||
req_buff_sz.value = requested_buff_sz
|
||||
|
||||
# We'll just request the tested method to pass us
|
||||
# a buffer this large.
|
||||
if (out_buff_sz.value < requested_buff_sz):
|
||||
raise exceptions.ClusterWin32Exception(
|
||||
error_code=w_const.ERROR_MORE_DATA,
|
||||
func_name='ClusterGroupControl',
|
||||
error_message='error more data')
|
||||
|
||||
out_buff = ctypes.cast(
|
||||
out_buff_p,
|
||||
ctypes.POINTER(
|
||||
ctypes.c_wchar *
|
||||
(requested_buff_sz // ctypes.sizeof(ctypes.c_wchar))))
|
||||
out_buff = out_buff.contents
|
||||
out_buff.value = fake_out_buff
|
||||
|
||||
self._mock_run.side_effect = fake_cluster_group_ctrl
|
||||
|
||||
out_buff, out_buff_sz = self._clusapi_utils.cluster_group_control(
|
||||
mock.sentinel.group_handle, mock.sentinel.control_code,
|
||||
mock.sentinel.node_handle, mock.sentinel.in_buff_p,
|
||||
mock.sentinel.in_buff_sz)
|
||||
|
||||
self.assertEqual(requested_buff_sz, out_buff_sz)
|
||||
wp_out_buff = ctypes.cast(
|
||||
out_buff,
|
||||
ctypes.POINTER(ctypes.c_wchar * requested_buff_sz))
|
||||
self.assertEqual(fake_out_buff,
|
||||
wp_out_buff.contents[:len(fake_out_buff)])
|
||||
|
||||
def test_get_cluster_group_status_info(self):
|
||||
prop_list = self._get_fake_prop_list()
|
||||
|
||||
status_info = self._clusapi_utils.get_cluster_group_status_info(
|
||||
ctypes.byref(prop_list), ctypes.sizeof(prop_list))
|
||||
self.assertEqual(
|
||||
w_const.CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE,
|
||||
status_info)
|
File diff suppressed because it is too large
Load Diff
@ -1,310 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# 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 platform
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils.compute import livemigrationutils
|
||||
from os_win.utils.compute import vmutils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class LiveMigrationUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V LiveMigrationUtils class."""
|
||||
|
||||
_FAKE_VM_NAME = 'fake_vm_name'
|
||||
_FAKE_RET_VAL = 0
|
||||
|
||||
_RESOURCE_TYPE_VHD = 31
|
||||
_RESOURCE_TYPE_DISK = 17
|
||||
_RESOURCE_SUB_TYPE_VHD = 'Microsoft:Hyper-V:Virtual Hard Disk'
|
||||
_RESOURCE_SUB_TYPE_DISK = 'Microsoft:Hyper-V:Physical Disk Drive'
|
||||
|
||||
def setUp(self):
|
||||
super(LiveMigrationUtilsTestCase, self).setUp()
|
||||
self.liveutils = livemigrationutils.LiveMigrationUtils()
|
||||
self._conn = mock.MagicMock()
|
||||
self.liveutils._conn_attr = self._conn
|
||||
self.liveutils._vmutils = mock.MagicMock()
|
||||
self.liveutils._jobutils = mock.Mock()
|
||||
|
||||
self.liveutils._get_wmi_obj = mock.MagicMock(return_value=self._conn)
|
||||
self.liveutils._conn_v2 = self._conn
|
||||
|
||||
def test_get_conn_v2(self):
|
||||
self.liveutils._get_wmi_obj.side_effect = exceptions.x_wmi(
|
||||
com_error=mock.Mock())
|
||||
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self.liveutils._get_conn_v2, '.')
|
||||
|
||||
self.liveutils._get_wmi_obj.assert_called_once_with(
|
||||
self.liveutils._wmi_namespace % '.', compatibility_mode=True)
|
||||
|
||||
def test_check_live_migration_config(self):
|
||||
mock_migr_svc = (
|
||||
self._conn.Msvm_VirtualSystemMigrationService.return_value[0])
|
||||
conn_vsmssd = self._conn.Msvm_VirtualSystemMigrationServiceSettingData
|
||||
|
||||
vsmssd = mock.MagicMock()
|
||||
vsmssd.EnableVirtualSystemMigration = True
|
||||
conn_vsmssd.return_value = [vsmssd]
|
||||
mock_migr_svc.MigrationServiceListenerIPAdressList.return_value = [
|
||||
mock.sentinel.FAKE_HOST]
|
||||
|
||||
self.liveutils.check_live_migration_config()
|
||||
conn_vsmssd.assert_called_once_with()
|
||||
self._conn.Msvm_VirtualSystemMigrationService.assert_called_once_with()
|
||||
|
||||
def test_get_vm(self):
|
||||
expected_vm = mock.MagicMock()
|
||||
mock_conn_v2 = mock.MagicMock()
|
||||
mock_conn_v2.Msvm_ComputerSystem.return_value = [expected_vm]
|
||||
|
||||
found_vm = self.liveutils._get_vm(mock_conn_v2, self._FAKE_VM_NAME)
|
||||
|
||||
self.assertEqual(expected_vm, found_vm)
|
||||
|
||||
def test_get_vm_duplicate(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_conn_v2 = mock.MagicMock()
|
||||
mock_conn_v2.Msvm_ComputerSystem.return_value = [mock_vm, mock_vm]
|
||||
|
||||
self.assertRaises(exceptions.HyperVException, self.liveutils._get_vm,
|
||||
mock_conn_v2, self._FAKE_VM_NAME)
|
||||
|
||||
def test_get_vm_not_found(self):
|
||||
mock_conn_v2 = mock.MagicMock()
|
||||
mock_conn_v2.Msvm_ComputerSystem.return_value = []
|
||||
|
||||
self.assertRaises(exceptions.HyperVVMNotFoundException,
|
||||
self.liveutils._get_vm,
|
||||
mock_conn_v2, self._FAKE_VM_NAME)
|
||||
|
||||
def test_create_planned_vm_helper(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_v2 = mock.MagicMock()
|
||||
mock_vsmsd_cls = mock_v2.Msvm_VirtualSystemMigrationSettingData
|
||||
mock_vsmsd = mock_vsmsd_cls.return_value[0]
|
||||
self._conn.Msvm_PlannedComputerSystem.return_value = [mock_vm]
|
||||
|
||||
migr_svc = mock_v2.Msvm_VirtualSystemMigrationService()[0]
|
||||
migr_svc.MigrateVirtualSystemToHost.return_value = (
|
||||
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
|
||||
|
||||
resulted_vm = self.liveutils._create_planned_vm(
|
||||
self._conn, mock_v2, mock_vm, [mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
mock.sentinel.FAKE_HOST)
|
||||
|
||||
self.assertEqual(mock_vm, resulted_vm)
|
||||
|
||||
mock_vsmsd_cls.assert_called_once_with(
|
||||
MigrationType=self.liveutils._MIGRATION_TYPE_STAGED)
|
||||
migr_svc.MigrateVirtualSystemToHost.assert_called_once_with(
|
||||
ComputerSystem=mock_vm.path_.return_value,
|
||||
DestinationHost=mock.sentinel.FAKE_HOST,
|
||||
MigrationSettingData=mock_vsmsd.GetText_.return_value)
|
||||
self.liveutils._jobutils.check_ret_val.assert_called_once_with(
|
||||
mock.sentinel.FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
|
||||
def test_get_disk_data(self):
|
||||
mock_vmutils_remote = mock.MagicMock()
|
||||
mock_disk = mock.MagicMock()
|
||||
mock_disk_path_mapping = {
|
||||
mock.sentinel.serial: mock.sentinel.disk_path}
|
||||
|
||||
mock_disk.path.return_value.RelPath = mock.sentinel.rel_path
|
||||
mock_vmutils_remote.get_vm_disks.return_value = [
|
||||
None, [mock_disk]]
|
||||
mock_disk.ElementName = mock.sentinel.serial
|
||||
|
||||
resulted_disk_paths = self.liveutils._get_disk_data(
|
||||
self._FAKE_VM_NAME, mock_vmutils_remote, mock_disk_path_mapping)
|
||||
|
||||
mock_vmutils_remote.get_vm_disks.assert_called_once_with(
|
||||
self._FAKE_VM_NAME)
|
||||
mock_disk.path.assert_called_once_with()
|
||||
expected_disk_paths = {mock.sentinel.rel_path: mock.sentinel.disk_path}
|
||||
self.assertEqual(expected_disk_paths, resulted_disk_paths)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
def test_update_planned_vm_disk_resources(self,
|
||||
mock_get_elem_associated_class):
|
||||
self._prepare_vm_mocks(self._RESOURCE_TYPE_DISK,
|
||||
self._RESOURCE_SUB_TYPE_DISK,
|
||||
mock_get_elem_associated_class)
|
||||
mock_vm = mock.Mock(Name='fake_name')
|
||||
sasd = mock_get_elem_associated_class.return_value[0]
|
||||
|
||||
mock_vsmsvc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
self.liveutils._update_planned_vm_disk_resources(
|
||||
self._conn, mock_vm, mock.sentinel.FAKE_VM_NAME,
|
||||
{sasd.path.return_value.RelPath: mock.sentinel.FAKE_RASD_PATH})
|
||||
|
||||
mock_vsmsvc.ModifyResourceSettings.assert_called_once_with(
|
||||
ResourceSettings=[sasd.GetText_.return_value])
|
||||
mock_get_elem_associated_class.assert_called_once_with(
|
||||
self._conn, self.liveutils._CIM_RES_ALLOC_SETTING_DATA_CLASS,
|
||||
element_uuid=mock_vm.Name)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
def test_get_vhd_setting_data(self, mock_get_elem_associated_class):
|
||||
self._prepare_vm_mocks(self._RESOURCE_TYPE_VHD,
|
||||
self._RESOURCE_SUB_TYPE_VHD,
|
||||
mock_get_elem_associated_class)
|
||||
mock_vm = mock.Mock(Name='fake_vm_name')
|
||||
mock_sasd = mock_get_elem_associated_class.return_value[0]
|
||||
|
||||
vhd_sds = self.liveutils._get_vhd_setting_data(mock_vm)
|
||||
self.assertEqual([mock_sasd.GetText_.return_value], vhd_sds)
|
||||
mock_get_elem_associated_class.assert_called_once_with(
|
||||
self._conn, self.liveutils._STORAGE_ALLOC_SETTING_DATA_CLASS,
|
||||
element_uuid=mock_vm.Name)
|
||||
|
||||
def test_live_migrate_vm_helper(self):
|
||||
mock_conn_local = mock.MagicMock()
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_vsmsd_cls = (
|
||||
mock_conn_local.Msvm_VirtualSystemMigrationSettingData)
|
||||
mock_vsmsd = mock_vsmsd_cls.return_value[0]
|
||||
|
||||
mock_vsmsvc = mock_conn_local.Msvm_VirtualSystemMigrationService()[0]
|
||||
mock_vsmsvc.MigrateVirtualSystemToHost.return_value = (
|
||||
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
|
||||
|
||||
self.liveutils._live_migrate_vm(
|
||||
mock_conn_local, mock_vm, None,
|
||||
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
mock.sentinel.FAKE_RASD_PATH, mock.sentinel.FAKE_HOST,
|
||||
mock.sentinel.migration_type)
|
||||
|
||||
mock_vsmsd_cls.assert_called_once_with(
|
||||
MigrationType=mock.sentinel.migration_type)
|
||||
mock_vsmsvc.MigrateVirtualSystemToHost.assert_called_once_with(
|
||||
ComputerSystem=mock_vm.path_.return_value,
|
||||
DestinationHost=mock.sentinel.FAKE_HOST,
|
||||
MigrationSettingData=mock_vsmsd.GetText_.return_value,
|
||||
NewResourceSettingData=mock.sentinel.FAKE_RASD_PATH)
|
||||
|
||||
@mock.patch.object(
|
||||
livemigrationutils.LiveMigrationUtils, '_get_planned_vm')
|
||||
def test_live_migrate_single_planned_vm(self, mock_get_planned_vm):
|
||||
mock_vm = self._get_vm()
|
||||
|
||||
mock_migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
|
||||
mock_migr_svc.MigrationServiceListenerIPAddressList = [
|
||||
mock.sentinel.FAKE_REMOTE_IP_ADDR]
|
||||
|
||||
# patches, call and assertions.
|
||||
with mock.patch.multiple(
|
||||
self.liveutils,
|
||||
_get_vhd_setting_data=mock.DEFAULT,
|
||||
_live_migrate_vm=mock.DEFAULT):
|
||||
|
||||
mock_get_planned_vm.return_value = mock_vm
|
||||
self.liveutils.live_migrate_vm(mock.sentinel.vm_name,
|
||||
mock.sentinel.FAKE_HOST)
|
||||
self.liveutils._live_migrate_vm.assert_called_once_with(
|
||||
self._conn, mock_vm, mock_vm,
|
||||
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
self.liveutils._get_vhd_setting_data.return_value,
|
||||
mock.sentinel.FAKE_HOST,
|
||||
self.liveutils._MIGRATION_TYPE_VIRTUAL_SYSTEM_AND_STORAGE)
|
||||
mock_get_planned_vm.assert_called_once_with(
|
||||
mock.sentinel.vm_name, self._conn)
|
||||
|
||||
@mock.patch.object(vmutils, 'VMUtils')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils, '_get_vm')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_get_ip_address_list')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_update_planned_vm_disk_resources')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_create_planned_vm')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'destroy_existing_planned_vm')
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_get_disk_data')
|
||||
def test_create_planned_vm(self, mock_get_disk_data,
|
||||
mock_destroy_existing_planned_vm,
|
||||
mock_create_planned_vm,
|
||||
mock_update_planned_vm_disk_resources,
|
||||
mock_get_ip_address_list, mock_get_vm,
|
||||
mock_cls_vmutils):
|
||||
dest_host = platform.node()
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_get_vm.return_value = mock_vm
|
||||
mock_conn_v2 = mock.MagicMock()
|
||||
self.liveutils._get_wmi_obj.return_value = mock_conn_v2
|
||||
|
||||
mock_get_disk_data.return_value = mock.sentinel.disk_data
|
||||
mock_get_ip_address_list.return_value = mock.sentinel.ip_address_list
|
||||
|
||||
mock_vsmsvc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
mock_vsmsvc.ModifyResourceSettings.return_value = (
|
||||
mock.sentinel.res_setting,
|
||||
mock.sentinel.job_path,
|
||||
self._FAKE_RET_VAL)
|
||||
|
||||
self.liveutils.create_planned_vm(mock.sentinel.vm_name,
|
||||
mock.sentinel.host,
|
||||
mock.sentinel.disk_path_mapping)
|
||||
|
||||
mock_destroy_existing_planned_vm.assert_called_once_with(
|
||||
mock.sentinel.vm_name)
|
||||
mock_get_ip_address_list.assert_called_once_with(self._conn, dest_host)
|
||||
mock_get_disk_data.assert_called_once_with(
|
||||
mock.sentinel.vm_name,
|
||||
mock_cls_vmutils.return_value,
|
||||
mock.sentinel.disk_path_mapping)
|
||||
mock_create_planned_vm.assert_called_once_with(
|
||||
self._conn, mock_conn_v2, mock_vm,
|
||||
mock.sentinel.ip_address_list, dest_host)
|
||||
mock_update_planned_vm_disk_resources.assert_called_once_with(
|
||||
self._conn, mock_create_planned_vm.return_value,
|
||||
mock.sentinel.vm_name, mock.sentinel.disk_data)
|
||||
|
||||
def _prepare_vm_mocks(self, resource_type, resource_sub_type,
|
||||
mock_get_elem_associated_class):
|
||||
mock_vm_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
vm = self._get_vm()
|
||||
self._conn.Msvm_PlannedComputerSystem.return_value = [vm]
|
||||
mock_vm_svc.DestroySystem.return_value = (mock.sentinel.FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
mock_vm_svc.ModifyResourceSettings.return_value = (
|
||||
None, mock.sentinel.FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
sasd = mock.MagicMock()
|
||||
other_sasd = mock.MagicMock()
|
||||
sasd.ResourceType = resource_type
|
||||
sasd.ResourceSubType = resource_sub_type
|
||||
sasd.HostResource = [mock.sentinel.FAKE_SASD_RESOURCE]
|
||||
sasd.path.return_value.RelPath = mock.sentinel.FAKE_DISK_PATH
|
||||
|
||||
mock_get_elem_associated_class.return_value = [sasd, other_sasd]
|
||||
|
||||
def _get_vm(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
self._conn.Msvm_ComputerSystem.return_value = [mock_vm]
|
||||
mock_vm.path_.return_value = mock.sentinel.FAKE_VM_PATH
|
||||
mock_vm.Name = self._FAKE_VM_NAME
|
||||
return mock_vm
|
@ -1,181 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.compute import migrationutils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class MigrationUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V MigrationUtils class."""
|
||||
|
||||
_FAKE_VM_NAME = 'fake_vm'
|
||||
|
||||
def setUp(self):
|
||||
super(MigrationUtilsTestCase, self).setUp()
|
||||
self._migrationutils = migrationutils.MigrationUtils()
|
||||
self._migrationutils._vmutils = mock.MagicMock()
|
||||
self._migrationutils._conn_attr = mock.MagicMock()
|
||||
self._migrationutils._jobutils = mock.MagicMock()
|
||||
|
||||
def test_get_export_setting_data(self):
|
||||
mock_vm = self._migrationutils._vmutils._lookup_vm.return_value
|
||||
mock_conn = self._migrationutils._compat_conn
|
||||
mock_exp = mock_conn.Msvm_VirtualSystemExportSettingData
|
||||
mock_exp.return_value = [mock.sentinel.export_setting_data]
|
||||
expected_result = mock.sentinel.export_setting_data
|
||||
|
||||
actual_result = self._migrationutils._get_export_setting_data(
|
||||
self._FAKE_VM_NAME)
|
||||
self.assertEqual(expected_result, actual_result)
|
||||
mock_exp.assert_called_once_with(InstanceID=mock_vm.InstanceID)
|
||||
|
||||
@mock.patch.object(
|
||||
migrationutils.MigrationUtils, '_get_export_setting_data')
|
||||
def test_export_vm(self, mock_get_export_setting_data):
|
||||
mock_vm = self._migrationutils._vmutils._lookup_vm.return_value
|
||||
export_setting_data = mock_get_export_setting_data.return_value
|
||||
mock_svc = self._migrationutils._vs_man_svc
|
||||
mock_svc.ExportSystemDefinition.return_value = (
|
||||
mock.sentinel.job_path, mock.sentinel.ret_val)
|
||||
|
||||
self._migrationutils.export_vm(
|
||||
vm_name=self._FAKE_VM_NAME,
|
||||
export_path=mock.sentinel.fake_export_path)
|
||||
|
||||
self.assertEqual(constants.EXPORT_CONFIG_SNAPSHOTS_ALL,
|
||||
export_setting_data.CopySnapshotConfiguration)
|
||||
self.assertFalse(export_setting_data.CopyVmStorage)
|
||||
self.assertFalse(export_setting_data.CreateVmExportSubdirectory)
|
||||
mock_get_export_setting_data.assert_called_once_with(
|
||||
self._FAKE_VM_NAME)
|
||||
mock_svc.ExportSystemDefinition.assert_called_once_with(
|
||||
ComputerSystem=mock_vm.path_(),
|
||||
ExportDirectory=mock.sentinel.fake_export_path,
|
||||
ExportSettingData=export_setting_data.GetText_(1))
|
||||
self._migrationutils._jobutils.check_ret_val.assert_called_once_with(
|
||||
mock.sentinel.ret_val, mock.sentinel.job_path)
|
||||
|
||||
def test_import_vm_definition(self):
|
||||
mock_svc = self._migrationutils._vs_man_svc
|
||||
mock_svc.ImportSystemDefinition.return_value = (
|
||||
mock.sentinel.ref,
|
||||
mock.sentinel.job_path,
|
||||
mock.sentinel.ret_val)
|
||||
|
||||
self._migrationutils.import_vm_definition(
|
||||
export_config_file_path=mock.sentinel.export_config_file_path,
|
||||
snapshot_folder_path=mock.sentinel.snapshot_folder_path)
|
||||
|
||||
mock_svc.ImportSystemDefinition.assert_called_once_with(
|
||||
False, mock.sentinel.snapshot_folder_path,
|
||||
mock.sentinel.export_config_file_path)
|
||||
self._migrationutils._jobutils.check_ret_val.assert_called_once_with(
|
||||
mock.sentinel.ret_val, mock.sentinel.job_path)
|
||||
|
||||
@mock.patch.object(migrationutils.MigrationUtils, '_get_planned_vm')
|
||||
def test_realize_vm(self, mock_get_planned_vm):
|
||||
mock_get_planned_vm.return_value = mock.MagicMock()
|
||||
self._migrationutils._vs_man_svc.ValidatePlannedSystem.return_value = (
|
||||
mock.sentinel.job_path_ValidatePlannedSystem,
|
||||
mock.sentinel.ret_val_ValidatePlannedSystem)
|
||||
self._migrationutils._vs_man_svc.RealizePlannedSystem.return_value = (
|
||||
mock.sentinel.job_path_RealizePlannedSystem,
|
||||
mock.sentinel.ref_RealizePlannedSystem,
|
||||
mock.sentinel.ret_val_RealizePlannedSystem)
|
||||
|
||||
self._migrationutils.realize_vm(self._FAKE_VM_NAME)
|
||||
|
||||
mock_get_planned_vm.assert_called_once_with(
|
||||
self._FAKE_VM_NAME, fail_if_not_found=True)
|
||||
expected_call = [
|
||||
mock.call(mock.sentinel.ret_val_ValidatePlannedSystem,
|
||||
mock.sentinel.job_path_ValidatePlannedSystem),
|
||||
mock.call(mock.sentinel.ret_val_RealizePlannedSystem,
|
||||
mock.sentinel.job_path_RealizePlannedSystem)]
|
||||
self._migrationutils._jobutils.check_ret_val.has_calls(expected_call)
|
||||
|
||||
@ddt.data([mock.sentinel.planned_vm], [])
|
||||
def test_get_planned_vm(self, planned_vm):
|
||||
planned_computer_system = (
|
||||
self._migrationutils._conn.Msvm_PlannedComputerSystem)
|
||||
planned_computer_system.return_value = planned_vm
|
||||
|
||||
actual_result = self._migrationutils._get_planned_vm(
|
||||
self._FAKE_VM_NAME, fail_if_not_found=False)
|
||||
|
||||
if planned_vm:
|
||||
self.assertEqual(planned_vm[0], actual_result)
|
||||
else:
|
||||
self.assertIsNone(actual_result)
|
||||
planned_computer_system.assert_called_once_with(
|
||||
ElementName=self._FAKE_VM_NAME)
|
||||
|
||||
def test_get_planned_vm_exception(self):
|
||||
planned_computer_system = (
|
||||
self._migrationutils._conn.Msvm_PlannedComputerSystem)
|
||||
planned_computer_system.return_value = None
|
||||
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self._migrationutils._get_planned_vm,
|
||||
self._FAKE_VM_NAME, fail_if_not_found=True)
|
||||
|
||||
planned_computer_system.assert_called_once_with(
|
||||
ElementName=self._FAKE_VM_NAME)
|
||||
|
||||
@mock.patch.object(migrationutils.MigrationUtils, '_get_planned_vm')
|
||||
def test_planned_vm_exists(self, mock_get_planned_vm):
|
||||
mock_get_planned_vm.return_value = None
|
||||
|
||||
result = self._migrationutils.planned_vm_exists(mock.sentinel.vm_name)
|
||||
self.assertFalse(result)
|
||||
mock_get_planned_vm.assert_called_once_with(mock.sentinel.vm_name)
|
||||
|
||||
def test_destroy_planned_vm(self):
|
||||
mock_planned_vm = mock.MagicMock()
|
||||
mock_planned_vm.path_.return_value = mock.sentinel.planned_vm_path
|
||||
mock_vs_man_svc = self._migrationutils._vs_man_svc
|
||||
mock_vs_man_svc.DestroySystem.return_value = (
|
||||
mock.sentinel.job_path, mock.sentinel.ret_val)
|
||||
|
||||
self._migrationutils._destroy_planned_vm(mock_planned_vm)
|
||||
|
||||
mock_vs_man_svc.DestroySystem.assert_called_once_with(
|
||||
mock.sentinel.planned_vm_path)
|
||||
self._migrationutils._jobutils.check_ret_val.assert_called_once_with(
|
||||
mock.sentinel.ret_val,
|
||||
mock.sentinel.job_path)
|
||||
|
||||
@ddt.data({'planned_vm': None}, {'planned_vm': mock.sentinel.planned_vm})
|
||||
@ddt.unpack
|
||||
@mock.patch.object(migrationutils.MigrationUtils, '_destroy_planned_vm')
|
||||
@mock.patch.object(migrationutils.MigrationUtils, '_get_planned_vm')
|
||||
def test_destroy_existing_planned_vm(self, mock_get_planned_vm,
|
||||
mock_destroy_planned_vm, planned_vm):
|
||||
mock_get_planned_vm.return_value = planned_vm
|
||||
|
||||
self._migrationutils.destroy_existing_planned_vm(mock.sentinel.vm_name)
|
||||
|
||||
mock_get_planned_vm.assert_called_once_with(
|
||||
mock.sentinel.vm_name, self._migrationutils._compat_conn)
|
||||
if planned_vm:
|
||||
mock_destroy_planned_vm.assert_called_once_with(planned_vm)
|
||||
else:
|
||||
self.assertFalse(mock_destroy_planned_vm.called)
|
@ -1,37 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.compute import rdpconsoleutils
|
||||
|
||||
|
||||
class RDPConsoleUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
_FAKE_RDP_PORT = 1000
|
||||
|
||||
def setUp(self):
|
||||
self._rdpconsoleutils = rdpconsoleutils.RDPConsoleUtils()
|
||||
self._rdpconsoleutils._conn_attr = mock.MagicMock()
|
||||
|
||||
super(RDPConsoleUtilsTestCase, self).setUp()
|
||||
|
||||
def test_get_rdp_console_port(self):
|
||||
conn = self._rdpconsoleutils._conn
|
||||
mock_rdp_setting_data = conn.Msvm_TerminalServiceSettingData()[0]
|
||||
mock_rdp_setting_data.ListenerPort = self._FAKE_RDP_PORT
|
||||
|
||||
listener_port = self._rdpconsoleutils.get_rdp_console_port()
|
||||
|
||||
self.assertEqual(self._FAKE_RDP_PORT, listener_port)
|
File diff suppressed because it is too large
Load Diff
@ -1,360 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 ddt
|
||||
import mock
|
||||
import six
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils.compute import vmutils10
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VMUtils10TestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V VMUtils10 class."""
|
||||
|
||||
_FAKE_PCI_ID = 'Microsoft:ED28B-7BDD0\\PCIP\\VEN_15B3&DEV_1007&SUBSYS_00'
|
||||
_FAKE_VENDOR_ID = '15B3'
|
||||
_FAKE_PRODUCT_ID = '1007'
|
||||
|
||||
def setUp(self):
|
||||
super(VMUtils10TestCase, self).setUp()
|
||||
self._vmutils = vmutils10.VMUtils10()
|
||||
self._vmutils._conn_attr = mock.MagicMock()
|
||||
self._vmutils._conn_msps_attr = mock.MagicMock()
|
||||
self._vmutils._jobutils = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_get_wmi_conn')
|
||||
def test_conn_msps(self, mock_get_wmi_conn):
|
||||
self._vmutils._conn_msps_attr = None
|
||||
self.assertEqual(mock_get_wmi_conn.return_value,
|
||||
self._vmutils._conn_msps)
|
||||
|
||||
mock_get_wmi_conn.assert_called_with(
|
||||
self._vmutils._MSPS_NAMESPACE % self._vmutils._host)
|
||||
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_get_wmi_conn')
|
||||
def test_conn_msps_no_namespace(self, mock_get_wmi_conn):
|
||||
self._vmutils._conn_msps_attr = None
|
||||
|
||||
mock_get_wmi_conn.side_effect = [exceptions.OSWinException]
|
||||
self.assertRaises(exceptions.OSWinException,
|
||||
lambda: self._vmutils._conn_msps)
|
||||
mock_get_wmi_conn.assert_called_with(
|
||||
self._vmutils._MSPS_NAMESPACE % self._vmutils._host)
|
||||
|
||||
def test_sec_svc(self):
|
||||
self._vmutils._sec_svc_attr = None
|
||||
self.assertEqual(
|
||||
self._vmutils._conn.Msvm_SecurityService.return_value[0],
|
||||
self._vmutils._sec_svc)
|
||||
|
||||
self._vmutils._conn.Msvm_SecurityService.assert_called_with()
|
||||
|
||||
def test_set_secure_boot_CA_required(self):
|
||||
vs_data = mock.MagicMock()
|
||||
mock_vssd = self._vmutils._conn.Msvm_VirtualSystemSettingData
|
||||
mock_vssd.return_value = [
|
||||
mock.MagicMock(SecureBootTemplateId=mock.sentinel.template_id)]
|
||||
|
||||
self._vmutils._set_secure_boot(vs_data, msft_ca_required=True)
|
||||
|
||||
self.assertTrue(vs_data.SecureBootEnabled)
|
||||
self.assertEqual(mock.sentinel.template_id,
|
||||
vs_data.SecureBootTemplateId)
|
||||
mock_vssd.assert_called_once_with(
|
||||
ElementName=self._vmutils._UEFI_CERTIFICATE_AUTH)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_set_nested_virtualization(self, mock_lookup_vm_check,
|
||||
mock_get_element_associated_class):
|
||||
mock_vmsettings = mock_lookup_vm_check.return_value
|
||||
mock_procsettings = mock_get_element_associated_class.return_value[0]
|
||||
|
||||
self._vmutils.set_nested_virtualization(mock.sentinel.vm_name,
|
||||
mock.sentinel.state)
|
||||
|
||||
mock_lookup_vm_check.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self._vmutils._conn, self._vmutils._PROCESSOR_SETTING_DATA_CLASS,
|
||||
element_instance_id=mock_vmsettings.InstanceID)
|
||||
self.assertEqual(mock.sentinel.state,
|
||||
mock_procsettings.ExposeVirtualizationExtensions)
|
||||
self._vmutils._jobutils.modify_virt_resource.assert_called_once_with(
|
||||
mock_procsettings)
|
||||
|
||||
def test_vm_gen_supports_remotefx(self):
|
||||
ret = self._vmutils.vm_gen_supports_remotefx(mock.sentinel.VM_GEN)
|
||||
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_validate_remotefx_monitor_count(self):
|
||||
self.assertRaises(exceptions.HyperVRemoteFXException,
|
||||
self._vmutils._validate_remotefx_params,
|
||||
10, constants.REMOTEFX_MAX_RES_1024x768)
|
||||
|
||||
def test_validate_remotefx_max_resolution(self):
|
||||
self.assertRaises(exceptions.HyperVRemoteFXException,
|
||||
self._vmutils._validate_remotefx_params,
|
||||
1, '1024x700')
|
||||
|
||||
def test_validate_remotefx_vram(self):
|
||||
self.assertRaises(exceptions.HyperVRemoteFXException,
|
||||
self._vmutils._validate_remotefx_params,
|
||||
1, constants.REMOTEFX_MAX_RES_1024x768,
|
||||
vram_bytes=10000)
|
||||
|
||||
def test_validate_remotefx(self):
|
||||
self._vmutils._validate_remotefx_params(
|
||||
1, constants.REMOTEFX_MAX_RES_1024x768)
|
||||
|
||||
def test_set_remotefx_vram(self):
|
||||
remotefx_ctrl_res = mock.MagicMock()
|
||||
vram_bytes = 512
|
||||
|
||||
self._vmutils._set_remotefx_vram(remotefx_ctrl_res, vram_bytes)
|
||||
self.assertEqual(six.text_type(vram_bytes),
|
||||
remotefx_ctrl_res.VRAMSizeBytes)
|
||||
|
||||
@mock.patch.object(vmutils10.VMUtils10, 'get_vm_generation')
|
||||
def _test_vm_has_s3_controller(self, vm_gen, mock_get_vm_gen):
|
||||
mock_get_vm_gen.return_value = vm_gen
|
||||
return self._vmutils._vm_has_s3_controller(mock.sentinel.fake_vm_name)
|
||||
|
||||
def test_vm_has_s3_controller_gen1(self):
|
||||
self.assertTrue(self._test_vm_has_s3_controller(constants.VM_GEN_1))
|
||||
|
||||
def test_vm_has_s3_controller_gen2(self):
|
||||
self.assertFalse(self._test_vm_has_s3_controller(constants.VM_GEN_2))
|
||||
|
||||
def test_populate_fsk(self):
|
||||
fsk_pairs = {mock.sentinel.computer: mock.sentinel.computer_value}
|
||||
|
||||
mock_fabricdata = (
|
||||
self._vmutils._conn_msps.Msps_FabricData.new.return_value)
|
||||
|
||||
fsk = self._vmutils._conn_msps.Msps_FSK.new.return_value
|
||||
mock_msps_pfp = self._vmutils._conn_msps.Msps_ProvisioningFileProcessor
|
||||
|
||||
self._vmutils.populate_fsk(mock.sentinel.fsk_filepath, fsk_pairs)
|
||||
|
||||
mock_msps_pfp.SerializeToFile.assert_called_once_with(
|
||||
mock.sentinel.fsk_filepath, fsk)
|
||||
self.assertEqual([mock_fabricdata], fsk.FabricDataPairs)
|
||||
self.assertEqual(mock.sentinel.computer, mock_fabricdata.key)
|
||||
self.assertEqual(mock.sentinel.computer_value,
|
||||
mock_fabricdata.Value)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_add_vtpm(self, mock_lookup_vm_check,
|
||||
mock_get_element_associated_class):
|
||||
mock_lookup_vm_check.return_value = mock.Mock(
|
||||
ConfigurationID=mock.sentinel.configuration_id)
|
||||
|
||||
mock_msps_pfp = self._vmutils._conn_msps.Msps_ProvisioningFileProcessor
|
||||
provisioning_file = mock.Mock(KeyProtector=mock.sentinel.keyprotector,
|
||||
PolicyData=mock.sentinel.policy)
|
||||
mock_msps_pfp.PopulateFromFile.return_value = [provisioning_file]
|
||||
security_profile = mock.Mock()
|
||||
|
||||
mock_get_element_associated_class.return_value = [security_profile]
|
||||
sec_profile_serialization = security_profile.GetText_.return_value
|
||||
|
||||
mock_sec_svc = self._vmutils._sec_svc
|
||||
mock_sec_svc.SetKeyProtector.return_value = (
|
||||
mock.sentinel.job_path_SetKeyProtector,
|
||||
mock.sentinel.ret_val_SetKeyProtector)
|
||||
mock_sec_svc.SetSecurityPolicy.return_value = (
|
||||
mock.sentinel.job_path_SetSecurityPolicy,
|
||||
mock.sentinel.ret_val_SetSecurityPolicy)
|
||||
mock_sec_svc.ModifySecuritySettings.return_value = (
|
||||
mock.sentinel.job_path_ModifySecuritySettings,
|
||||
mock.sentinel.ret_val_ModifySecuritySettings)
|
||||
|
||||
self._vmutils.add_vtpm(mock.sentinel.VM_NAME,
|
||||
mock.sentinel.pdk_filepath,
|
||||
shielded=True)
|
||||
|
||||
mock_lookup_vm_check.assert_called_with(mock.sentinel.VM_NAME)
|
||||
mock_msps_pfp.PopulateFromFile.assert_called_once_with(
|
||||
mock.sentinel.pdk_filepath)
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self._vmutils._conn,
|
||||
self._vmutils._SECURITY_SETTING_DATA,
|
||||
element_uuid=mock.sentinel.configuration_id)
|
||||
mock_sec_svc.SetKeyProtector.assert_called_once_with(
|
||||
mock.sentinel.keyprotector,
|
||||
sec_profile_serialization)
|
||||
mock_sec_svc.SetSecurityPolicy.assert_called_once_with(
|
||||
mock.sentinel.policy, sec_profile_serialization)
|
||||
mock_sec_svc.ModifySecuritySettings.assert_called_once_with(
|
||||
sec_profile_serialization)
|
||||
|
||||
expected_call = [
|
||||
mock.call(mock.sentinel.job_path_SetKeyProtector,
|
||||
mock.sentinel.ret_val_SetKeyProtector),
|
||||
mock.call(mock.sentinel.job_path_SetSecurityPolicy,
|
||||
mock.sentinel.ret_val_SetSecurityPolicy),
|
||||
mock.call(mock.sentinel.job_path_ModifySecuritySettings,
|
||||
mock.sentinel.ret_val_ModifySecuritySettings)]
|
||||
self._vmutils._jobutils.check_ret_val.has_calls(expected_call)
|
||||
self.assertTrue(security_profile.EncryptStateAndVmMigrationTraffic)
|
||||
self.assertTrue(security_profile.TpmEnabled)
|
||||
self.assertTrue(security_profile.ShieldingRequested)
|
||||
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_provision_vm(self, mock_lookup_vm_check):
|
||||
mock_vm = mock_lookup_vm_check.return_value
|
||||
provisioning_srv = self._vmutils._conn_msps.Msps_ProvisioningService
|
||||
|
||||
provisioning_srv.ProvisionMachine.return_value = (
|
||||
mock.sentinel.job_path_ProvisionMachine,
|
||||
mock.sentinel.ret_val_ProvisionMachine)
|
||||
|
||||
self._vmutils.provision_vm(mock.sentinel.vm_name,
|
||||
mock.sentinel.fsk_file,
|
||||
mock.sentinel.pdk_file)
|
||||
|
||||
provisioning_srv.ProvisionMachine.assert_called_once_with(
|
||||
mock.sentinel.fsk_file,
|
||||
mock_vm.ConfigurationID,
|
||||
mock.sentinel.pdk_file)
|
||||
self._vmutils._jobutils.check_ret_val.assert_called_once_with(
|
||||
mock.sentinel.ret_val_ProvisionMachine,
|
||||
mock.sentinel.job_path_ProvisionMachine)
|
||||
|
||||
mock_lookup_vm_check.assert_called_with(mock.sentinel.vm_name)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(vmutils10.VMUtils10, 'get_vm_id')
|
||||
def _test_secure_vm(self, mock_get_vm_id,
|
||||
mock_get_element_associated_class,
|
||||
is_encrypted_vm=True):
|
||||
inst_id = mock_get_vm_id.return_value
|
||||
security_profile = mock.MagicMock()
|
||||
mock_get_element_associated_class.return_value = [security_profile]
|
||||
security_profile.EncryptStateAndVmMigrationTraffic = is_encrypted_vm
|
||||
|
||||
response = self._vmutils.is_secure_vm(mock.sentinel.instance_name)
|
||||
self.assertEqual(is_encrypted_vm, response)
|
||||
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self._vmutils._conn,
|
||||
self._vmutils._SECURITY_SETTING_DATA,
|
||||
element_uuid=inst_id)
|
||||
|
||||
def test_is_secure_shielded_vm(self):
|
||||
self._test_secure_vm()
|
||||
|
||||
def test_not_secure_vm(self):
|
||||
self._test_secure_vm(is_encrypted_vm=False)
|
||||
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_get_assignable_pci_device')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_get_new_setting_data')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_add_pci_device(self, mock_lookup_vm_check,
|
||||
mock_get_new_setting_data,
|
||||
mock_get_pci_device):
|
||||
vmsettings = mock_lookup_vm_check.return_value
|
||||
pci_setting_data = mock_get_new_setting_data.return_value
|
||||
pci_device = mock_get_pci_device.return_value
|
||||
|
||||
self._vmutils.add_pci_device(mock.sentinel.vm_name,
|
||||
mock.sentinel.vendor_id,
|
||||
mock.sentinel.product_id)
|
||||
|
||||
self.assertEqual(pci_setting_data.HostResource,
|
||||
[pci_device.path_.return_value])
|
||||
mock_lookup_vm_check.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_new_setting_data.assert_called_once_with(
|
||||
self._vmutils._PCI_EXPRESS_SETTING_DATA)
|
||||
mock_get_pci_device.assert_called_once_with(
|
||||
mock.sentinel.vendor_id, mock.sentinel.product_id)
|
||||
self._vmutils._jobutils.add_virt_resource.assert_called_once_with(
|
||||
pci_setting_data, vmsettings)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_get_assignable_pci_device_exception(self, matched):
|
||||
product_id = self._FAKE_PRODUCT_ID if matched else '0000'
|
||||
pci_dev = mock.MagicMock(DeviceID=self._FAKE_PCI_ID)
|
||||
pci_devs = [pci_dev] * 2 if matched else [pci_dev]
|
||||
self._vmutils._conn.Msvm_PciExpress.return_value = pci_devs
|
||||
|
||||
self.assertRaises(exceptions.PciDeviceNotFound,
|
||||
self._vmutils._get_assignable_pci_device,
|
||||
self._FAKE_VENDOR_ID, product_id)
|
||||
|
||||
self._vmutils._conn.Msvm_PciExpress.assert_called_once_with()
|
||||
|
||||
def test_get_assignable_pci_device(self):
|
||||
pci_dev = mock.MagicMock(DeviceID=self._FAKE_PCI_ID)
|
||||
self._vmutils._conn.Msvm_PciExpress.return_value = [pci_dev]
|
||||
|
||||
result = self._vmutils._get_assignable_pci_device(
|
||||
self._FAKE_VENDOR_ID, self._FAKE_PRODUCT_ID)
|
||||
|
||||
self.assertEqual(pci_dev, result)
|
||||
self._vmutils._conn.Msvm_PciExpress.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_remove_pci_device(self, mock_lookup_vm_check,
|
||||
mock_get_element_associated_class):
|
||||
vmsettings = mock_lookup_vm_check.return_value
|
||||
pci_setting_data = mock.MagicMock(HostResource=(self._FAKE_PCI_ID, ))
|
||||
bad_pci_setting_data = mock.MagicMock(HostResource=('', ))
|
||||
mock_get_element_associated_class.return_value = [
|
||||
bad_pci_setting_data, pci_setting_data]
|
||||
|
||||
self._vmutils.remove_pci_device(mock.sentinel.vm_name,
|
||||
self._FAKE_VENDOR_ID,
|
||||
self._FAKE_PRODUCT_ID)
|
||||
|
||||
mock_lookup_vm_check.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self._vmutils._conn, self._vmutils._PCI_EXPRESS_SETTING_DATA,
|
||||
vmsettings.InstanceID)
|
||||
self._vmutils._jobutils.remove_virt_resource.assert_called_once_with(
|
||||
pci_setting_data)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(vmutils10.VMUtils10, '_lookup_vm_check')
|
||||
def test_remove_all_pci_devices(self, mock_lookup_vm_check,
|
||||
mock_get_element_associated_class):
|
||||
vmsettings = mock_lookup_vm_check.return_value
|
||||
|
||||
self._vmutils.remove_all_pci_devices(mock.sentinel.vm_name)
|
||||
|
||||
mock_lookup_vm_check.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self._vmutils._conn, self._vmutils._PCI_EXPRESS_SETTING_DATA,
|
||||
vmsettings.InstanceID)
|
||||
mock_remove_multiple_virt_resource = (
|
||||
self._vmutils._jobutils.remove_multiple_virt_resources)
|
||||
mock_remove_multiple_virt_resource.assert_called_once_with(
|
||||
mock_get_element_associated_class.return_value)
|
||||
|
||||
def test_set_snapshot_type(self):
|
||||
vmsettings = mock.Mock()
|
||||
|
||||
self._vmutils._set_vm_snapshot_type(
|
||||
vmsettings, mock.sentinel.snapshot_type)
|
||||
|
||||
self.assertEqual(mock.sentinel.snapshot_type,
|
||||
vmsettings.UserSnapshotType)
|
@ -1,271 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.dns import dnsutils
|
||||
|
||||
|
||||
class DNSUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V DNSUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(DNSUtilsTestCase, self).setUp()
|
||||
self._dnsutils = dnsutils.DNSUtils()
|
||||
self._dnsutils._dns_manager_attr = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_wmi_obj')
|
||||
def test_dns_manager(self, mock_get_wmi_obj):
|
||||
self._dnsutils._dns_manager_attr = None
|
||||
|
||||
self.assertEqual(mock_get_wmi_obj.return_value,
|
||||
self._dnsutils._dns_manager)
|
||||
|
||||
mock_get_wmi_obj.assert_called_once_with(
|
||||
self._dnsutils._DNS_NAMESPACE % self._dnsutils._host)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_wmi_obj')
|
||||
def test_dns_manager_fail(self, mock_get_wmi_obj):
|
||||
self._dnsutils._dns_manager_attr = None
|
||||
expected_exception = exceptions.DNSException
|
||||
mock_get_wmi_obj.side_effect = expected_exception
|
||||
|
||||
self.assertRaises(expected_exception,
|
||||
lambda: self._dnsutils._dns_manager)
|
||||
|
||||
mock_get_wmi_obj.assert_called_once_with(
|
||||
self._dnsutils._DNS_NAMESPACE % self._dnsutils._host)
|
||||
|
||||
def test_get_zone(self):
|
||||
zone_manager = self._dnsutils._dns_manager.MicrosoftDNS_Zone
|
||||
zone_manager.return_value = [mock.sentinel.zone]
|
||||
|
||||
zone_found = self._dnsutils._get_zone(mock.sentinel.zone_name)
|
||||
|
||||
zone_manager.assert_called_once_with(Name=mock.sentinel.zone_name)
|
||||
self.assertEqual(mock.sentinel.zone, zone_found)
|
||||
|
||||
def test_get_zone_ignore_missing(self):
|
||||
zone_manager = self._dnsutils._dns_manager.MicrosoftDNS_Zone
|
||||
zone_manager.return_value = []
|
||||
|
||||
zone_found = self._dnsutils._get_zone(mock.sentinel.zone_name)
|
||||
|
||||
zone_manager.assert_called_once_with(Name=mock.sentinel.zone_name)
|
||||
self.assertIsNone(zone_found)
|
||||
|
||||
def test_get_zone_missing(self):
|
||||
zone_manager = self._dnsutils._dns_manager.MicrosoftDNS_Zone
|
||||
zone_manager.return_value = []
|
||||
|
||||
self.assertRaises(exceptions.DNSZoneNotFound,
|
||||
self._dnsutils._get_zone,
|
||||
mock.sentinel.zone_name,
|
||||
ignore_missing=False)
|
||||
zone_manager.assert_called_once_with(Name=mock.sentinel.zone_name)
|
||||
|
||||
def test_zone_list(self):
|
||||
zone_manager = self._dnsutils._dns_manager.MicrosoftDNS_Zone
|
||||
zone_manager.return_value = [mock.Mock(Name=mock.sentinel.fake_name1),
|
||||
mock.Mock(Name=mock.sentinel.fake_name2)]
|
||||
|
||||
zone_list = self._dnsutils.zone_list()
|
||||
|
||||
expected_zone_list = [mock.sentinel.fake_name1,
|
||||
mock.sentinel.fake_name2]
|
||||
self.assertEqual(expected_zone_list, zone_list)
|
||||
zone_manager.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_exists(self, mock_get_zone):
|
||||
zone_already_exists = self._dnsutils.zone_exists(
|
||||
mock.sentinel.zone_name)
|
||||
mock_get_zone.assert_called_once_with(mock.sentinel.zone_name)
|
||||
|
||||
self.assertTrue(zone_already_exists)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_exists_false(self, mock_get_zone):
|
||||
mock_get_zone.return_value = None
|
||||
|
||||
zone_already_exists = self._dnsutils.zone_exists(
|
||||
mock.sentinel.zone_name)
|
||||
mock_get_zone.assert_called_once_with(mock.sentinel.zone_name)
|
||||
|
||||
self.assertFalse(zone_already_exists)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_get_zone_properties(self, mock_get_zone):
|
||||
mock_get_zone.return_value = mock.Mock(
|
||||
ZoneType=mock.sentinel.zone_type,
|
||||
DsIntegrated=mock.sentinel.ds_integrated,
|
||||
DataFile=mock.sentinel.data_file_name,
|
||||
MasterServers=[mock.sentinel.ip_addrs])
|
||||
|
||||
zone_properties = self._dnsutils.get_zone_properties(
|
||||
mock.sentinel.zone_name)
|
||||
expected_zone_props = {
|
||||
'zone_type': mock.sentinel.zone_type,
|
||||
'ds_integrated': mock.sentinel.ds_integrated,
|
||||
'master_servers': [mock.sentinel.ip_addrs],
|
||||
'data_file_name': mock.sentinel.data_file_name
|
||||
}
|
||||
self.assertEqual(expected_zone_props, zone_properties)
|
||||
mock_get_zone.assert_called_once_with(mock.sentinel.zone_name,
|
||||
ignore_missing=False)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, 'zone_exists')
|
||||
def test_zone_create(self, mock_zone_exists):
|
||||
mock_zone_exists.return_value = False
|
||||
zone_manager = self._dnsutils._dns_manager.MicrosoftDNS_Zone
|
||||
zone_manager.CreateZone.return_value = (mock.sentinel.zone_path,)
|
||||
|
||||
zone_path = self._dnsutils.zone_create(
|
||||
zone_name=mock.sentinel.zone_name,
|
||||
zone_type=mock.sentinel.zone_type,
|
||||
ds_integrated=mock.sentinel.ds_integrated,
|
||||
data_file_name=mock.sentinel.data_file_name,
|
||||
ip_addrs=mock.sentinel.ip_addrs,
|
||||
admin_email_name=mock.sentinel.admin_email_name)
|
||||
|
||||
zone_manager.CreateZone.assert_called_once_with(
|
||||
ZoneName=mock.sentinel.zone_name,
|
||||
ZoneType=mock.sentinel.zone_type,
|
||||
DsIntegrated=mock.sentinel.ds_integrated,
|
||||
DataFileName=mock.sentinel.data_file_name,
|
||||
IpAddr=mock.sentinel.ip_addrs,
|
||||
AdminEmailname=mock.sentinel.admin_email_name)
|
||||
mock_zone_exists.assert_called_once_with(mock.sentinel.zone_name)
|
||||
self.assertEqual(mock.sentinel.zone_path, zone_path)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, 'zone_exists')
|
||||
def test_zone_create_existing_zone(self, mock_zone_exists):
|
||||
self.assertRaises(exceptions.DNSZoneAlreadyExists,
|
||||
self._dnsutils.zone_create,
|
||||
zone_name=mock.sentinel.zone_name,
|
||||
zone_type=mock.sentinel.zone_type,
|
||||
ds_integrated=mock.sentinel.ds_integrated)
|
||||
mock_zone_exists.assert_called_once_with(mock.sentinel.zone_name)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_delete(self, mock_get_zone):
|
||||
self._dnsutils.zone_delete(mock.sentinel.zone_name)
|
||||
|
||||
mock_get_zone.assert_called_once_with(mock.sentinel.zone_name)
|
||||
mock_get_zone.return_value.Delete_.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_modify(self, mock_get_zone):
|
||||
mock_zone = mock.MagicMock(
|
||||
AllowUpdate=mock.sentinel.allowupdate,
|
||||
DisableWINSRecordReplication=mock.sentinel.disablewins,
|
||||
Notify=mock.sentinel.notify,
|
||||
SecureSecondaries=mock.sentinel.securesecondaries)
|
||||
mock_get_zone.return_value = mock_zone
|
||||
|
||||
self._dnsutils.zone_modify(
|
||||
mock.sentinel.zone_name,
|
||||
allow_update=None,
|
||||
disable_wins=mock.sentinel.disable_wins,
|
||||
notify=None,
|
||||
reverse=mock.sentinel.reverse,
|
||||
secure_secondaries=None)
|
||||
|
||||
self.assertEqual(mock.sentinel.allowupdate, mock_zone.AllowUpdate)
|
||||
self.assertEqual(mock.sentinel.disable_wins,
|
||||
mock_zone.DisableWINSRecordReplication)
|
||||
self.assertEqual(mock.sentinel.notify, mock_zone.Notify)
|
||||
self.assertEqual(mock.sentinel.reverse,
|
||||
mock_zone.Reverse)
|
||||
self.assertEqual(mock.sentinel.securesecondaries,
|
||||
mock_zone.SecureSecondaries)
|
||||
mock_zone.put.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_update_force_refresh(self, mock_get_zone):
|
||||
mock_zone = mock.MagicMock(DsIntegrated=False,
|
||||
ZoneType=constants.DNS_ZONE_TYPE_SECONDARY)
|
||||
mock_get_zone.return_value = mock_zone
|
||||
|
||||
self._dnsutils.zone_update(mock.sentinel.zone_name)
|
||||
|
||||
mock_get_zone.assert_called_once_with(
|
||||
mock.sentinel.zone_name,
|
||||
ignore_missing=False)
|
||||
mock_zone.ForceRefresh.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_update_from_ds(self, mock_get_zone):
|
||||
mock_zone = mock.MagicMock(DsIntegrated=True,
|
||||
ZoneType=constants.DNS_ZONE_TYPE_PRIMARY)
|
||||
mock_get_zone.return_value = mock_zone
|
||||
|
||||
self._dnsutils.zone_update(mock.sentinel.zone_name)
|
||||
|
||||
mock_get_zone.assert_called_once_with(
|
||||
mock.sentinel.zone_name,
|
||||
ignore_missing=False)
|
||||
mock_zone.UpdateFromDS.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, '_get_zone')
|
||||
def test_zone_update_reload_zone(self, mock_get_zone):
|
||||
mock_zone = mock.MagicMock(DsIntegrated=False,
|
||||
ZoneType=constants.DNS_ZONE_TYPE_PRIMARY)
|
||||
mock_get_zone.return_value = mock_zone
|
||||
|
||||
self._dnsutils.zone_update(mock.sentinel.zone_name)
|
||||
|
||||
mock_get_zone.assert_called_once_with(
|
||||
mock.sentinel.zone_name,
|
||||
ignore_missing=False)
|
||||
mock_zone.ReloadZone.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, 'zone_exists')
|
||||
def test_get_zone_serial(self, mock_zone_exists):
|
||||
mock_zone_exists.return_value = True
|
||||
fake_serial_number = 1
|
||||
msdns_soatype = self._dnsutils._dns_manager.MicrosoftDNS_SOAType
|
||||
msdns_soatype.return_value = [
|
||||
mock.Mock(SerialNumber=fake_serial_number)]
|
||||
|
||||
serial_number = self._dnsutils.get_zone_serial(mock.sentinel.zone_name)
|
||||
|
||||
expected_serial_number = fake_serial_number
|
||||
self.assertEqual(expected_serial_number, serial_number)
|
||||
msdns_soatype.assert_called_once_with(
|
||||
ContainerName=mock.sentinel.zone_name)
|
||||
mock_zone_exists.assert_called_once_with(mock.sentinel.zone_name)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, 'zone_exists')
|
||||
def test_get_zone_serial_zone_not_found(self, mock_zone_exists):
|
||||
mock_zone_exists.return_value = False
|
||||
|
||||
serial_number = self._dnsutils.get_zone_serial(mock.sentinel.zone_name)
|
||||
|
||||
self.assertIsNone(serial_number)
|
||||
mock_zone_exists.assert_called_once_with(mock.sentinel.zone_name)
|
||||
|
||||
@mock.patch.object(dnsutils.DNSUtils, 'zone_exists')
|
||||
def test_get_zone_serial_zone_soatype_not_found(self, mock_zone_exists):
|
||||
mock_zone_exists.return_value = True
|
||||
self._dnsutils._dns_manager.MicrosoftDNS_SOAType.return_value = []
|
||||
|
||||
serial_number = self._dnsutils.get_zone_serial(mock.sentinel.zone_name)
|
||||
|
||||
self.assertIsNone(serial_number)
|
||||
mock_zone_exists.assert_called_once_with(mock.sentinel.zone_name)
|
@ -1,325 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# 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 mock
|
||||
|
||||
import mock
|
||||
from oslotest import base
|
||||
import six
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils.io import ioutils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
|
||||
class IOUtilsTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(IOUtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._ioutils = ioutils.IOUtils()
|
||||
self._ioutils._win32_utils = mock.Mock()
|
||||
|
||||
self._mock_run = self._ioutils._win32_utils.run_and_check_output
|
||||
self._run_args = dict(kernel32_lib_func=True,
|
||||
failure_exc=exceptions.Win32IOException,
|
||||
eventlet_nonblocking_mode=False)
|
||||
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
self._ctypes.c_wchar_p = lambda x: (x, "c_wchar_p")
|
||||
|
||||
mock.patch.multiple(ioutils,
|
||||
ctypes=self._ctypes, kernel32=mock.DEFAULT,
|
||||
create=True).start()
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
ret_val = self._ioutils._run_and_check_output(
|
||||
mock.sentinel.func, mock.sentinel.arg)
|
||||
|
||||
self._mock_run.assert_called_once_with(mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
**self._run_args)
|
||||
self.assertEqual(self._mock_run.return_value, ret_val)
|
||||
|
||||
def test_wait_named_pipe(self):
|
||||
fake_timeout_s = 10
|
||||
self._ioutils.wait_named_pipe(mock.sentinel.pipe_name,
|
||||
timeout=fake_timeout_s)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
ioutils.kernel32.WaitNamedPipeW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.pipe_name),
|
||||
fake_timeout_s * 1000,
|
||||
**self._run_args)
|
||||
|
||||
def test_open(self):
|
||||
handle = self._ioutils.open(mock.sentinel.path,
|
||||
mock.sentinel.access,
|
||||
mock.sentinel.share_mode,
|
||||
mock.sentinel.create_disposition,
|
||||
mock.sentinel.flags)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
ioutils.kernel32.CreateFileW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.path),
|
||||
mock.sentinel.access,
|
||||
mock.sentinel.share_mode,
|
||||
None,
|
||||
mock.sentinel.create_disposition,
|
||||
mock.sentinel.flags,
|
||||
None,
|
||||
error_ret_vals=[w_const.INVALID_HANDLE_VALUE],
|
||||
**self._run_args)
|
||||
self.assertEqual(self._mock_run.return_value, handle)
|
||||
|
||||
def test_cancel_io(self):
|
||||
self._ioutils.cancel_io(mock.sentinel.handle,
|
||||
mock.sentinel.overlapped_struct,
|
||||
ignore_invalid_handle=True)
|
||||
|
||||
expected_ignored_err_codes = [w_const.ERROR_NOT_FOUND,
|
||||
w_const.ERROR_INVALID_HANDLE]
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
ioutils.kernel32.CancelIoEx,
|
||||
mock.sentinel.handle,
|
||||
self._ctypes.byref(mock.sentinel.overlapped_struct),
|
||||
ignored_error_codes=expected_ignored_err_codes,
|
||||
**self._run_args)
|
||||
|
||||
def test_close_handle(self):
|
||||
self._ioutils.close_handle(mock.sentinel.handle)
|
||||
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.CloseHandle,
|
||||
mock.sentinel.handle,
|
||||
**self._run_args)
|
||||
|
||||
def test_wait_io_completion(self):
|
||||
self._ioutils._wait_io_completion(mock.sentinel.event)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
ioutils.kernel32.WaitForSingleObjectEx,
|
||||
mock.sentinel.event,
|
||||
ioutils.WAIT_INFINITE_TIMEOUT,
|
||||
True,
|
||||
error_ret_vals=[w_const.WAIT_FAILED],
|
||||
**self._run_args)
|
||||
|
||||
def test_set_event(self):
|
||||
self._ioutils.set_event(mock.sentinel.event)
|
||||
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.SetEvent,
|
||||
mock.sentinel.event,
|
||||
**self._run_args)
|
||||
|
||||
def test_reset_event(self):
|
||||
self._ioutils._reset_event(mock.sentinel.event)
|
||||
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.ResetEvent,
|
||||
mock.sentinel.event,
|
||||
**self._run_args)
|
||||
|
||||
def test_create_event(self):
|
||||
event = self._ioutils._create_event(mock.sentinel.event_attributes,
|
||||
mock.sentinel.manual_reset,
|
||||
mock.sentinel.initial_state,
|
||||
mock.sentinel.name)
|
||||
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.CreateEventW,
|
||||
mock.sentinel.event_attributes,
|
||||
mock.sentinel.manual_reset,
|
||||
mock.sentinel.initial_state,
|
||||
mock.sentinel.name,
|
||||
error_ret_vals=[None],
|
||||
**self._run_args)
|
||||
self.assertEqual(self._mock_run.return_value, event)
|
||||
|
||||
@mock.patch.object(wintypes, 'LPOVERLAPPED', create=True)
|
||||
@mock.patch.object(wintypes, 'LPOVERLAPPED_COMPLETION_ROUTINE',
|
||||
lambda x: x, create=True)
|
||||
@mock.patch.object(ioutils.IOUtils, 'set_event')
|
||||
def test_get_completion_routine(self, mock_set_event,
|
||||
mock_LPOVERLAPPED):
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
compl_routine = self._ioutils.get_completion_routine(mock_callback)
|
||||
compl_routine(mock.sentinel.error_code,
|
||||
mock.sentinel.num_bytes,
|
||||
mock.sentinel.lpOverLapped)
|
||||
|
||||
self._ctypes.cast.assert_called_once_with(mock.sentinel.lpOverLapped,
|
||||
wintypes.LPOVERLAPPED)
|
||||
mock_overlapped_struct = self._ctypes.cast.return_value.contents
|
||||
mock_set_event.assert_called_once_with(mock_overlapped_struct.hEvent)
|
||||
mock_callback.assert_called_once_with(mock.sentinel.num_bytes)
|
||||
|
||||
@mock.patch.object(wintypes, 'OVERLAPPED', create=True)
|
||||
@mock.patch.object(ioutils.IOUtils, '_create_event')
|
||||
def test_get_new_overlapped_structure(self, mock_create_event,
|
||||
mock_OVERLAPPED):
|
||||
overlapped_struct = self._ioutils.get_new_overlapped_structure()
|
||||
|
||||
self.assertEqual(mock_OVERLAPPED.return_value, overlapped_struct)
|
||||
self.assertEqual(mock_create_event.return_value,
|
||||
overlapped_struct.hEvent)
|
||||
|
||||
@mock.patch.object(ioutils.IOUtils, '_reset_event')
|
||||
@mock.patch.object(ioutils.IOUtils, '_wait_io_completion')
|
||||
def test_read(self, mock_wait_io_completion, mock_reset_event):
|
||||
mock_overlapped_struct = mock.Mock()
|
||||
mock_event = mock_overlapped_struct.hEvent
|
||||
self._ioutils.read(mock.sentinel.handle, mock.sentinel.buff,
|
||||
mock.sentinel.num_bytes,
|
||||
mock_overlapped_struct,
|
||||
mock.sentinel.compl_routine)
|
||||
|
||||
mock_reset_event.assert_called_once_with(mock_event)
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.ReadFileEx,
|
||||
mock.sentinel.handle,
|
||||
mock.sentinel.buff,
|
||||
mock.sentinel.num_bytes,
|
||||
self._ctypes.byref(
|
||||
mock_overlapped_struct),
|
||||
mock.sentinel.compl_routine,
|
||||
**self._run_args)
|
||||
mock_wait_io_completion.assert_called_once_with(mock_event)
|
||||
|
||||
@mock.patch.object(ioutils.IOUtils, '_reset_event')
|
||||
@mock.patch.object(ioutils.IOUtils, '_wait_io_completion')
|
||||
def test_write(self, mock_wait_io_completion, mock_reset_event):
|
||||
mock_overlapped_struct = mock.Mock()
|
||||
mock_event = mock_overlapped_struct.hEvent
|
||||
self._ioutils.write(mock.sentinel.handle, mock.sentinel.buff,
|
||||
mock.sentinel.num_bytes,
|
||||
mock_overlapped_struct,
|
||||
mock.sentinel.compl_routine)
|
||||
|
||||
mock_reset_event.assert_called_once_with(mock_event)
|
||||
self._mock_run.assert_called_once_with(ioutils.kernel32.WriteFileEx,
|
||||
mock.sentinel.handle,
|
||||
mock.sentinel.buff,
|
||||
mock.sentinel.num_bytes,
|
||||
self._ctypes.byref(
|
||||
mock_overlapped_struct),
|
||||
mock.sentinel.compl_routine,
|
||||
**self._run_args)
|
||||
mock_wait_io_completion.assert_called_once_with(mock_event)
|
||||
|
||||
def test_buffer_ops(self):
|
||||
mock.patch.stopall()
|
||||
|
||||
fake_data = 'fake data'
|
||||
|
||||
buff = self._ioutils.get_buffer(len(fake_data), data=fake_data)
|
||||
buff_data = self._ioutils.get_buffer_data(buff, len(fake_data))
|
||||
|
||||
self.assertEqual(six.b(fake_data), buff_data)
|
||||
|
||||
|
||||
class IOQueueTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(IOQueueTestCase, self).setUp()
|
||||
|
||||
self._mock_queue = mock.Mock()
|
||||
queue_patcher = mock.patch.object(ioutils.Queue, 'Queue',
|
||||
new=self._mock_queue)
|
||||
queue_patcher.start()
|
||||
self.addCleanup(queue_patcher.stop)
|
||||
|
||||
self._mock_client_connected = mock.Mock()
|
||||
self._ioqueue = ioutils.IOQueue(self._mock_client_connected)
|
||||
|
||||
def test_get(self):
|
||||
self._mock_client_connected.isSet.return_value = True
|
||||
self._mock_queue.get.return_value = mock.sentinel.item
|
||||
|
||||
queue_item = self._ioqueue.get(timeout=mock.sentinel.timeout)
|
||||
|
||||
self._mock_queue.get.assert_called_once_with(
|
||||
self._ioqueue, timeout=mock.sentinel.timeout)
|
||||
self.assertEqual(mock.sentinel.item, queue_item)
|
||||
|
||||
def _test_get_timeout(self, continue_on_timeout=True):
|
||||
self._mock_client_connected.isSet.side_effect = [True, True, False]
|
||||
self._mock_queue.get.side_effect = ioutils.Queue.Empty
|
||||
|
||||
queue_item = self._ioqueue.get(timeout=mock.sentinel.timeout,
|
||||
continue_on_timeout=continue_on_timeout)
|
||||
|
||||
expected_calls_number = 2 if continue_on_timeout else 1
|
||||
self._mock_queue.get.assert_has_calls(
|
||||
[mock.call(self._ioqueue, timeout=mock.sentinel.timeout)] *
|
||||
expected_calls_number)
|
||||
self.assertIsNone(queue_item)
|
||||
|
||||
def test_get_continue_on_timeout(self):
|
||||
# Test that the queue blocks as long
|
||||
# as the client connected event is set.
|
||||
self._test_get_timeout()
|
||||
|
||||
def test_get_break_on_timeout(self):
|
||||
self._test_get_timeout(continue_on_timeout=False)
|
||||
|
||||
def test_put(self):
|
||||
self._mock_client_connected.isSet.side_effect = [True, True, False]
|
||||
self._mock_queue.put.side_effect = ioutils.Queue.Full
|
||||
|
||||
self._ioqueue.put(mock.sentinel.item,
|
||||
timeout=mock.sentinel.timeout)
|
||||
|
||||
self._mock_queue.put.assert_has_calls(
|
||||
[mock.call(self._ioqueue, mock.sentinel.item,
|
||||
timeout=mock.sentinel.timeout)] * 2)
|
||||
|
||||
@mock.patch.object(ioutils.IOQueue, 'get')
|
||||
def _test_get_burst(self, mock_get,
|
||||
exceeded_max_size=False):
|
||||
fake_data = 'fake_data'
|
||||
|
||||
mock_get.side_effect = [fake_data, fake_data, None]
|
||||
|
||||
if exceeded_max_size:
|
||||
max_size = 0
|
||||
else:
|
||||
max_size = constants.SERIAL_CONSOLE_BUFFER_SIZE
|
||||
|
||||
ret_val = self._ioqueue.get_burst(
|
||||
timeout=mock.sentinel.timeout,
|
||||
burst_timeout=mock.sentinel.burst_timeout,
|
||||
max_size=max_size)
|
||||
|
||||
expected_calls = [mock.call(timeout=mock.sentinel.timeout)]
|
||||
expected_ret_val = fake_data
|
||||
|
||||
if not exceeded_max_size:
|
||||
expected_calls.append(
|
||||
mock.call(timeout=mock.sentinel.burst_timeout,
|
||||
continue_on_timeout=False))
|
||||
expected_ret_val += fake_data
|
||||
|
||||
mock_get.assert_has_calls(expected_calls)
|
||||
self.assertEqual(expected_ret_val, ret_val)
|
||||
|
||||
def test_get_burst(self):
|
||||
self._test_get_burst()
|
||||
|
||||
def test_get_burst_exceeded_size(self):
|
||||
self._test_get_burst(exceeded_max_size=True)
|
@ -1,369 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 errno
|
||||
|
||||
import mock
|
||||
from oslotest import base
|
||||
from six.moves import builtins
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils.io import namedpipe
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
|
||||
|
||||
class NamedPipeTestCase(base.BaseTestCase):
|
||||
_FAKE_LOG_PATH = 'fake_log_path'
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_setup_io_structures')
|
||||
def setUp(self, mock_setup_structures):
|
||||
super(NamedPipeTestCase, self).setUp()
|
||||
|
||||
self._mock_input_queue = mock.Mock()
|
||||
self._mock_output_queue = mock.Mock()
|
||||
self._mock_client_connected = mock.Mock()
|
||||
|
||||
self._ioutils = mock.Mock()
|
||||
|
||||
threading_patcher = mock.patch.object(namedpipe, 'threading')
|
||||
threading_patcher.start()
|
||||
self.addCleanup(threading_patcher.stop)
|
||||
|
||||
self._handler = namedpipe.NamedPipeHandler(
|
||||
mock.sentinel.pipe_name,
|
||||
self._mock_input_queue,
|
||||
self._mock_output_queue,
|
||||
self._mock_client_connected,
|
||||
self._FAKE_LOG_PATH)
|
||||
self._handler._ioutils = self._ioutils
|
||||
|
||||
def _mock_setup_pipe_handler(self):
|
||||
self._handler._log_file_handle = mock.Mock()
|
||||
self._handler._pipe_handle = mock.sentinel.pipe_handle
|
||||
self._r_worker = mock.Mock()
|
||||
self._w_worker = mock.Mock()
|
||||
self._handler._workers = [self._r_worker, self._w_worker]
|
||||
self._handler._r_buffer = mock.Mock()
|
||||
self._handler._w_buffer = mock.Mock()
|
||||
self._handler._r_overlapped = mock.Mock()
|
||||
self._handler._w_overlapped = mock.Mock()
|
||||
self._handler._r_completion_routine = mock.Mock()
|
||||
self._handler._w_completion_routine = mock.Mock()
|
||||
|
||||
@mock.patch.object(builtins, 'open')
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_open_pipe')
|
||||
def test_start_pipe_handler(self, mock_open_pipe, mock_open):
|
||||
self._handler.start()
|
||||
|
||||
mock_open_pipe.assert_called_once_with()
|
||||
mock_open.assert_called_once_with(self._FAKE_LOG_PATH, 'ab', 1)
|
||||
self.assertEqual(mock_open.return_value,
|
||||
self._handler._log_file_handle)
|
||||
|
||||
thread = namedpipe.threading.Thread
|
||||
thread.assert_has_calls(
|
||||
[mock.call(target=self._handler._read_from_pipe),
|
||||
mock.call().setDaemon(True),
|
||||
mock.call().start(),
|
||||
mock.call(target=self._handler._write_to_pipe),
|
||||
mock.call().setDaemon(True),
|
||||
mock.call().start()])
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, 'stop')
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_open_pipe')
|
||||
def test_start_pipe_handler_exception(self, mock_open_pipe,
|
||||
mock_stop_handler):
|
||||
mock_open_pipe.side_effect = Exception
|
||||
|
||||
self.assertRaises(exceptions.OSWinException,
|
||||
self._handler.start)
|
||||
|
||||
mock_stop_handler.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_cleanup_handles')
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_cancel_io')
|
||||
def _test_stop_pipe_handler(self, mock_cancel_io,
|
||||
mock_cleanup_handles,
|
||||
workers_started=True):
|
||||
self._mock_setup_pipe_handler()
|
||||
if not workers_started:
|
||||
handler_workers = []
|
||||
self._handler._workers = handler_workers
|
||||
else:
|
||||
handler_workers = self._handler._workers
|
||||
self._r_worker.is_alive.side_effect = (True, False)
|
||||
self._w_worker.is_alive.return_value = False
|
||||
|
||||
self._handler.stop()
|
||||
|
||||
self._handler._stopped.set.assert_called_once_with()
|
||||
if not workers_started:
|
||||
mock_cleanup_handles.assert_called_once_with()
|
||||
else:
|
||||
self.assertFalse(mock_cleanup_handles.called)
|
||||
|
||||
if workers_started:
|
||||
mock_cancel_io.assert_called_once_with()
|
||||
self._r_worker.join.assert_called_once_with(0.5)
|
||||
self.assertFalse(self._w_worker.join.called)
|
||||
|
||||
self.assertEqual([], self._handler._workers)
|
||||
|
||||
def test_stop_pipe_handler_workers_started(self):
|
||||
self._test_stop_pipe_handler()
|
||||
|
||||
def test_stop_pipe_handler_workers_not_started(self):
|
||||
self._test_stop_pipe_handler(workers_started=False)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_close_pipe')
|
||||
def test_cleanup_handles(self, mock_close_pipe):
|
||||
self._mock_setup_pipe_handler()
|
||||
log_handle = self._handler._log_file_handle
|
||||
r_event = self._handler._r_overlapped.hEvent
|
||||
w_event = self._handler._w_overlapped.hEvent
|
||||
|
||||
self._handler._cleanup_handles()
|
||||
|
||||
mock_close_pipe.assert_called_once_with()
|
||||
log_handle.close.assert_called_once_with()
|
||||
self._ioutils.close_handle.assert_has_calls(
|
||||
[mock.call(r_event), mock.call(w_event)])
|
||||
|
||||
self.assertIsNone(self._handler._log_file_handle)
|
||||
self.assertIsNone(self._handler._r_overlapped.hEvent)
|
||||
self.assertIsNone(self._handler._w_overlapped.hEvent)
|
||||
|
||||
def test_setup_io_structures(self):
|
||||
self._handler._setup_io_structures()
|
||||
|
||||
self.assertEqual(self._ioutils.get_buffer.return_value,
|
||||
self._handler._r_buffer)
|
||||
self.assertEqual(self._ioutils.get_buffer.return_value,
|
||||
self._handler._w_buffer)
|
||||
self.assertEqual(
|
||||
self._ioutils.get_new_overlapped_structure.return_value,
|
||||
self._handler._r_overlapped)
|
||||
self.assertEqual(
|
||||
self._ioutils.get_new_overlapped_structure.return_value,
|
||||
self._handler._w_overlapped)
|
||||
self.assertEqual(
|
||||
self._ioutils.get_completion_routine.return_value,
|
||||
self._handler._r_completion_routine)
|
||||
self.assertEqual(
|
||||
self._ioutils.get_completion_routine.return_value,
|
||||
self._handler._w_completion_routine)
|
||||
self.assertIsNone(self._handler._log_file_handle)
|
||||
|
||||
self._ioutils.get_buffer.assert_has_calls(
|
||||
[mock.call(constants.SERIAL_CONSOLE_BUFFER_SIZE)] * 2)
|
||||
self._ioutils.get_completion_routine.assert_has_calls(
|
||||
[mock.call(self._handler._read_callback),
|
||||
mock.call()])
|
||||
|
||||
def test_open_pipe(self):
|
||||
self._handler._open_pipe()
|
||||
|
||||
self._ioutils.wait_named_pipe.assert_called_once_with(
|
||||
mock.sentinel.pipe_name)
|
||||
self._ioutils.open.assert_called_once_with(
|
||||
mock.sentinel.pipe_name,
|
||||
desired_access=(w_const.GENERIC_READ | w_const.GENERIC_WRITE),
|
||||
share_mode=(w_const.FILE_SHARE_READ | w_const.FILE_SHARE_WRITE),
|
||||
creation_disposition=w_const.OPEN_EXISTING,
|
||||
flags_and_attributes=w_const.FILE_FLAG_OVERLAPPED)
|
||||
|
||||
self.assertEqual(self._ioutils.open.return_value,
|
||||
self._handler._pipe_handle)
|
||||
|
||||
def test_close_pipe(self):
|
||||
self._mock_setup_pipe_handler()
|
||||
|
||||
self._handler._close_pipe()
|
||||
|
||||
self._ioutils.close_handle.assert_called_once_with(
|
||||
mock.sentinel.pipe_handle)
|
||||
self.assertIsNone(self._handler._pipe_handle)
|
||||
|
||||
def test_cancel_io(self):
|
||||
self._mock_setup_pipe_handler()
|
||||
|
||||
self._handler._cancel_io()
|
||||
|
||||
overlapped_structures = [self._handler._r_overlapped,
|
||||
self._handler._w_overlapped]
|
||||
|
||||
self._ioutils.cancel_io.assert_has_calls(
|
||||
[mock.call(self._handler._pipe_handle,
|
||||
overlapped_structure,
|
||||
ignore_invalid_handle=True)
|
||||
for overlapped_structure in overlapped_structures])
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_start_io_worker')
|
||||
def test_read_from_pipe(self, mock_start_worker):
|
||||
self._mock_setup_pipe_handler()
|
||||
|
||||
self._handler._read_from_pipe()
|
||||
|
||||
mock_start_worker.assert_called_once_with(
|
||||
self._ioutils.read,
|
||||
self._handler._r_buffer,
|
||||
self._handler._r_overlapped,
|
||||
self._handler._r_completion_routine)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_start_io_worker')
|
||||
def test_write_to_pipe(self, mock_start_worker):
|
||||
self._mock_setup_pipe_handler()
|
||||
|
||||
self._handler._write_to_pipe()
|
||||
|
||||
mock_start_worker.assert_called_once_with(
|
||||
self._ioutils.write,
|
||||
self._handler._w_buffer,
|
||||
self._handler._w_overlapped,
|
||||
self._handler._w_completion_routine,
|
||||
self._handler._get_data_to_write)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_cleanup_handles')
|
||||
def _test_start_io_worker(self, mock_cleanup_handles,
|
||||
buff_update_func=None, exception=None):
|
||||
self._handler._stopped.isSet.side_effect = [False, True]
|
||||
self._handler._pipe_handle = mock.sentinel.pipe_handle
|
||||
self._handler.stop = mock.Mock()
|
||||
|
||||
io_func = mock.Mock(side_effect=exception)
|
||||
fake_buffer = 'fake_buffer'
|
||||
|
||||
self._handler._start_io_worker(io_func, fake_buffer,
|
||||
mock.sentinel.overlapped_structure,
|
||||
mock.sentinel.completion_routine,
|
||||
buff_update_func)
|
||||
|
||||
if buff_update_func:
|
||||
num_bytes = buff_update_func()
|
||||
else:
|
||||
num_bytes = len(fake_buffer)
|
||||
|
||||
io_func.assert_called_once_with(mock.sentinel.pipe_handle,
|
||||
fake_buffer, num_bytes,
|
||||
mock.sentinel.overlapped_structure,
|
||||
mock.sentinel.completion_routine)
|
||||
|
||||
if exception:
|
||||
self._handler._stopped.set.assert_called_once_with()
|
||||
mock_cleanup_handles.assert_called_once_with()
|
||||
|
||||
def test_start_io_worker(self):
|
||||
self._test_start_io_worker()
|
||||
|
||||
def test_start_io_worker_with_buffer_update_method(self):
|
||||
self._test_start_io_worker(buff_update_func=mock.Mock())
|
||||
|
||||
def test_start_io_worker_exception(self):
|
||||
self._test_start_io_worker(exception=IOError)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_write_to_log')
|
||||
def test_read_callback(self, mock_write_to_log):
|
||||
self._mock_setup_pipe_handler()
|
||||
fake_data = self._ioutils.get_buffer_data.return_value
|
||||
|
||||
self._handler._read_callback(mock.sentinel.num_bytes)
|
||||
|
||||
self._ioutils.get_buffer_data.assert_called_once_with(
|
||||
self._handler._r_buffer, mock.sentinel.num_bytes)
|
||||
self._mock_output_queue.put.assert_called_once_with(fake_data)
|
||||
mock_write_to_log.assert_called_once_with(fake_data)
|
||||
|
||||
@mock.patch.object(namedpipe, 'time')
|
||||
def test_get_data_to_write(self, mock_time):
|
||||
self._mock_setup_pipe_handler()
|
||||
self._handler._stopped.isSet.side_effect = [False, False]
|
||||
self._mock_client_connected.isSet.side_effect = [False, True]
|
||||
fake_data = 'fake input data'
|
||||
self._mock_input_queue.get.return_value = fake_data
|
||||
|
||||
num_bytes = self._handler._get_data_to_write()
|
||||
|
||||
mock_time.sleep.assert_called_once_with(1)
|
||||
self._ioutils.write_buffer_data.assert_called_once_with(
|
||||
self._handler._w_buffer, fake_data)
|
||||
self.assertEqual(len(fake_data), num_bytes)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_rotate_logs')
|
||||
def _test_write_to_log(self, mock_rotate_logs, size_exceeded=False):
|
||||
self._mock_setup_pipe_handler()
|
||||
self._handler._stopped.isSet.return_value = False
|
||||
fake_handle = self._handler._log_file_handle
|
||||
fake_handle.tell.return_value = (constants.MAX_CONSOLE_LOG_FILE_SIZE
|
||||
if size_exceeded else 0)
|
||||
fake_data = 'fake_data'
|
||||
|
||||
self._handler._write_to_log(fake_data)
|
||||
|
||||
if size_exceeded:
|
||||
mock_rotate_logs.assert_called_once_with()
|
||||
|
||||
self._handler._log_file_handle.write.assert_called_once_with(
|
||||
fake_data)
|
||||
|
||||
def test_write_to_log(self):
|
||||
self._test_write_to_log()
|
||||
|
||||
def test_write_to_log_size_exceeded(self):
|
||||
self._test_write_to_log(size_exceeded=True)
|
||||
|
||||
@mock.patch.object(namedpipe.NamedPipeHandler, '_retry_if_file_in_use')
|
||||
@mock.patch.object(builtins, 'open')
|
||||
@mock.patch.object(namedpipe, 'os')
|
||||
def test_rotate_logs(self, mock_os, mock_open, mock_exec_retry):
|
||||
fake_archived_log_path = self._FAKE_LOG_PATH + '.1'
|
||||
mock_os.path.exists.return_value = True
|
||||
|
||||
self._mock_setup_pipe_handler()
|
||||
fake_handle = self._handler._log_file_handle
|
||||
|
||||
self._handler._rotate_logs()
|
||||
|
||||
fake_handle.flush.assert_called_once_with()
|
||||
fake_handle.close.assert_called_once_with()
|
||||
mock_os.path.exists.assert_called_once_with(
|
||||
fake_archived_log_path)
|
||||
|
||||
mock_exec_retry.assert_has_calls([mock.call(mock_os.remove,
|
||||
fake_archived_log_path),
|
||||
mock.call(mock_os.rename,
|
||||
self._FAKE_LOG_PATH,
|
||||
fake_archived_log_path)])
|
||||
|
||||
mock_open.assert_called_once_with(self._FAKE_LOG_PATH, 'ab', 1)
|
||||
self.assertEqual(mock_open.return_value,
|
||||
self._handler._log_file_handle)
|
||||
|
||||
@mock.patch.object(namedpipe, 'time')
|
||||
def test_retry_if_file_in_use_exceeded_retries(self, mock_time):
|
||||
class FakeWindowsException(Exception):
|
||||
errno = errno.EACCES
|
||||
|
||||
raise_count = self._handler._MAX_LOG_ROTATE_RETRIES + 1
|
||||
mock_func_side_eff = [FakeWindowsException] * raise_count
|
||||
mock_func = mock.Mock(side_effect=mock_func_side_eff)
|
||||
|
||||
with mock.patch.object(namedpipe, 'WindowsError',
|
||||
FakeWindowsException, create=True):
|
||||
self.assertRaises(FakeWindowsException,
|
||||
self._handler._retry_if_file_in_use,
|
||||
mock_func, mock.sentinel.arg)
|
||||
mock_time.sleep.assert_has_calls(
|
||||
[mock.call(1)] * self._handler._MAX_LOG_ROTATE_RETRIES)
|
@ -1,407 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils.metrics import metricsutils
|
||||
|
||||
|
||||
class MetricsUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V MetricsUtils class."""
|
||||
|
||||
_FAKE_RET_VAL = 0
|
||||
_FAKE_PORT = "fake's port name"
|
||||
|
||||
def setUp(self):
|
||||
super(MetricsUtilsTestCase, self).setUp()
|
||||
self.utils = metricsutils.MetricsUtils()
|
||||
self.utils._conn_attr = mock.MagicMock()
|
||||
|
||||
def test_cache_metrics_defs(self):
|
||||
mock_metric_def = mock.Mock(ElementName=mock.sentinel.elementname)
|
||||
self.utils._conn.CIM_BaseMetricDefinition.return_value = [
|
||||
mock_metric_def]
|
||||
self.utils._cache_metrics_defs()
|
||||
expected_cache_metrics = {mock.sentinel.elementname: mock_metric_def}
|
||||
self.assertEqual(expected_cache_metrics, self.utils._metrics_defs_obj)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_enable_metrics')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm')
|
||||
def test_enable_vm_metrics_collection(
|
||||
self, mock_get_vm, mock_get_vm_resources, mock_enable_metrics):
|
||||
mock_vm = mock_get_vm.return_value
|
||||
mock_disk = mock.MagicMock()
|
||||
mock_dvd = mock.MagicMock(
|
||||
ResourceSubType=self.utils._DVD_DISK_RES_SUB_TYPE)
|
||||
mock_get_vm_resources.return_value = [mock_disk, mock_dvd]
|
||||
|
||||
self.utils.enable_vm_metrics_collection(mock.sentinel.vm_name)
|
||||
|
||||
metrics_names = [self.utils._CPU_METRICS,
|
||||
self.utils._MEMORY_METRICS]
|
||||
mock_enable_metrics.assert_has_calls(
|
||||
[mock.call(mock_disk), mock.call(mock_vm, metrics_names)])
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_enable_metrics')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_switch_port')
|
||||
def test_enable_switch_port_metrics_collection(self, mock_get_port,
|
||||
mock_enable_metrics):
|
||||
self.utils.enable_port_metrics_collection(mock.sentinel.port_name)
|
||||
|
||||
mock_get_port.assert_called_once_with(mock.sentinel.port_name)
|
||||
metrics = [self.utils._NET_IN_METRICS,
|
||||
self.utils._NET_OUT_METRICS]
|
||||
mock_enable_metrics.assert_called_once_with(
|
||||
mock_get_port.return_value, metrics)
|
||||
|
||||
def _check_enable_metrics(self, metrics=None, definition=None):
|
||||
mock_element = mock.MagicMock()
|
||||
|
||||
self.utils._enable_metrics(mock_element, metrics)
|
||||
|
||||
self.utils._metrics_svc.ControlMetrics.assert_called_once_with(
|
||||
Subject=mock_element.path_.return_value,
|
||||
Definition=definition,
|
||||
MetricCollectionEnabled=self.utils._METRICS_ENABLED)
|
||||
|
||||
def test_enable_metrics_no_metrics(self):
|
||||
self._check_enable_metrics()
|
||||
|
||||
def test_enable_metrics(self):
|
||||
metrics_name = self.utils._CPU_METRICS
|
||||
metrics_def = mock.MagicMock()
|
||||
self.utils._metrics_defs_obj = {metrics_name: metrics_def}
|
||||
self._check_enable_metrics([metrics_name, mock.sentinel.metrics_name],
|
||||
metrics_def.path_.return_value)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_metrics')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm')
|
||||
def test_get_cpu_metrics(self, mock_get_vm, mock_get_vm_resources,
|
||||
mock_get_metrics):
|
||||
fake_cpu_count = 2
|
||||
fake_uptime = 1000
|
||||
fake_cpu_metrics_val = 2000
|
||||
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._CPU_METRICS: mock.sentinel.metrics}
|
||||
|
||||
mock_vm = mock_get_vm.return_value
|
||||
mock_vm.OnTimeInMilliseconds = fake_uptime
|
||||
mock_cpu = mock.MagicMock(VirtualQuantity=fake_cpu_count)
|
||||
mock_get_vm_resources.return_value = [mock_cpu]
|
||||
|
||||
mock_metric = mock.MagicMock(MetricValue=fake_cpu_metrics_val)
|
||||
mock_get_metrics.return_value = [mock_metric]
|
||||
|
||||
cpu_metrics = self.utils.get_cpu_metrics(mock.sentinel.vm_name)
|
||||
|
||||
self.assertEqual(3, len(cpu_metrics))
|
||||
self.assertEqual(fake_cpu_metrics_val, cpu_metrics[0])
|
||||
self.assertEqual(fake_cpu_count, cpu_metrics[1])
|
||||
self.assertEqual(fake_uptime, cpu_metrics[2])
|
||||
|
||||
mock_get_vm.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_vm_resources.assert_called_once_with(
|
||||
mock.sentinel.vm_name, self.utils._PROCESSOR_SETTING_DATA_CLASS)
|
||||
mock_get_metrics.assert_called_once_with(mock_vm,
|
||||
mock.sentinel.metrics)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_metrics')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm')
|
||||
def test_get_memory_metrics(self, mock_get_vm, mock_get_metrics):
|
||||
mock_vm = mock_get_vm.return_value
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._MEMORY_METRICS: mock.sentinel.metrics}
|
||||
|
||||
metrics_memory = mock.MagicMock()
|
||||
metrics_memory.MetricValue = 3
|
||||
mock_get_metrics.return_value = [metrics_memory]
|
||||
|
||||
response = self.utils.get_memory_metrics(mock.sentinel.vm_name)
|
||||
|
||||
self.assertEqual(3, response)
|
||||
mock_get_vm.assert_called_once_with(mock.sentinel.vm_name)
|
||||
mock_get_metrics.assert_called_once_with(mock_vm,
|
||||
mock.sentinel.metrics)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(metricsutils.MetricsUtils,
|
||||
'_sum_metrics_values_by_defs')
|
||||
@mock.patch.object(metricsutils.MetricsUtils,
|
||||
'_get_metrics_value_instances')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
def test_get_vnic_metrics(self, mock_get_vm_resources,
|
||||
mock_get_value_instances, mock_sum_by_defs,
|
||||
mock_get_element_associated_class):
|
||||
fake_rx_mb = 1000
|
||||
fake_tx_mb = 2000
|
||||
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._NET_IN_METRICS: mock.sentinel.net_in_metrics,
|
||||
self.utils._NET_OUT_METRICS: mock.sentinel.net_out_metrics}
|
||||
|
||||
mock_port = mock.MagicMock(Parent=mock.sentinel.vnic_path)
|
||||
mock_vnic = mock.MagicMock(ElementName=mock.sentinel.element_name,
|
||||
Address=mock.sentinel.address)
|
||||
mock_vnic.path_.return_value = mock.sentinel.vnic_path
|
||||
mock_get_vm_resources.side_effect = [[mock_port], [mock_vnic]]
|
||||
mock_sum_by_defs.return_value = [fake_rx_mb, fake_tx_mb]
|
||||
|
||||
vnic_metrics = list(
|
||||
self.utils.get_vnic_metrics(mock.sentinel.vm_name))
|
||||
|
||||
self.assertEqual(1, len(vnic_metrics))
|
||||
self.assertEqual(fake_rx_mb, vnic_metrics[0]['rx_mb'])
|
||||
self.assertEqual(fake_tx_mb, vnic_metrics[0]['tx_mb'])
|
||||
self.assertEqual(mock.sentinel.element_name,
|
||||
vnic_metrics[0]['element_name'])
|
||||
self.assertEqual(mock.sentinel.address, vnic_metrics[0]['address'])
|
||||
|
||||
mock_get_vm_resources.assert_has_calls([
|
||||
mock.call(mock.sentinel.vm_name, self.utils._PORT_ALLOC_SET_DATA),
|
||||
mock.call(mock.sentinel.vm_name,
|
||||
self.utils._SYNTH_ETH_PORT_SET_DATA)])
|
||||
mock_get_value_instances.assert_called_once_with(
|
||||
mock_get_element_associated_class.return_value,
|
||||
self.utils._BASE_METRICS_VALUE)
|
||||
mock_sum_by_defs.assert_called_once_with(
|
||||
mock_get_value_instances.return_value,
|
||||
[mock.sentinel.net_in_metrics, mock.sentinel.net_out_metrics])
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_metrics_values')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
def test_get_disk_metrics(self, mock_get_vm_resources,
|
||||
mock_get_metrics_values):
|
||||
fake_read_mb = 1000
|
||||
fake_write_mb = 2000
|
||||
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._DISK_RD_METRICS: mock.sentinel.disk_rd_metrics,
|
||||
self.utils._DISK_WR_METRICS: mock.sentinel.disk_wr_metrics}
|
||||
|
||||
mock_disk = mock.MagicMock(HostResource=[mock.sentinel.host_resource],
|
||||
InstanceID=mock.sentinel.instance_id)
|
||||
mock_get_vm_resources.return_value = [mock_disk]
|
||||
mock_get_metrics_values.return_value = [fake_read_mb, fake_write_mb]
|
||||
|
||||
disk_metrics = list(
|
||||
self.utils.get_disk_metrics(mock.sentinel.vm_name))
|
||||
|
||||
self.assertEqual(1, len(disk_metrics))
|
||||
self.assertEqual(fake_read_mb, disk_metrics[0]['read_mb'])
|
||||
self.assertEqual(fake_write_mb, disk_metrics[0]['write_mb'])
|
||||
self.assertEqual(mock.sentinel.instance_id,
|
||||
disk_metrics[0]['instance_id'])
|
||||
self.assertEqual(mock.sentinel.host_resource,
|
||||
disk_metrics[0]['host_resource'])
|
||||
|
||||
mock_get_vm_resources.assert_called_once_with(
|
||||
mock.sentinel.vm_name,
|
||||
self.utils._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
metrics = [mock.sentinel.disk_rd_metrics,
|
||||
mock.sentinel.disk_wr_metrics]
|
||||
mock_get_metrics_values.assert_called_once_with(mock_disk, metrics)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_metrics_values')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
def test_get_disk_latency_metrics(self, mock_get_vm_resources,
|
||||
mock_get_metrics_values):
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._DISK_LATENCY_METRICS: mock.sentinel.metrics}
|
||||
|
||||
mock_disk = mock.MagicMock(HostResource=[mock.sentinel.host_resource],
|
||||
InstanceID=mock.sentinel.instance_id)
|
||||
mock_get_vm_resources.return_value = [mock_disk]
|
||||
mock_get_metrics_values.return_value = [mock.sentinel.latency]
|
||||
|
||||
disk_metrics = list(
|
||||
self.utils.get_disk_latency_metrics(mock.sentinel.vm_name))
|
||||
|
||||
self.assertEqual(1, len(disk_metrics))
|
||||
self.assertEqual(mock.sentinel.latency,
|
||||
disk_metrics[0]['disk_latency'])
|
||||
self.assertEqual(mock.sentinel.instance_id,
|
||||
disk_metrics[0]['instance_id'])
|
||||
mock_get_vm_resources.assert_called_once_with(
|
||||
mock.sentinel.vm_name,
|
||||
self.utils._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
mock_get_metrics_values.assert_called_once_with(
|
||||
mock_disk, [mock.sentinel.metrics])
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_metrics_values')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_resources')
|
||||
def test_get_disk_iops_metrics(self, mock_get_vm_resources,
|
||||
mock_get_metrics_values):
|
||||
self.utils._metrics_defs_obj = {
|
||||
self.utils._DISK_IOPS_METRICS: mock.sentinel.metrics}
|
||||
mock_disk = mock.MagicMock(HostResource=[mock.sentinel.host_resource],
|
||||
InstanceID=mock.sentinel.instance_id)
|
||||
mock_get_vm_resources.return_value = [mock_disk]
|
||||
mock_get_metrics_values.return_value = [mock.sentinel.iops]
|
||||
|
||||
disk_metrics = list(
|
||||
self.utils.get_disk_iops_count(mock.sentinel.vm_name))
|
||||
|
||||
self.assertEqual(1, len(disk_metrics))
|
||||
self.assertEqual(mock.sentinel.iops,
|
||||
disk_metrics[0]['iops_count'])
|
||||
self.assertEqual(mock.sentinel.instance_id,
|
||||
disk_metrics[0]['instance_id'])
|
||||
mock_get_vm_resources.assert_called_once_with(
|
||||
mock.sentinel.vm_name,
|
||||
self.utils._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
mock_get_metrics_values.assert_called_once_with(
|
||||
mock_disk, [mock.sentinel.metrics])
|
||||
|
||||
def test_sum_metrics_values(self):
|
||||
mock_metric = mock.MagicMock(MetricValue='100')
|
||||
result = self.utils._sum_metrics_values([mock_metric] * 2)
|
||||
self.assertEqual(200, result)
|
||||
|
||||
def test_sum_metrics_values_by_defs(self):
|
||||
mock_metric = mock.MagicMock(MetricDefinitionId=mock.sentinel.def_id,
|
||||
MetricValue='100')
|
||||
mock_metric_useless = mock.MagicMock(MetricValue='200')
|
||||
mock_metric_def = mock.MagicMock(Id=mock.sentinel.def_id)
|
||||
|
||||
result = self.utils._sum_metrics_values_by_defs(
|
||||
[mock_metric, mock_metric_useless], [None, mock_metric_def])
|
||||
|
||||
self.assertEqual([0, 100], result)
|
||||
|
||||
def test_get_metrics_value_instances(self):
|
||||
FAKE_CLASS_NAME = "FAKE_CLASS"
|
||||
mock_el_metric = mock.MagicMock()
|
||||
mock_el_metric_2 = mock.MagicMock()
|
||||
mock_el_metric_2.path.return_value = mock.Mock(Class=FAKE_CLASS_NAME)
|
||||
|
||||
self.utils._conn.Msvm_MetricForME.side_effect = [
|
||||
[], [mock.Mock(Dependent=mock_el_metric_2)]]
|
||||
|
||||
returned = self.utils._get_metrics_value_instances(
|
||||
[mock_el_metric, mock_el_metric_2], FAKE_CLASS_NAME)
|
||||
|
||||
expected_return = [mock_el_metric_2]
|
||||
self.assertEqual(expected_return, returned)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils,
|
||||
'_sum_metrics_values_by_defs')
|
||||
def test_get_metrics_values(self, mock_sum_by_defs):
|
||||
mock_element = mock.MagicMock()
|
||||
self.utils._conn.Msvm_MetricForME.return_value = [
|
||||
mock.Mock(Dependent=mock.sentinel.metric),
|
||||
mock.Mock(Dependent=mock.sentinel.another_metric)]
|
||||
|
||||
resulted_metrics_sum = self.utils._get_metrics_values(
|
||||
mock_element, mock.sentinel.metrics_defs)
|
||||
|
||||
self.utils._conn.Msvm_MetricForME.assert_called_once_with(
|
||||
Antecedent=mock_element.path_.return_value)
|
||||
mock_sum_by_defs.assert_called_once_with(
|
||||
[mock.sentinel.metric, mock.sentinel.another_metric],
|
||||
mock.sentinel.metrics_defs)
|
||||
expected_metrics_sum = mock_sum_by_defs.return_value
|
||||
self.assertEqual(expected_metrics_sum, resulted_metrics_sum)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_filter_metrics')
|
||||
def test_get_metrics(self, mock_filter_metrics):
|
||||
mock_metric = mock.MagicMock()
|
||||
mock_element = mock.MagicMock()
|
||||
self.utils._conn.Msvm_MetricForME.return_value = [mock_metric]
|
||||
|
||||
result = self.utils._get_metrics(mock_element,
|
||||
mock.sentinel.metrics_def)
|
||||
|
||||
self.assertEqual(mock_filter_metrics.return_value, result)
|
||||
self.utils._conn.Msvm_MetricForME.assert_called_once_with(
|
||||
Antecedent=mock_element.path_.return_value)
|
||||
mock_filter_metrics.assert_called_once_with(
|
||||
[mock_metric.Dependent],
|
||||
mock.sentinel.metrics_def)
|
||||
|
||||
def test_filter_metrics(self):
|
||||
mock_metric = mock.MagicMock(MetricDefinitionId=mock.sentinel.def_id)
|
||||
mock_bad_metric = mock.MagicMock()
|
||||
mock_metric_def = mock.MagicMock(Id=mock.sentinel.def_id)
|
||||
|
||||
result = self.utils._filter_metrics([mock_bad_metric, mock_metric],
|
||||
mock_metric_def)
|
||||
|
||||
self.assertEqual([mock_metric], result)
|
||||
|
||||
@mock.patch.object(_wqlutils, 'get_element_associated_class')
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_get_vm_setting_data')
|
||||
def test_get_vm_resources(self, mock_get_vm_setting_data,
|
||||
mock_get_element_associated_class):
|
||||
result = self.utils._get_vm_resources(mock.sentinel.vm_name,
|
||||
mock.sentinel.resource_class)
|
||||
|
||||
mock_get_vm_setting_data.assert_called_once_with(mock.sentinel.vm_name)
|
||||
vm_setting_data = mock_get_vm_setting_data.return_value
|
||||
mock_get_element_associated_class.assert_called_once_with(
|
||||
self.utils._conn, mock.sentinel.resource_class,
|
||||
element_instance_id=vm_setting_data.InstanceID)
|
||||
self.assertEqual(mock_get_element_associated_class.return_value,
|
||||
result)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_unique_result')
|
||||
def test_get_vm(self, mock_unique_result):
|
||||
result = self.utils._get_vm(mock.sentinel.vm_name)
|
||||
|
||||
self.assertEqual(mock_unique_result.return_value, result)
|
||||
conn_class = self.utils._conn.Msvm_ComputerSystem
|
||||
conn_class.assert_called_once_with(ElementName=mock.sentinel.vm_name)
|
||||
mock_unique_result.assert_called_once_with(conn_class.return_value,
|
||||
mock.sentinel.vm_name)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_unique_result')
|
||||
def test_get_switch_port(self, mock_unique_result):
|
||||
result = self.utils._get_switch_port(mock.sentinel.port_name)
|
||||
|
||||
self.assertEqual(mock_unique_result.return_value, result)
|
||||
conn_class = self.utils._conn.Msvm_SyntheticEthernetPortSettingData
|
||||
conn_class.assert_called_once_with(ElementName=mock.sentinel.port_name)
|
||||
mock_unique_result.assert_called_once_with(conn_class.return_value,
|
||||
mock.sentinel.port_name)
|
||||
|
||||
@mock.patch.object(metricsutils.MetricsUtils, '_unique_result')
|
||||
def test_get_vm_setting_data(self, mock_unique_result):
|
||||
result = self.utils._get_vm_setting_data(mock.sentinel.vm_name)
|
||||
|
||||
self.assertEqual(mock_unique_result.return_value, result)
|
||||
conn_class = self.utils._conn.Msvm_VirtualSystemSettingData
|
||||
conn_class.assert_called_once_with(ElementName=mock.sentinel.vm_name)
|
||||
mock_unique_result.assert_called_once_with(conn_class.return_value,
|
||||
mock.sentinel.vm_name)
|
||||
|
||||
def test_unique_result_not_found(self):
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.utils._unique_result,
|
||||
[], mock.sentinel.resource_name)
|
||||
|
||||
def test_unique_result_duplicate(self):
|
||||
self.assertRaises(exceptions.OSWinException,
|
||||
self.utils._unique_result,
|
||||
[mock.ANY, mock.ANY], mock.sentinel.resource_name)
|
||||
|
||||
def test_unique_result(self):
|
||||
result = self.utils._unique_result([mock.sentinel.obj],
|
||||
mock.sentinel.resource_name)
|
||||
self.assertEqual(mock.sentinel.obj, result)
|
File diff suppressed because it is too large
Load Diff
@ -1,259 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions SRL
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for the Hyper-V NVGRE support.
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.network import nvgreutils
|
||||
|
||||
|
||||
class TestNvgreUtils(test_base.OsWinBaseTestCase):
|
||||
|
||||
_FAKE_RDID = 'fake_rdid'
|
||||
_FAKE_NETWORK_NAME = 'fake_network_name'
|
||||
_FAKE_VSID = 9001
|
||||
_FAKE_DEST_PREFIX = 'fake_dest_prefix'
|
||||
_FAKE_GW_BAD = '10.0.0.1'
|
||||
_FAKE_GW = '10.0.0.2'
|
||||
|
||||
def setUp(self):
|
||||
super(TestNvgreUtils, self).setUp()
|
||||
self.utils = nvgreutils.NvgreUtils()
|
||||
self.utils._utils = mock.MagicMock()
|
||||
self.utils._scimv2 = mock.MagicMock()
|
||||
|
||||
def _create_mock_binding(self):
|
||||
binding = mock.MagicMock()
|
||||
binding.BindName = self.utils._WNV_BIND_NAME
|
||||
binding.Name = mock.sentinel.fake_network
|
||||
|
||||
net_binds = self.utils._scimv2.MSFT_NetAdapterBindingSettingData
|
||||
net_binds.return_value = [binding]
|
||||
return binding
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, 'get_network_iface_ip')
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_address(self, mock_get_iface_index,
|
||||
mock_get_iface_ip):
|
||||
mock_get_iface_index.return_value = mock.sentinel.iface_index
|
||||
mock_get_iface_ip.return_value = (mock.sentinel.iface_ip,
|
||||
mock.sentinel.prefix_len)
|
||||
|
||||
provider_addr = mock.MagicMock()
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationProviderAddressSettingData
|
||||
obj_class.return_value = [provider_addr]
|
||||
|
||||
self.utils.create_provider_address(mock.sentinel.fake_network,
|
||||
mock.sentinel.fake_vlan_id)
|
||||
|
||||
self.assertTrue(provider_addr.Delete_.called)
|
||||
obj_class.new.assert_called_once_with(
|
||||
ProviderAddress=mock.sentinel.iface_ip,
|
||||
VlanID=mock.sentinel.fake_vlan_id,
|
||||
InterfaceIndex=mock.sentinel.iface_index,
|
||||
PrefixLength=mock.sentinel.prefix_len)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, 'get_network_iface_ip')
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_address_exc(self, mock_get_iface_index,
|
||||
mock_get_iface_ip):
|
||||
mock_get_iface_ip.return_value = (None, None)
|
||||
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.utils.create_provider_address,
|
||||
mock.sentinel.fake_network,
|
||||
mock.sentinel.fake_vlan_id)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, 'get_network_iface_ip')
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_address_exists(self, mock_get_iface_index,
|
||||
mock_get_iface_ip):
|
||||
mock_get_iface_index.return_value = mock.sentinel.iface_index
|
||||
mock_get_iface_ip.return_value = (mock.sentinel.iface_ip,
|
||||
mock.sentinel.prefix_len)
|
||||
|
||||
provider_addr = mock.MagicMock(
|
||||
VlanID=mock.sentinel.fake_vlan_id,
|
||||
InterfaceIndex=mock.sentinel.iface_index)
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationProviderAddressSettingData
|
||||
obj_class.return_value = [provider_addr]
|
||||
|
||||
self.utils.create_provider_address(mock.sentinel.fake_network,
|
||||
mock.sentinel.fake_vlan_id)
|
||||
|
||||
self.assertFalse(obj_class.new.called)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_route(self, mock_get_iface_index):
|
||||
mock_get_iface_index.return_value = mock.sentinel.iface_index
|
||||
self.utils._scimv2.MSFT_NetVirtualizationProviderRouteSettingData = (
|
||||
mock.MagicMock(return_value=[]))
|
||||
|
||||
self.utils.create_provider_route(mock.sentinel.fake_network)
|
||||
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationProviderRouteSettingData
|
||||
obj_class.new.assert_called_once_with(
|
||||
InterfaceIndex=mock.sentinel.iface_index,
|
||||
DestinationPrefix='%s/0' % constants.IPV4_DEFAULT,
|
||||
NextHop=constants.IPV4_DEFAULT)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_route_none(self, mock_get_iface_index):
|
||||
mock_get_iface_index.return_value = None
|
||||
|
||||
self.utils.create_provider_route(mock.sentinel.fake_network)
|
||||
scimv2 = self.utils._scimv2
|
||||
self.assertFalse(
|
||||
scimv2.MSFT_NetVirtualizationProviderRouteSettingData.new.called)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_iface_index')
|
||||
def test_create_provider_route_exists(self, mock_get_iface_index):
|
||||
mock_get_iface_index.return_value = mock.sentinel.iface_index
|
||||
self.utils._scimv2.MSFT_NetVirtualizationProviderRouteSettingData = (
|
||||
mock.MagicMock(return_value=[mock.MagicMock()]))
|
||||
|
||||
self.utils.create_provider_route(mock.sentinel.fake_network)
|
||||
|
||||
scimv2 = self.utils._scimv2
|
||||
self.assertFalse(
|
||||
scimv2.MSFT_NetVirtualizationProviderRouteSettingData.new.called)
|
||||
|
||||
def test_clear_customer_routes(self):
|
||||
cls = self.utils._scimv2.MSFT_NetVirtualizationCustomerRouteSettingData
|
||||
route = mock.MagicMock()
|
||||
cls.return_value = [route]
|
||||
|
||||
self.utils.clear_customer_routes(mock.sentinel.vsid)
|
||||
|
||||
cls.assert_called_once_with(VirtualSubnetID=mock.sentinel.vsid)
|
||||
route.Delete_.assert_called_once_with()
|
||||
|
||||
def test_create_customer_route(self):
|
||||
self.utils.create_customer_route(
|
||||
mock.sentinel.fake_vsid, mock.sentinel.dest_prefix,
|
||||
mock.sentinel.next_hop, self._FAKE_RDID)
|
||||
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationCustomerRouteSettingData
|
||||
obj_class.new.assert_called_once_with(
|
||||
VirtualSubnetID=mock.sentinel.fake_vsid,
|
||||
DestinationPrefix=mock.sentinel.dest_prefix,
|
||||
NextHop=mock.sentinel.next_hop,
|
||||
Metric=255,
|
||||
RoutingDomainID='{%s}' % self._FAKE_RDID)
|
||||
|
||||
def _check_create_lookup_record(self, customer_addr, expected_type):
|
||||
lookup = mock.MagicMock()
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationLookupRecordSettingData
|
||||
obj_class.return_value = [lookup]
|
||||
|
||||
self.utils.create_lookup_record(mock.sentinel.provider_addr,
|
||||
customer_addr,
|
||||
mock.sentinel.mac_addr,
|
||||
mock.sentinel.fake_vsid)
|
||||
|
||||
self.assertTrue(lookup.Delete_.called)
|
||||
obj_class.new.assert_called_once_with(
|
||||
VirtualSubnetID=mock.sentinel.fake_vsid,
|
||||
Rule=self.utils._TRANSLATE_ENCAP,
|
||||
Type=expected_type,
|
||||
MACAddress=mock.sentinel.mac_addr,
|
||||
CustomerAddress=customer_addr,
|
||||
ProviderAddress=mock.sentinel.provider_addr)
|
||||
|
||||
def test_create_lookup_record_l2_only(self):
|
||||
self._check_create_lookup_record(
|
||||
constants.IPV4_DEFAULT,
|
||||
self.utils._LOOKUP_RECORD_TYPE_L2_ONLY)
|
||||
|
||||
def test_create_lookup_record_static(self):
|
||||
self._check_create_lookup_record(
|
||||
mock.sentinel.customer_addr,
|
||||
self.utils._LOOKUP_RECORD_TYPE_STATIC)
|
||||
|
||||
def test_create_lookup_record_exists(self):
|
||||
lookup = mock.MagicMock(VirtualSubnetID=mock.sentinel.fake_vsid,
|
||||
ProviderAddress=mock.sentinel.provider_addr,
|
||||
CustomerAddress=mock.sentinel.customer_addr,
|
||||
MACAddress=mock.sentinel.mac_addr)
|
||||
scimv2 = self.utils._scimv2
|
||||
obj_class = scimv2.MSFT_NetVirtualizationLookupRecordSettingData
|
||||
obj_class.return_value = [lookup]
|
||||
|
||||
self.utils.create_lookup_record(mock.sentinel.provider_addr,
|
||||
mock.sentinel.customer_addr,
|
||||
mock.sentinel.mac_addr,
|
||||
mock.sentinel.fake_vsid)
|
||||
self.assertFalse(obj_class.new.called)
|
||||
|
||||
def test_get_network_iface_index_cached(self):
|
||||
self.utils._net_if_indexes[mock.sentinel.fake_network] = (
|
||||
mock.sentinel.iface_index)
|
||||
|
||||
index = self.utils._get_network_iface_index(mock.sentinel.fake_network)
|
||||
|
||||
self.assertEqual(mock.sentinel.iface_index, index)
|
||||
self.assertFalse(self.utils._scimv2.MSFT_NetAdapter.called)
|
||||
|
||||
def test_get_network_iface_index_not_found(self):
|
||||
self.utils._scimv2.MSFT_NetAdapter.return_value = []
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.utils._get_network_iface_index,
|
||||
mock.sentinel.network_name)
|
||||
|
||||
def test_get_network_iface_index(self):
|
||||
fake_network = mock.MagicMock(InterfaceIndex=mock.sentinel.iface_index)
|
||||
self.utils._scimv2.MSFT_NetAdapter.return_value = [fake_network]
|
||||
description = (
|
||||
self.utils._utils.get_vswitch_external_network_name.return_value)
|
||||
|
||||
index = self.utils._get_network_iface_index(mock.sentinel.fake_network)
|
||||
|
||||
self.assertEqual(mock.sentinel.iface_index, index)
|
||||
self.assertIn(mock.sentinel.fake_network, self.utils._net_if_indexes)
|
||||
self.utils._scimv2.MSFT_NetAdapter.assert_called_once_with(
|
||||
InterfaceDescription=description)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_ifaces_by_name')
|
||||
def test_get_network_iface_ip(self, mock_get_net_ifaces):
|
||||
fake_network = mock.MagicMock(
|
||||
InterfaceIndex=mock.sentinel.iface_index,
|
||||
DriverDescription=self.utils._HYPERV_VIRT_ADAPTER)
|
||||
mock_get_net_ifaces.return_value = [fake_network]
|
||||
|
||||
fake_netip = mock.MagicMock(IPAddress=mock.sentinel.provider_addr,
|
||||
PrefixLength=mock.sentinel.prefix_len)
|
||||
self.utils._scimv2.MSFT_NetIPAddress.return_value = [fake_netip]
|
||||
|
||||
pair = self.utils.get_network_iface_ip(mock.sentinel.fake_network)
|
||||
|
||||
self.assertEqual(
|
||||
(mock.sentinel.provider_addr, mock.sentinel.prefix_len), pair)
|
||||
|
||||
@mock.patch.object(nvgreutils.NvgreUtils, '_get_network_ifaces_by_name')
|
||||
def test_get_network_iface_ip_none(self, mock_get_net_ifaces):
|
||||
mock_get_net_ifaces.return_value = []
|
||||
pair = self.utils.get_network_iface_ip(mock.sentinel.fake_network)
|
||||
self.assertEqual((None, None), pair)
|
@ -1,341 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 ctypes
|
||||
|
||||
import mock
|
||||
from oslotest import base
|
||||
import six
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.utils.storage.initiator import fc_utils
|
||||
from os_win.utils.winapi.libs import hbaapi as fc_struct
|
||||
|
||||
|
||||
class FCUtilsTestCase(base.BaseTestCase):
|
||||
"""Unit tests for the Hyper-V FCUtils class."""
|
||||
|
||||
_FAKE_ADAPTER_NAME = 'fake_adapter_name'
|
||||
_FAKE_ADAPTER_WWN = list(range(8))
|
||||
|
||||
def setUp(self):
|
||||
super(FCUtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._fc_utils = fc_utils.FCUtils()
|
||||
self._run_mocker = mock.patch.object(self._fc_utils,
|
||||
'_run_and_check_output')
|
||||
self._run_mocker.start()
|
||||
|
||||
self._mock_run = self._fc_utils._run_and_check_output
|
||||
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
|
||||
mock.patch.object(fc_utils, 'hbaapi', create=True).start()
|
||||
self._ctypes_mocker = mock.patch.object(fc_utils, 'ctypes',
|
||||
self._ctypes)
|
||||
self._ctypes_mocker.start()
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._run_mocker.stop()
|
||||
with mock.patch.object(fc_utils.win32utils.Win32Utils,
|
||||
'run_and_check_output') as mock_win32_run:
|
||||
self._fc_utils._run_and_check_output(
|
||||
adapter_name=self._FAKE_ADAPTER_NAME)
|
||||
|
||||
mock_win32_run.assert_called_once_with(
|
||||
adapter_name=self._FAKE_ADAPTER_NAME,
|
||||
failure_exc=exceptions.FCWin32Exception)
|
||||
|
||||
def test_get_fc_hba_count(self):
|
||||
hba_count = self._fc_utils.get_fc_hba_count()
|
||||
|
||||
fc_utils.hbaapi.HBA_GetNumberOfAdapters.assert_called_once_with()
|
||||
self.assertEqual(fc_utils.hbaapi.HBA_GetNumberOfAdapters.return_value,
|
||||
hba_count)
|
||||
|
||||
def test_open_adapter_by_name(self):
|
||||
self._ctypes_mocker.stop()
|
||||
|
||||
self._mock_run.return_value = mock.sentinel.handle
|
||||
|
||||
resulted_handle = self._fc_utils._open_adapter_by_name(
|
||||
self._FAKE_ADAPTER_NAME)
|
||||
|
||||
args_list = self._mock_run.call_args_list[0][0]
|
||||
self.assertEqual(fc_utils.hbaapi.HBA_OpenAdapter, args_list[0])
|
||||
self.assertEqual(six.b(self._FAKE_ADAPTER_NAME), args_list[1].value)
|
||||
|
||||
self.assertEqual(mock.sentinel.handle, resulted_handle)
|
||||
|
||||
@mock.patch.object(fc_utils.fc_struct, 'HBA_HANDLE')
|
||||
def test_open_adapter_by_wwn(self, mock_hba_handle_struct):
|
||||
exp_handle = mock_hba_handle_struct.return_value
|
||||
resulted_handle = self._fc_utils._open_adapter_by_wwn(
|
||||
self._FAKE_ADAPTER_WWN)
|
||||
|
||||
self.assertEqual(exp_handle, resulted_handle)
|
||||
|
||||
args_list = self._mock_run.call_args_list[0][0]
|
||||
self.assertEqual(fc_utils.hbaapi.HBA_OpenAdapterByWWN,
|
||||
args_list[0])
|
||||
self.assertEqual(self._FAKE_ADAPTER_WWN, list(args_list[2].wwn))
|
||||
|
||||
self.assertEqual(self._ctypes.byref(exp_handle), args_list[1])
|
||||
|
||||
def test_close_adapter(self):
|
||||
self._fc_utils._close_adapter(mock.sentinel.hba_handle)
|
||||
fc_utils.hbaapi.HBA_CloseAdapter.assert_called_once_with(
|
||||
mock.sentinel.hba_handle)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, '_open_adapter_by_name')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_close_adapter')
|
||||
def test_get_hba_handle_by_name(self, mock_close_adapter,
|
||||
mock_open_adapter):
|
||||
with self._fc_utils._get_hba_handle(
|
||||
adapter_name=self._FAKE_ADAPTER_NAME) as handle:
|
||||
self.assertEqual(mock_open_adapter.return_value, handle)
|
||||
mock_open_adapter.assert_called_once_with(
|
||||
self._FAKE_ADAPTER_NAME)
|
||||
mock_close_adapter.assert_called_once_with(
|
||||
mock_open_adapter.return_value)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, '_open_adapter_by_wwn')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_close_adapter')
|
||||
def test_get_hba_handle_by_wwn(self, mock_close_adapter,
|
||||
mock_open_adapter):
|
||||
with self._fc_utils._get_hba_handle(
|
||||
adapter_wwn=self._FAKE_ADAPTER_WWN) as handle:
|
||||
self.assertEqual(mock_open_adapter.return_value, handle)
|
||||
mock_open_adapter.assert_called_once_with(
|
||||
self._FAKE_ADAPTER_WWN)
|
||||
mock_close_adapter.assert_called_once_with(
|
||||
mock_open_adapter.return_value)
|
||||
|
||||
def test_get_hba_handle_missing_params(self):
|
||||
self.assertRaises(exceptions.FCException,
|
||||
self._fc_utils._get_hba_handle().__enter__)
|
||||
|
||||
def test_get_adapter_name(self):
|
||||
self._ctypes_mocker.stop()
|
||||
fake_adapter_index = 1
|
||||
|
||||
def update_buff(func, adapter_index, buff):
|
||||
buff.value = six.b(self._FAKE_ADAPTER_NAME)
|
||||
|
||||
self._mock_run.side_effect = update_buff
|
||||
|
||||
resulted_adapter_name = self._fc_utils._get_adapter_name(
|
||||
fake_adapter_index)
|
||||
|
||||
args_list = self._mock_run.call_args_list[0][0]
|
||||
|
||||
self.assertEqual(fc_utils.hbaapi.HBA_GetAdapterName,
|
||||
args_list[0])
|
||||
self.assertIsInstance(args_list[1], ctypes.c_uint32)
|
||||
self.assertEqual(fake_adapter_index, args_list[1].value)
|
||||
|
||||
buff = ctypes.cast(args_list[2], ctypes.POINTER(
|
||||
ctypes.c_char * 256)).contents
|
||||
self.assertIsInstance(buff, ctypes.c_char * 256)
|
||||
self.assertEqual(self._FAKE_ADAPTER_NAME, resulted_adapter_name)
|
||||
|
||||
@mock.patch.object(fc_struct, 'get_target_mapping_struct')
|
||||
def test_get_target_mapping(self, mock_get_target_mapping):
|
||||
fake_entry_count = 10
|
||||
hresults = [fc_utils.HBA_STATUS_ERROR_MORE_DATA,
|
||||
fc_utils.HBA_STATUS_OK]
|
||||
mock_mapping = mock.Mock(NumberOfEntries=fake_entry_count)
|
||||
mock_get_target_mapping.return_value = mock_mapping
|
||||
self._mock_run.side_effect = hresults
|
||||
|
||||
resulted_mapping = self._fc_utils._get_target_mapping(
|
||||
mock.sentinel.hba_handle)
|
||||
|
||||
expected_calls = [
|
||||
mock.call(fc_utils.hbaapi.HBA_GetFcpTargetMapping,
|
||||
mock.sentinel.hba_handle,
|
||||
self._ctypes.byref(mock_mapping),
|
||||
ignored_error_codes=[fc_utils.HBA_STATUS_ERROR_MORE_DATA]
|
||||
)] * 2
|
||||
self._mock_run.assert_has_calls(expected_calls)
|
||||
self.assertEqual(mock_mapping, resulted_mapping)
|
||||
mock_get_target_mapping.assert_has_calls([mock.call(0),
|
||||
mock.call(fake_entry_count)])
|
||||
|
||||
@mock.patch.object(fc_struct, 'HBA_PortAttributes')
|
||||
def test_get_adapter_port_attributes(self, mock_class_HBA_PortAttributes):
|
||||
resulted_port_attributes = self._fc_utils._get_adapter_port_attributes(
|
||||
mock.sentinel.hba_handle, mock.sentinel.port_index)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
fc_utils.hbaapi.HBA_GetAdapterPortAttributes,
|
||||
mock.sentinel.hba_handle,
|
||||
mock.sentinel.port_index,
|
||||
self._ctypes.byref(mock_class_HBA_PortAttributes.return_value))
|
||||
|
||||
self.assertEqual(mock_class_HBA_PortAttributes.return_value,
|
||||
resulted_port_attributes)
|
||||
|
||||
@mock.patch.object(fc_struct, 'HBA_AdapterAttributes')
|
||||
def test_get_adapter_attributes(self, mock_class_HBA_AdapterAttributes):
|
||||
resulted_hba_attributes = self._fc_utils._get_adapter_attributes(
|
||||
mock.sentinel.hba_handle)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
fc_utils.hbaapi.HBA_GetAdapterAttributes,
|
||||
mock.sentinel.hba_handle,
|
||||
self._ctypes.byref(mock_class_HBA_AdapterAttributes.return_value))
|
||||
|
||||
self.assertEqual(mock_class_HBA_AdapterAttributes.return_value,
|
||||
resulted_hba_attributes)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, 'get_fc_hba_count')
|
||||
def test_get_fc_hba_ports_missing_hbas(self, mock_get_fc_hba_count):
|
||||
mock_get_fc_hba_count.return_value = 0
|
||||
|
||||
resulted_hba_ports = self._fc_utils.get_fc_hba_ports()
|
||||
|
||||
self.assertEqual([], resulted_hba_ports)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, '_get_fc_hba_adapter_ports')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_get_adapter_name')
|
||||
@mock.patch.object(fc_utils.FCUtils, 'get_fc_hba_count')
|
||||
def test_get_fc_hba_ports(self, mock_get_fc_hba_count,
|
||||
mock_get_adapter_name,
|
||||
mock_get_adapter_ports):
|
||||
fake_adapter_count = 3
|
||||
|
||||
mock_get_adapter_name.side_effect = [Exception,
|
||||
mock.sentinel.adapter_name,
|
||||
mock.sentinel.adapter_name]
|
||||
mock_get_fc_hba_count.return_value = fake_adapter_count
|
||||
mock_get_adapter_ports.side_effect = [Exception,
|
||||
[mock.sentinel.port]]
|
||||
|
||||
expected_hba_ports = [mock.sentinel.port]
|
||||
resulted_hba_ports = self._fc_utils.get_fc_hba_ports()
|
||||
self.assertEqual(expected_hba_ports, resulted_hba_ports)
|
||||
self.assertEqual(expected_hba_ports, resulted_hba_ports)
|
||||
|
||||
mock_get_adapter_name.assert_has_calls(
|
||||
[mock.call(index) for index in range(fake_adapter_count)])
|
||||
mock_get_adapter_ports.assert_has_calls(
|
||||
[mock.call(mock.sentinel.adapter_name)] * 2)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, '_open_adapter_by_name')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_close_adapter')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_get_adapter_port_attributes')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_get_adapter_attributes')
|
||||
def test_get_fc_hba_adapter_ports(self, mock_get_adapter_attributes,
|
||||
mock_get_adapter_port_attributes,
|
||||
mock_close_adapter,
|
||||
mock_open_adapter):
|
||||
fake_port_count = 1
|
||||
fake_port_index = 0
|
||||
# Local WWNs
|
||||
fake_node_wwn = list(range(3))
|
||||
fake_port_wwn = list(range(3))
|
||||
|
||||
mock_adapter_attributes = mock.MagicMock()
|
||||
mock_adapter_attributes.NumberOfPorts = fake_port_count
|
||||
mock_port_attributes = mock.MagicMock()
|
||||
mock_port_attributes.NodeWWN.wwn = fake_node_wwn
|
||||
mock_port_attributes.PortWWN.wwn = fake_port_wwn
|
||||
|
||||
mock_get_adapter_attributes.return_value = mock_adapter_attributes
|
||||
mock_get_adapter_port_attributes.return_value = mock_port_attributes
|
||||
|
||||
resulted_hba_ports = self._fc_utils._get_fc_hba_adapter_ports(
|
||||
mock.sentinel.adapter_name)
|
||||
|
||||
expected_hba_ports = [{
|
||||
'node_name': self._fc_utils._wwn_array_to_hex_str(fake_node_wwn),
|
||||
'port_name': self._fc_utils._wwn_array_to_hex_str(fake_port_wwn)
|
||||
}]
|
||||
self.assertEqual(expected_hba_ports, resulted_hba_ports)
|
||||
|
||||
mock_open_adapter.assert_called_once_with(mock.sentinel.adapter_name)
|
||||
mock_close_adapter.assert_called_once_with(
|
||||
mock_open_adapter(mock.sentinel.adapter_nam))
|
||||
mock_get_adapter_attributes.assert_called_once_with(
|
||||
mock_open_adapter.return_value)
|
||||
mock_get_adapter_port_attributes.assert_called_once_with(
|
||||
mock_open_adapter.return_value, fake_port_index)
|
||||
|
||||
def test_wwn_hex_string_to_array(self):
|
||||
fake_wwn_hex_string = '000102'
|
||||
|
||||
resulted_array = self._fc_utils._wwn_hex_string_to_array(
|
||||
fake_wwn_hex_string)
|
||||
|
||||
expected_wwn_hex_array = list(range(3))
|
||||
self.assertEqual(expected_wwn_hex_array, resulted_array)
|
||||
|
||||
def test_wwn_array_to_hex_str(self):
|
||||
fake_wwn_array = list(range(3))
|
||||
|
||||
resulted_string = self._fc_utils._wwn_array_to_hex_str(fake_wwn_array)
|
||||
|
||||
expected_string = '000102'
|
||||
self.assertEqual(expected_string, resulted_string)
|
||||
|
||||
@mock.patch.object(fc_utils.FCUtils, '_open_adapter_by_wwn')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_close_adapter')
|
||||
@mock.patch.object(fc_utils.FCUtils, '_get_target_mapping')
|
||||
def test_get_fc_target_mapping(self, mock_get_target_mapping,
|
||||
mock_close_adapter, mock_open_adapter):
|
||||
# Local WWNN
|
||||
fake_node_wwn_string = "123"
|
||||
# Remote WWNs
|
||||
fake_node_wwn = list(range(3))
|
||||
fake_port_wwn = list(range(3))
|
||||
|
||||
mock_fcp_mappings = mock.MagicMock()
|
||||
mock_entry = mock.MagicMock()
|
||||
mock_entry.FcpId.NodeWWN.wwn = fake_node_wwn
|
||||
mock_entry.FcpId.PortWWN.wwn = fake_port_wwn
|
||||
mock_entry.ScsiId.OSDeviceName = mock.sentinel.OSDeviceName
|
||||
mock_entry.ScsiId.ScsiOSLun = mock.sentinel.ScsiOSLun
|
||||
mock_fcp_mappings.Entries = [mock_entry]
|
||||
mock_get_target_mapping.return_value = mock_fcp_mappings
|
||||
mock_node_wwn = self._fc_utils._wwn_hex_string_to_array(
|
||||
fake_node_wwn_string)
|
||||
|
||||
resulted_mappings = self._fc_utils.get_fc_target_mappings(
|
||||
fake_node_wwn_string)
|
||||
|
||||
expected_mappings = [{
|
||||
'node_name': self._fc_utils._wwn_array_to_hex_str(fake_node_wwn),
|
||||
'port_name': self._fc_utils._wwn_array_to_hex_str(fake_port_wwn),
|
||||
'device_name': mock.sentinel.OSDeviceName,
|
||||
'lun': mock.sentinel.ScsiOSLun
|
||||
}]
|
||||
self.assertEqual(expected_mappings, resulted_mappings)
|
||||
mock_open_adapter.assert_called_once_with(mock_node_wwn)
|
||||
mock_close_adapter.assert_called_once_with(
|
||||
mock_open_adapter.return_value)
|
||||
|
||||
def test_refresh_hba_configuration(self):
|
||||
self._fc_utils.refresh_hba_configuration()
|
||||
|
||||
expected_func = fc_utils.hbaapi.HBA_RefreshAdapterConfiguration
|
||||
expected_func.assert_called_once_with()
|
@ -1,838 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 collections
|
||||
import ctypes
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
import six
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage.initiator import iscsi_utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi.errmsg import iscsierr
|
||||
from os_win.utils.winapi.libs import iscsidsc as iscsi_struct
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ISCSIInitiatorUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V ISCSIInitiatorUtils class."""
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, '__init__',
|
||||
lambda *args, **kwargs: None)
|
||||
def setUp(self):
|
||||
super(ISCSIInitiatorUtilsTestCase, self).setUp()
|
||||
|
||||
self._initiator = iscsi_utils.ISCSIInitiatorUtils()
|
||||
self._initiator._win32utils = mock.Mock()
|
||||
self._initiator._diskutils = mock.Mock()
|
||||
|
||||
self._iscsidsc = mock.patch.object(
|
||||
iscsi_utils, 'iscsidsc', create=True).start()
|
||||
|
||||
self._run_mocker = mock.patch.object(self._initiator,
|
||||
'_run_and_check_output')
|
||||
self._mock_run = self._run_mocker.start()
|
||||
|
||||
iscsi_utils.portal_map = collections.defaultdict(set)
|
||||
|
||||
def _mock_ctypes(self):
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
|
||||
mock.patch.object(iscsi_utils, 'ctypes', self._ctypes).start()
|
||||
|
||||
def _get_fake_iscsi_utils_getter_func(self, func_side_effect,
|
||||
decorator_args,
|
||||
returned_element_count=None,
|
||||
required_buff_sz=None):
|
||||
@iscsi_utils.ensure_buff_and_retrieve_items(**decorator_args)
|
||||
def fake_func(inst, buff=None, buff_size=None,
|
||||
element_count=None, *args, **kwargs):
|
||||
raised_exc = None
|
||||
try:
|
||||
# Those arguments will always be ULONGs, as requested
|
||||
# by the iscsidsc functions.
|
||||
self.assertIsInstance(buff_size, ctypes.c_ulong)
|
||||
self.assertIsInstance(element_count, ctypes.c_ulong)
|
||||
func_side_effect(buff=buff, buff_size_val=buff_size.value,
|
||||
element_count_val=element_count.value,
|
||||
*args, **kwargs)
|
||||
except Exception as ex:
|
||||
raised_exc = ex
|
||||
|
||||
if returned_element_count:
|
||||
element_count.value = returned_element_count
|
||||
if required_buff_sz:
|
||||
buff_size.value = required_buff_sz
|
||||
|
||||
if raised_exc:
|
||||
raise raised_exc
|
||||
return mock.sentinel.ret_val
|
||||
return fake_func
|
||||
|
||||
@mock.patch.object(iscsi_utils, '_get_items_from_buff')
|
||||
def _test_ensure_buff_decorator(self, mock_get_items,
|
||||
required_buff_sz=None,
|
||||
returned_element_count=None,
|
||||
parse_output=False):
|
||||
insufficient_buff_exc = exceptions.Win32Exception(
|
||||
message='fake_err_msg',
|
||||
error_code=w_const.ERROR_INSUFFICIENT_BUFFER)
|
||||
func_requests_buff_sz = required_buff_sz is not None
|
||||
struct_type = ctypes.c_uint
|
||||
|
||||
decorator_args = dict(struct_type=struct_type,
|
||||
parse_output=parse_output,
|
||||
func_requests_buff_sz=func_requests_buff_sz)
|
||||
|
||||
func_side_effect = mock.Mock(side_effect=(insufficient_buff_exc, None))
|
||||
fake_func = self._get_fake_iscsi_utils_getter_func(
|
||||
returned_element_count=returned_element_count,
|
||||
required_buff_sz=required_buff_sz,
|
||||
func_side_effect=func_side_effect,
|
||||
decorator_args=decorator_args)
|
||||
|
||||
ret_val = fake_func(self._initiator, fake_arg=mock.sentinel.arg)
|
||||
if parse_output:
|
||||
self.assertEqual(mock_get_items.return_value, ret_val)
|
||||
else:
|
||||
self.assertEqual(mock.sentinel.ret_val, ret_val)
|
||||
|
||||
# We expect our decorated method to be called exactly two times.
|
||||
first_call_args_dict = func_side_effect.call_args_list[0][1]
|
||||
self.assertIsInstance(first_call_args_dict['buff'],
|
||||
ctypes.POINTER(struct_type))
|
||||
self.assertEqual(first_call_args_dict['buff_size_val'], 0)
|
||||
self.assertEqual(first_call_args_dict['element_count_val'], 0)
|
||||
|
||||
second_call_args_dict = func_side_effect.call_args_list[1][1]
|
||||
self.assertIsInstance(second_call_args_dict['buff'],
|
||||
ctypes.POINTER(struct_type))
|
||||
self.assertEqual(second_call_args_dict['buff_size_val'],
|
||||
required_buff_sz or 0)
|
||||
self.assertEqual(second_call_args_dict['element_count_val'],
|
||||
returned_element_count or 0)
|
||||
|
||||
def test_ensure_buff_func_requests_buff_sz(self):
|
||||
self._test_ensure_buff_decorator(required_buff_sz=10,
|
||||
parse_output=True)
|
||||
|
||||
def test_ensure_buff_func_requests_el_count(self):
|
||||
self._test_ensure_buff_decorator(returned_element_count=5)
|
||||
|
||||
def test_ensure_buff_func_unexpected_exception(self):
|
||||
fake_exc = exceptions.Win32Exception(message='fake_message',
|
||||
error_code=1)
|
||||
|
||||
func_side_effect = mock.Mock(side_effect=fake_exc)
|
||||
fake_func = self._get_fake_iscsi_utils_getter_func(
|
||||
func_side_effect=func_side_effect,
|
||||
decorator_args={'struct_type': ctypes.c_ubyte})
|
||||
|
||||
self.assertRaises(exceptions.Win32Exception, fake_func,
|
||||
self._initiator)
|
||||
|
||||
def test_get_items_from_buff(self):
|
||||
fake_buff_contents = 'fake_buff_contents'
|
||||
fake_buff = (ctypes.c_wchar * len(fake_buff_contents))()
|
||||
fake_buff.value = fake_buff_contents
|
||||
|
||||
fake_buff = ctypes.cast(fake_buff, ctypes.POINTER(ctypes.c_ubyte))
|
||||
|
||||
result = iscsi_utils._get_items_from_buff(fake_buff, ctypes.c_wchar,
|
||||
len(fake_buff_contents))
|
||||
|
||||
self.assertEqual(fake_buff_contents, result.value)
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._run_mocker.stop()
|
||||
self._initiator._win32utils = mock.Mock()
|
||||
mock_win32utils_run_and_check_output = (
|
||||
self._initiator._win32utils.run_and_check_output)
|
||||
|
||||
self._initiator._run_and_check_output(mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
fake_kwarg=mock.sentinel.kwarg)
|
||||
|
||||
mock_win32utils_run_and_check_output.assert_called_once_with(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
fake_kwarg=mock.sentinel.kwarg,
|
||||
error_msg_src=iscsierr.err_msg_dict,
|
||||
failure_exc=exceptions.ISCSIInitiatorAPIException)
|
||||
|
||||
def test_get_iscsi_persistent_logins(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
_get_iscsi_persistent_logins = _utils.get_wrapped_function(
|
||||
self._initiator._get_iscsi_persistent_logins)
|
||||
_get_iscsi_persistent_logins(
|
||||
self._initiator,
|
||||
buff=mock.sentinel.buff,
|
||||
buff_size=mock.sentinel.buff_size,
|
||||
element_count=mock.sentinel.element_count)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.ReportIScsiPersistentLoginsW,
|
||||
self._ctypes.byref(mock.sentinel.element_count),
|
||||
mock.sentinel.buff,
|
||||
self._ctypes.byref(mock.sentinel.buff_size))
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_parse_string_list')
|
||||
def test_get_targets(self, mock_parse_string_list):
|
||||
self._mock_ctypes()
|
||||
|
||||
get_targets = _utils.get_wrapped_function(
|
||||
self._initiator.get_targets)
|
||||
mock_el_count = mock.Mock(value=mock.sentinel.element_count)
|
||||
|
||||
resulted_target_list = get_targets(
|
||||
self._initiator,
|
||||
forced_update=mock.sentinel.forced_update,
|
||||
element_count=mock_el_count,
|
||||
buff=mock.sentinel.buff)
|
||||
self.assertEqual(mock_parse_string_list.return_value,
|
||||
resulted_target_list)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.ReportIScsiTargetsW,
|
||||
mock.sentinel.forced_update,
|
||||
self._ctypes.byref(mock_el_count),
|
||||
mock.sentinel.buff)
|
||||
mock_parse_string_list.assert_called_once_with(
|
||||
mock.sentinel.buff, mock.sentinel.element_count)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_parse_string_list')
|
||||
def test_get_initiators(self, mock_parse_string_list):
|
||||
self._mock_ctypes()
|
||||
|
||||
get_initiators = _utils.get_wrapped_function(
|
||||
self._initiator.get_iscsi_initiators)
|
||||
mock_el_count = mock.Mock(value=mock.sentinel.element_count)
|
||||
|
||||
resulted_initator_list = get_initiators(
|
||||
self._initiator,
|
||||
element_count=mock_el_count,
|
||||
buff=mock.sentinel.buff)
|
||||
self.assertEqual(mock_parse_string_list.return_value,
|
||||
resulted_initator_list)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.ReportIScsiInitiatorListW,
|
||||
self._ctypes.byref(mock_el_count),
|
||||
mock.sentinel.buff)
|
||||
mock_parse_string_list.assert_called_once_with(
|
||||
mock.sentinel.buff, mock.sentinel.element_count)
|
||||
|
||||
def test_parse_string_list(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
fake_buff = 'fake\x00buff\x00\x00'
|
||||
self._ctypes.cast.return_value = fake_buff
|
||||
|
||||
str_list = self._initiator._parse_string_list(fake_buff,
|
||||
len(fake_buff))
|
||||
|
||||
self.assertEqual(['fake', 'buff'], str_list)
|
||||
|
||||
self._ctypes.cast.assert_called_once_with(
|
||||
fake_buff, self._ctypes.POINTER.return_value)
|
||||
self._ctypes.POINTER.assert_called_once_with(self._ctypes.c_wchar)
|
||||
|
||||
def test_get_iscsi_initiator(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
self._ctypes.c_wchar = mock.MagicMock()
|
||||
fake_buff = (self._ctypes.c_wchar * (
|
||||
w_const.MAX_ISCSI_NAME_LEN + 1))()
|
||||
fake_buff.value = mock.sentinel.buff_value
|
||||
|
||||
resulted_iscsi_initiator = self._initiator.get_iscsi_initiator()
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.GetIScsiInitiatorNodeNameW,
|
||||
fake_buff)
|
||||
self.assertEqual(mock.sentinel.buff_value,
|
||||
resulted_iscsi_initiator)
|
||||
|
||||
@mock.patch('socket.getfqdn')
|
||||
def test_get_iscsi_initiator_exception(self, mock_get_fqdn):
|
||||
fake_fqdn = 'fakehost.FAKE-DOMAIN.com'
|
||||
fake_exc = exceptions.ISCSIInitiatorAPIException(
|
||||
message='fake_message',
|
||||
error_code=1,
|
||||
func_name='fake_func')
|
||||
|
||||
self._mock_run.side_effect = fake_exc
|
||||
mock_get_fqdn.return_value = fake_fqdn
|
||||
|
||||
resulted_iqn = self._initiator.get_iscsi_initiator()
|
||||
|
||||
expected_iqn = "%s:%s" % (self._initiator._MS_IQN_PREFIX,
|
||||
fake_fqdn.lower())
|
||||
self.assertEqual(expected_iqn, resulted_iqn)
|
||||
|
||||
@mock.patch.object(ctypes, 'byref')
|
||||
@mock.patch.object(iscsi_struct, 'ISCSI_UNIQUE_CONNECTION_ID')
|
||||
@mock.patch.object(iscsi_struct, 'ISCSI_UNIQUE_SESSION_ID')
|
||||
def test_login_iscsi_target(self, mock_cls_ISCSI_UNIQUE_SESSION_ID,
|
||||
mock_cls_ISCSI_UNIQUE_CONNECTION_ID,
|
||||
mock_byref):
|
||||
fake_target_name = 'fake_target_name'
|
||||
|
||||
resulted_session_id, resulted_conection_id = (
|
||||
self._initiator._login_iscsi_target(fake_target_name))
|
||||
|
||||
args_list = self._mock_run.call_args_list[0][0]
|
||||
|
||||
self.assertIsInstance(args_list[1], ctypes.c_wchar_p)
|
||||
self.assertEqual(fake_target_name, args_list[1].value)
|
||||
self.assertIsInstance(args_list[4], ctypes.c_ulong)
|
||||
self.assertEqual(
|
||||
ctypes.c_ulong(w_const.ISCSI_ANY_INITIATOR_PORT).value,
|
||||
args_list[4].value)
|
||||
self.assertIsInstance(args_list[6], ctypes.c_ulonglong)
|
||||
self.assertEqual(0, args_list[6].value)
|
||||
self.assertIsInstance(args_list[9], ctypes.c_ulong)
|
||||
self.assertEqual(0, args_list[9].value)
|
||||
|
||||
mock_byref.assert_has_calls([
|
||||
mock.call(mock_cls_ISCSI_UNIQUE_SESSION_ID.return_value),
|
||||
mock.call(mock_cls_ISCSI_UNIQUE_CONNECTION_ID.return_value)])
|
||||
self.assertEqual(
|
||||
mock_cls_ISCSI_UNIQUE_SESSION_ID.return_value,
|
||||
resulted_session_id)
|
||||
self.assertEqual(
|
||||
mock_cls_ISCSI_UNIQUE_CONNECTION_ID.return_value,
|
||||
resulted_conection_id)
|
||||
|
||||
def test_get_iscsi_sessions(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
_get_iscsi_sessions = _utils.get_wrapped_function(
|
||||
self._initiator._get_iscsi_sessions)
|
||||
_get_iscsi_sessions(
|
||||
self._initiator,
|
||||
buff=mock.sentinel.buff,
|
||||
buff_size=mock.sentinel.buff_size,
|
||||
element_count=mock.sentinel.element_count)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.GetIScsiSessionListW,
|
||||
self._ctypes.byref(mock.sentinel.buff_size),
|
||||
self._ctypes.byref(mock.sentinel.element_count),
|
||||
mock.sentinel.buff)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_sessions')
|
||||
def test_get_iscsi_target_sessions(self, mock_get_iscsi_sessions,
|
||||
target_sessions_found=True):
|
||||
fake_session = mock.Mock(TargetNodeName="FAKE_TARGET_NAME",
|
||||
ConnectionCount=1)
|
||||
fake_disconn_session = mock.Mock(
|
||||
TargetNodeName="fake_target_name",
|
||||
ConnectionCount=0)
|
||||
other_session = mock.Mock(TargetNodeName="other_target_name",
|
||||
ConnectionCount=1)
|
||||
|
||||
sessions = [fake_session, fake_disconn_session, other_session]
|
||||
mock_get_iscsi_sessions.return_value = sessions
|
||||
|
||||
resulted_tgt_sessions = self._initiator._get_iscsi_target_sessions(
|
||||
"fake_target_name")
|
||||
|
||||
self.assertEqual([fake_session], resulted_tgt_sessions)
|
||||
|
||||
def test_get_iscsi_session_devices(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
_get_iscsi_session_devices = _utils.get_wrapped_function(
|
||||
self._initiator._get_iscsi_session_devices)
|
||||
_get_iscsi_session_devices(
|
||||
self._initiator,
|
||||
mock.sentinel.session_id,
|
||||
buff=mock.sentinel.buff,
|
||||
element_count=mock.sentinel.element_count)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.GetDevicesForIScsiSessionW,
|
||||
self._ctypes.byref(mock.sentinel.session_id),
|
||||
self._ctypes.byref(mock.sentinel.element_count),
|
||||
mock.sentinel.buff)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_session_devices')
|
||||
def test_get_iscsi_session_luns(self, mock_get_iscsi_session_devices):
|
||||
fake_device = mock.Mock()
|
||||
fake_device.StorageDeviceNumber.DeviceType = w_const.FILE_DEVICE_DISK
|
||||
mock_get_iscsi_session_devices.return_value = [fake_device,
|
||||
mock.Mock()]
|
||||
|
||||
resulted_luns = self._initiator._get_iscsi_session_disk_luns(
|
||||
mock.sentinel.session_id)
|
||||
expected_luns = [fake_device.ScsiAddress.Lun]
|
||||
|
||||
mock_get_iscsi_session_devices.assert_called_once_with(
|
||||
mock.sentinel.session_id)
|
||||
self.assertEqual(expected_luns, resulted_luns)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_session_devices')
|
||||
def test_get_iscsi_device_from_session(self,
|
||||
mock_get_iscsi_session_devices):
|
||||
fake_device = mock.Mock()
|
||||
fake_device.ScsiAddress.Lun = mock.sentinel.target_lun
|
||||
mock_get_iscsi_session_devices.return_value = [mock.Mock(),
|
||||
fake_device]
|
||||
|
||||
resulted_device = self._initiator._get_iscsi_device_from_session(
|
||||
mock.sentinel.session_id,
|
||||
mock.sentinel.target_lun)
|
||||
|
||||
mock_get_iscsi_session_devices.assert_called_once_with(
|
||||
mock.sentinel.session_id)
|
||||
self.assertEqual(fake_device, resulted_device)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'get_device_number_and_path')
|
||||
def test_get_device_number_for_target(self, mock_get_dev_num_and_path):
|
||||
dev_num = self._initiator.get_device_number_for_target(
|
||||
mock.sentinel.target_name, mock.sentinel.lun,
|
||||
mock.sentinel.fail_if_not_found)
|
||||
|
||||
mock_get_dev_num_and_path.assert_called_once_with(
|
||||
mock.sentinel.target_name, mock.sentinel.lun,
|
||||
mock.sentinel.fail_if_not_found)
|
||||
self.assertEqual(mock_get_dev_num_and_path.return_value[0], dev_num)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'ensure_lun_available')
|
||||
def test_get_device_number_and_path(self, mock_ensure_lun_available):
|
||||
mock_ensure_lun_available.return_value = (mock.sentinel.dev_num,
|
||||
mock.sentinel.dev_path)
|
||||
|
||||
dev_num, dev_path = self._initiator.get_device_number_and_path(
|
||||
mock.sentinel.target_name, mock.sentinel.lun)
|
||||
|
||||
mock_ensure_lun_available.assert_called_once_with(
|
||||
mock.sentinel.target_name, mock.sentinel.lun,
|
||||
rescan_attempts=10, retry_interval=0.1, rescan_disks=False)
|
||||
|
||||
self.assertEqual(mock.sentinel.dev_num, dev_num)
|
||||
self.assertEqual(mock.sentinel.dev_path, dev_path)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'ensure_lun_available')
|
||||
def test_get_device_number_and_path_exc(self, fail_if_not_found,
|
||||
mock_ensure_lun_available):
|
||||
raised_exc = exceptions.ISCSILunNotAvailable
|
||||
mock_ensure_lun_available.side_effect = raised_exc(
|
||||
target_iqn=mock.sentinel.target_iqn,
|
||||
target_lun=mock.sentinel.target_lun)
|
||||
|
||||
if fail_if_not_found:
|
||||
self.assertRaises(raised_exc,
|
||||
self._initiator.get_device_number_and_path,
|
||||
mock.sentinel.target_name,
|
||||
mock.sentinel.lun,
|
||||
fail_if_not_found)
|
||||
else:
|
||||
dev_num, dev_path = self._initiator.get_device_number_and_path(
|
||||
mock.sentinel.target_name,
|
||||
mock.sentinel.lun,
|
||||
fail_if_not_found)
|
||||
self.assertIsNone(dev_num)
|
||||
self.assertIsNone(dev_path)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_target_sessions')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_session_disk_luns')
|
||||
def test_get_target_luns(self, mock_get_iscsi_session_disk_luns,
|
||||
mock_get_iscsi_target_sessions):
|
||||
fake_session = mock.Mock()
|
||||
mock_get_iscsi_target_sessions.return_value = [fake_session]
|
||||
|
||||
retrieved_luns = [mock.sentinel.lun_0]
|
||||
mock_get_iscsi_session_disk_luns.return_value = retrieved_luns
|
||||
|
||||
resulted_luns = self._initiator.get_target_luns(
|
||||
mock.sentinel.target_name)
|
||||
|
||||
mock_get_iscsi_target_sessions.assert_called_once_with(
|
||||
mock.sentinel.target_name)
|
||||
mock_get_iscsi_session_disk_luns.assert_called_once_with(
|
||||
fake_session.SessionId)
|
||||
self.assertEqual(retrieved_luns, resulted_luns)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'get_target_luns')
|
||||
def test_get_target_lun_count(self, mock_get_target_luns):
|
||||
target_luns = [mock.sentinel.lun0, mock.sentinel.lun1]
|
||||
mock_get_target_luns.return_value = target_luns
|
||||
|
||||
lun_count = self._initiator.get_target_lun_count(
|
||||
mock.sentinel.target_name)
|
||||
|
||||
self.assertEqual(len(target_luns), lun_count)
|
||||
mock_get_target_luns.assert_called_once_with(
|
||||
mock.sentinel.target_name)
|
||||
|
||||
def test_logout_iscsi_target(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
self._initiator._logout_iscsi_target(mock.sentinel.session_id)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.LogoutIScsiTarget,
|
||||
self._ctypes.byref(mock.sentinel.session_id))
|
||||
|
||||
def test_add_static_target(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
is_persistent = True
|
||||
self._initiator._add_static_target(mock.sentinel.target_name,
|
||||
is_persistent=is_persistent)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.AddIScsiStaticTargetW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.target_name),
|
||||
None, 0, is_persistent, None, None, None)
|
||||
|
||||
def test_remove_static_target(self):
|
||||
self._mock_ctypes()
|
||||
|
||||
self._initiator._remove_static_target(mock.sentinel.target_name)
|
||||
|
||||
expected_ignored_err_codes = [w_const.ISDSC_TARGET_NOT_FOUND]
|
||||
self._mock_run.assert_called_once_with(
|
||||
self._iscsidsc.RemoveIScsiStaticTargetW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.target_name),
|
||||
ignored_error_codes=expected_ignored_err_codes)
|
||||
|
||||
def test_get_login_opts(self):
|
||||
fake_username = 'fake_chap_username'
|
||||
fake_password = 'fake_chap_secret'
|
||||
auth_type = constants.ISCSI_CHAP_AUTH_TYPE
|
||||
login_flags = w_const.ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED
|
||||
|
||||
login_opts = self._initiator._get_login_opts(
|
||||
auth_username=fake_username,
|
||||
auth_password=fake_password,
|
||||
auth_type=auth_type,
|
||||
login_flags=login_flags)
|
||||
|
||||
self.assertEqual(len(fake_username), login_opts.UsernameLength)
|
||||
self.assertEqual(len(fake_password), login_opts.PasswordLength)
|
||||
|
||||
username_struct_contents = ctypes.cast(
|
||||
login_opts.Username,
|
||||
ctypes.POINTER(ctypes.c_char * len(fake_username))).contents.value
|
||||
pwd_struct_contents = ctypes.cast(
|
||||
login_opts.Password,
|
||||
ctypes.POINTER(ctypes.c_char * len(fake_password))).contents.value
|
||||
|
||||
self.assertEqual(six.b(fake_username), username_struct_contents)
|
||||
self.assertEqual(six.b(fake_password), pwd_struct_contents)
|
||||
|
||||
expected_info_bitmap = (w_const.ISCSI_LOGIN_OPTIONS_USERNAME |
|
||||
w_const.ISCSI_LOGIN_OPTIONS_PASSWORD |
|
||||
w_const.ISCSI_LOGIN_OPTIONS_AUTH_TYPE)
|
||||
self.assertEqual(expected_info_bitmap,
|
||||
login_opts.InformationSpecified)
|
||||
self.assertEqual(login_flags,
|
||||
login_opts.LoginFlags)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_session_devices')
|
||||
def test_session_on_path_exists(self, mock_get_iscsi_session_devices):
|
||||
mock_device = mock.Mock(InitiatorName=mock.sentinel.initiator_name)
|
||||
mock_get_iscsi_session_devices.return_value = [mock_device]
|
||||
|
||||
fake_connection = mock.Mock(TargetAddress=mock.sentinel.portal_addr,
|
||||
TargetSocket=mock.sentinel.portal_port)
|
||||
fake_connections = [mock.Mock(), fake_connection]
|
||||
fake_session = mock.Mock(ConnectionCount=len(fake_connections),
|
||||
Connections=fake_connections)
|
||||
fake_sessions = [mock.Mock(Connections=[], ConnectionCount=0),
|
||||
fake_session]
|
||||
|
||||
session_on_path_exists = self._initiator._session_on_path_exists(
|
||||
fake_sessions, mock.sentinel.portal_addr,
|
||||
mock.sentinel.portal_port,
|
||||
mock.sentinel.initiator_name)
|
||||
self.assertTrue(session_on_path_exists)
|
||||
mock_get_iscsi_session_devices.assert_has_calls(
|
||||
[mock.call(session.SessionId) for session in fake_sessions])
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_target_sessions')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_session_on_path_exists')
|
||||
def _test_new_session_required(self, mock_session_on_path_exists,
|
||||
mock_get_iscsi_target_sessions,
|
||||
sessions=None,
|
||||
mpio_enabled=False,
|
||||
session_on_path_exists=False):
|
||||
mock_get_iscsi_target_sessions.return_value = sessions
|
||||
mock_session_on_path_exists.return_value = session_on_path_exists
|
||||
|
||||
expected_result = (not sessions or
|
||||
(mpio_enabled and not session_on_path_exists))
|
||||
result = self._initiator._new_session_required(
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.portal_addr,
|
||||
mock.sentinel.portal_port,
|
||||
mock.sentinel.initiator_name,
|
||||
mpio_enabled)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
if sessions and mpio_enabled:
|
||||
mock_session_on_path_exists.assert_called_once_with(
|
||||
sessions,
|
||||
mock.sentinel.portal_addr,
|
||||
mock.sentinel.portal_port,
|
||||
mock.sentinel.initiator_name)
|
||||
|
||||
def test_new_session_required_no_sessions(self):
|
||||
self._test_new_session_required()
|
||||
|
||||
def test_new_session_required_existing_sessions_no_mpio(self):
|
||||
self._test_new_session_required(sessions=mock.sentinel.sessions)
|
||||
|
||||
def test_new_session_required_existing_sessions_mpio_enabled(self):
|
||||
self._test_new_session_required(sessions=mock.sentinel.sessions,
|
||||
mpio_enabled=True)
|
||||
|
||||
def test_new_session_required_session_on_path_exists(self):
|
||||
self._test_new_session_required(sessions=mock.sentinel.sessions,
|
||||
mpio_enabled=True,
|
||||
session_on_path_exists=True)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_login_opts')
|
||||
@mock.patch.object(iscsi_struct, 'ISCSI_TARGET_PORTAL')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_new_session_required')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, 'get_targets')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils, '_login_iscsi_target')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'ensure_lun_available')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_add_static_target')
|
||||
def _test_login_storage_target(self, mock_add_static_target,
|
||||
mock_ensure_lun_available,
|
||||
mock_login_iscsi_target,
|
||||
mock_get_targets,
|
||||
mock_session_required,
|
||||
mock_cls_ISCSI_TARGET_PORTAL,
|
||||
mock_get_login_opts,
|
||||
mpio_enabled=False,
|
||||
login_required=True):
|
||||
fake_portal_addr = '127.0.0.1'
|
||||
fake_portal_port = 3260
|
||||
fake_target_portal = '%s:%s' % (fake_portal_addr, fake_portal_port)
|
||||
|
||||
fake_portal = mock_cls_ISCSI_TARGET_PORTAL.return_value
|
||||
fake_login_opts = mock_get_login_opts.return_value
|
||||
|
||||
mock_get_targets.return_value = []
|
||||
mock_login_iscsi_target.return_value = (mock.sentinel.session_id,
|
||||
mock.sentinel.conn_id)
|
||||
mock_session_required.return_value = login_required
|
||||
|
||||
self._initiator.login_storage_target(
|
||||
mock.sentinel.target_lun,
|
||||
mock.sentinel.target_iqn,
|
||||
fake_target_portal,
|
||||
auth_username=mock.sentinel.auth_username,
|
||||
auth_password=mock.sentinel.auth_password,
|
||||
auth_type=mock.sentinel.auth_type,
|
||||
mpio_enabled=mpio_enabled,
|
||||
rescan_attempts=mock.sentinel.rescan_attempts)
|
||||
|
||||
mock_get_targets.assert_called_once_with()
|
||||
mock_add_static_target.assert_called_once_with(
|
||||
mock.sentinel.target_iqn)
|
||||
|
||||
if login_required:
|
||||
expected_login_flags = (
|
||||
w_const.ISCSI_LOGIN_FLAG_MULTIPATH_ENABLED
|
||||
if mpio_enabled else 0)
|
||||
mock_get_login_opts.assert_called_once_with(
|
||||
mock.sentinel.auth_username,
|
||||
mock.sentinel.auth_password,
|
||||
mock.sentinel.auth_type,
|
||||
expected_login_flags)
|
||||
mock_cls_ISCSI_TARGET_PORTAL.assert_called_once_with(
|
||||
Address=fake_portal_addr,
|
||||
Socket=fake_portal_port)
|
||||
mock_login_iscsi_target.assert_has_calls([
|
||||
mock.call(mock.sentinel.target_iqn,
|
||||
fake_portal,
|
||||
fake_login_opts,
|
||||
is_persistent=True),
|
||||
mock.call(mock.sentinel.target_iqn,
|
||||
fake_portal,
|
||||
fake_login_opts,
|
||||
is_persistent=False)])
|
||||
else:
|
||||
self.assertFalse(mock_login_iscsi_target.called)
|
||||
|
||||
mock_ensure_lun_available.assert_called_once_with(
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun,
|
||||
mock.sentinel.rescan_attempts)
|
||||
|
||||
def test_login_storage_target_path_exists(self):
|
||||
self._test_login_storage_target(login_required=False)
|
||||
|
||||
def test_login_new_storage_target_no_mpio(self):
|
||||
self._test_login_storage_target()
|
||||
|
||||
def test_login_storage_target_new_path_using_mpio(self):
|
||||
self._test_login_storage_target(mpio_enabled=True)
|
||||
|
||||
@ddt.data(dict(rescan_disks=True),
|
||||
dict(retry_interval=mock.sentinel.retry_interval))
|
||||
@ddt.unpack
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_device_from_session')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_target_sessions')
|
||||
@mock.patch('time.sleep')
|
||||
def test_ensure_lun_available(self, mock_sleep,
|
||||
mock_get_iscsi_target_sessions,
|
||||
mock_get_iscsi_device_from_session,
|
||||
rescan_disks=False, retry_interval=0):
|
||||
retry_count = 5
|
||||
mock_get_iscsi_target_sessions.return_value = [
|
||||
mock.Mock(SessionId=mock.sentinel.session_id)]
|
||||
|
||||
fake_exc = exceptions.ISCSIInitiatorAPIException(
|
||||
message='fake_message',
|
||||
error_code=1,
|
||||
func_name='fake_func')
|
||||
dev_num_side_eff = [None, -1,
|
||||
mock.sentinel.dev_num,
|
||||
mock.sentinel.dev_num]
|
||||
dev_path_side_eff = ([mock.sentinel.dev_path] * 2 +
|
||||
[None] + [mock.sentinel.dev_path])
|
||||
fake_device = mock.Mock()
|
||||
type(fake_device.StorageDeviceNumber).DeviceNumber = (
|
||||
mock.PropertyMock(side_effect=dev_num_side_eff))
|
||||
type(fake_device).LegacyName = (
|
||||
mock.PropertyMock(side_effect=dev_path_side_eff))
|
||||
|
||||
mock_get_dev_side_eff = [None, fake_exc] + [fake_device] * 4
|
||||
mock_get_iscsi_device_from_session.side_effect = mock_get_dev_side_eff
|
||||
|
||||
dev_num, dev_path = self._initiator.ensure_lun_available(
|
||||
mock.sentinel.target_iqn,
|
||||
mock.sentinel.target_lun,
|
||||
rescan_attempts=retry_count,
|
||||
retry_interval=retry_interval,
|
||||
rescan_disks=rescan_disks)
|
||||
|
||||
self.assertEqual(mock.sentinel.dev_num, dev_num)
|
||||
self.assertEqual(mock.sentinel.dev_path, dev_path)
|
||||
|
||||
mock_get_iscsi_target_sessions.assert_has_calls(
|
||||
[mock.call(mock.sentinel.target_iqn)] * (retry_count + 1))
|
||||
mock_get_iscsi_device_from_session.assert_has_calls(
|
||||
[mock.call(mock.sentinel.session_id,
|
||||
mock.sentinel.target_lun)] * retry_count)
|
||||
|
||||
expected_rescan_count = retry_count if rescan_disks else 0
|
||||
self.assertEqual(
|
||||
expected_rescan_count,
|
||||
self._initiator._diskutils.rescan_disks.call_count)
|
||||
|
||||
if retry_interval:
|
||||
mock_sleep.assert_has_calls(
|
||||
[mock.call(retry_interval)] * retry_count)
|
||||
else:
|
||||
self.assertFalse(mock_sleep.called)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_target_sessions')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_logout_iscsi_target')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_remove_target_persistent_logins')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_remove_static_target')
|
||||
def test_logout_storage_target(self, mock_remove_static_target,
|
||||
mock_remove_target_persistent_logins,
|
||||
mock_logout_iscsi_target,
|
||||
mock_get_iscsi_target_sessions):
|
||||
fake_session = mock.Mock(SessionId=mock.sentinel.session_id)
|
||||
mock_get_iscsi_target_sessions.return_value = [fake_session]
|
||||
|
||||
self._initiator.logout_storage_target(mock.sentinel.target_iqn)
|
||||
|
||||
mock_get_iscsi_target_sessions.assert_called_once_with(
|
||||
mock.sentinel.target_iqn, connected_only=False)
|
||||
mock_logout_iscsi_target.assert_called_once_with(
|
||||
mock.sentinel.session_id)
|
||||
mock_remove_target_persistent_logins.assert_called_once_with(
|
||||
mock.sentinel.target_iqn)
|
||||
mock_remove_static_target.assert_called_once_with(
|
||||
mock.sentinel.target_iqn)
|
||||
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_remove_persistent_login')
|
||||
@mock.patch.object(iscsi_utils.ISCSIInitiatorUtils,
|
||||
'_get_iscsi_persistent_logins')
|
||||
def test_remove_target_persistent_logins(self,
|
||||
mock_get_iscsi_persistent_logins,
|
||||
mock_remove_persistent_login):
|
||||
fake_persistent_login = mock.Mock(TargetName=mock.sentinel.target_iqn)
|
||||
mock_get_iscsi_persistent_logins.return_value = [fake_persistent_login]
|
||||
|
||||
self._initiator._remove_target_persistent_logins(
|
||||
mock.sentinel.target_iqn)
|
||||
|
||||
mock_remove_persistent_login.assert_called_once_with(
|
||||
fake_persistent_login)
|
||||
mock_get_iscsi_persistent_logins.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(ctypes, 'byref')
|
||||
def test_remove_persistent_login(self, mock_byref):
|
||||
fake_persistent_login = mock.Mock()
|
||||
fake_persistent_login.InitiatorInstance = 'fake_initiator_instance'
|
||||
fake_persistent_login.TargetName = 'fake_target_name'
|
||||
|
||||
self._initiator._remove_persistent_login(fake_persistent_login)
|
||||
|
||||
args_list = self._mock_run.call_args_list[0][0]
|
||||
self.assertIsInstance(args_list[1], ctypes.c_wchar_p)
|
||||
self.assertEqual(fake_persistent_login.InitiatorInstance,
|
||||
args_list[1].value)
|
||||
self.assertIsInstance(args_list[3], ctypes.c_wchar_p)
|
||||
self.assertEqual(fake_persistent_login.TargetName,
|
||||
args_list[3].value)
|
||||
mock_byref.assert_called_once_with(fake_persistent_login.TargetPortal)
|
@ -1,491 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage.target import iscsi_target_utils as tg_utils
|
||||
|
||||
|
||||
class ISCSITargetUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
@mock.patch.object(tg_utils, 'hostutils')
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils,
|
||||
'_ensure_wt_provider_available')
|
||||
def setUp(self, mock_ensure_wt_provider_available, mock_hostutils):
|
||||
super(ISCSITargetUtilsTestCase, self).setUp()
|
||||
|
||||
self._tgutils = tg_utils.ISCSITargetUtils()
|
||||
self._tgutils._conn_wmi = mock.Mock()
|
||||
self._tgutils._pathutils = mock.Mock()
|
||||
|
||||
def test_ensure_wt_provider_unavailable(self):
|
||||
self._tgutils._conn_wmi = None
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils._ensure_wt_provider_available)
|
||||
|
||||
def test_get_supported_disk_format_6_2(self):
|
||||
self._tgutils._win_gteq_6_3 = False
|
||||
fmt = self._tgutils.get_supported_disk_format()
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, fmt)
|
||||
|
||||
def test_get_supported_disk_format_6_3(self):
|
||||
self._tgutils._win_gteq_6_3 = True
|
||||
fmt = self._tgutils.get_supported_disk_format()
|
||||
self.assertEqual(constants.DISK_FORMAT_VHDX, fmt)
|
||||
|
||||
def test_get_supported_vhd_type_6_2(self):
|
||||
self._tgutils._win_gteq_6_3 = False
|
||||
vhd_type = self._tgutils.get_supported_vhd_type()
|
||||
self.assertEqual(constants.VHD_TYPE_FIXED, vhd_type)
|
||||
|
||||
def test_get_supported_vhd_type_6_3(self):
|
||||
self._tgutils._win_gteq_6_3 = True
|
||||
vhd_type = self._tgutils.get_supported_vhd_type()
|
||||
self.assertEqual(constants.VHD_TYPE_DYNAMIC, vhd_type)
|
||||
|
||||
def _test_get_portal_locations(self, available_only=False,
|
||||
fail_if_none_found=False):
|
||||
mock_portal = mock.Mock(Listen=False,
|
||||
Address=mock.sentinel.address,
|
||||
Port=mock.sentinel.port)
|
||||
mock_portal_location = "%s:%s" % (mock.sentinel.address,
|
||||
mock.sentinel.port)
|
||||
|
||||
mock_wt_portal_cls = self._tgutils._conn_wmi.WT_Portal
|
||||
mock_wt_portal_cls.return_value = [mock_portal]
|
||||
|
||||
if available_only and fail_if_none_found:
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.get_portal_locations,
|
||||
available_only=available_only,
|
||||
fail_if_none_found=fail_if_none_found)
|
||||
else:
|
||||
portals = self._tgutils.get_portal_locations(
|
||||
available_only=available_only,
|
||||
fail_if_none_found=fail_if_none_found)
|
||||
|
||||
expected_retrieved_portals = []
|
||||
if not available_only:
|
||||
expected_retrieved_portals.append(mock_portal_location)
|
||||
|
||||
self.assertEqual(expected_retrieved_portals,
|
||||
portals)
|
||||
|
||||
def test_get_portal_locations(self):
|
||||
self._test_get_portal_locations()
|
||||
|
||||
def test_get_available_portal_locations(self):
|
||||
self._test_get_portal_locations(available_only=True)
|
||||
|
||||
def test_get_portal_locations_failing_if_none(self):
|
||||
self._test_get_portal_locations(available_only=True,
|
||||
fail_if_none_found=True)
|
||||
|
||||
def _test_get_wt_host(self, host_found=True, fail_if_not_found=False):
|
||||
mock_wt_host = mock.Mock()
|
||||
mock_wt_host_cls = self._tgutils._conn_wmi.WT_Host
|
||||
mock_wt_host_cls.return_value = [mock_wt_host] if host_found else []
|
||||
|
||||
if not host_found and fail_if_not_found:
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils._get_wt_host,
|
||||
mock.sentinel.target_name,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
else:
|
||||
wt_host = self._tgutils._get_wt_host(
|
||||
mock.sentinel.target_name,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
|
||||
expected_wt_host = mock_wt_host if host_found else None
|
||||
self.assertEqual(expected_wt_host, wt_host)
|
||||
|
||||
mock_wt_host_cls.assert_called_once_with(
|
||||
HostName=mock.sentinel.target_name)
|
||||
|
||||
def test_get_wt_host(self):
|
||||
self._test_get_wt_host()
|
||||
|
||||
def test_get_wt_host_not_found(self):
|
||||
self._test_get_wt_host(host_found=False)
|
||||
|
||||
def test_get_wt_host_not_found_exception(self):
|
||||
self._test_get_wt_host(host_found=False,
|
||||
fail_if_not_found=True)
|
||||
|
||||
def _test_get_wt_disk(self, disk_found=True, fail_if_not_found=False):
|
||||
mock_wt_disk = mock.Mock()
|
||||
mock_wt_disk_cls = self._tgutils._conn_wmi.WT_Disk
|
||||
mock_wt_disk_cls.return_value = [mock_wt_disk] if disk_found else []
|
||||
|
||||
if not disk_found and fail_if_not_found:
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils._get_wt_disk,
|
||||
mock.sentinel.disk_description,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
else:
|
||||
wt_disk = self._tgutils._get_wt_disk(
|
||||
mock.sentinel.disk_description,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
|
||||
expected_wt_disk = mock_wt_disk if disk_found else None
|
||||
self.assertEqual(expected_wt_disk, wt_disk)
|
||||
|
||||
mock_wt_disk_cls.assert_called_once_with(
|
||||
Description=mock.sentinel.disk_description)
|
||||
|
||||
def test_get_wt_disk(self):
|
||||
self._test_get_wt_disk()
|
||||
|
||||
def test_get_wt_disk_not_found(self):
|
||||
self._test_get_wt_disk(disk_found=False)
|
||||
|
||||
def test_get_wt_disk_not_found_exception(self):
|
||||
self._test_get_wt_disk(disk_found=False,
|
||||
fail_if_not_found=True)
|
||||
|
||||
def _test_get_wt_snap(self, snap_found=True, fail_if_not_found=False):
|
||||
mock_wt_snap = mock.Mock()
|
||||
mock_wt_snap_cls = self._tgutils._conn_wmi.WT_Snapshot
|
||||
mock_wt_snap_cls.return_value = [mock_wt_snap] if snap_found else []
|
||||
|
||||
if not snap_found and fail_if_not_found:
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils._get_wt_snapshot,
|
||||
mock.sentinel.snap_description,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
else:
|
||||
wt_snap = self._tgutils._get_wt_snapshot(
|
||||
mock.sentinel.snap_description,
|
||||
fail_if_not_found=fail_if_not_found)
|
||||
|
||||
expected_wt_snap = mock_wt_snap if snap_found else None
|
||||
self.assertEqual(expected_wt_snap, wt_snap)
|
||||
|
||||
mock_wt_snap_cls.assert_called_once_with(
|
||||
Description=mock.sentinel.snap_description)
|
||||
|
||||
def test_get_wt_snap(self):
|
||||
self._test_get_wt_snap()
|
||||
|
||||
def test_get_wt_snap_not_found(self):
|
||||
self._test_get_wt_snap(snap_found=False)
|
||||
|
||||
def test_get_wt_snap_not_found_exception(self):
|
||||
self._test_get_wt_snap(snap_found=False,
|
||||
fail_if_not_found=True)
|
||||
|
||||
def _test_get_wt_idmethod(self, idmeth_found=True):
|
||||
mock_wt_idmeth = mock.Mock()
|
||||
mock_wt_idmeth_cls = self._tgutils._conn_wmi.WT_IDMethod
|
||||
mock_wt_idmeth_cls.return_value = ([mock_wt_idmeth]
|
||||
if idmeth_found else [])
|
||||
|
||||
wt_idmeth = self._tgutils._get_wt_idmethod(mock.sentinel.initiator,
|
||||
mock.sentinel.target_name)
|
||||
|
||||
expected_wt_idmeth = mock_wt_idmeth if idmeth_found else None
|
||||
self.assertEqual(expected_wt_idmeth, wt_idmeth)
|
||||
|
||||
mock_wt_idmeth_cls.assert_called_once_with(
|
||||
HostName=mock.sentinel.target_name,
|
||||
Value=mock.sentinel.initiator)
|
||||
|
||||
def test_get_wt_idmethod(self):
|
||||
self._test_get_wt_idmethod()
|
||||
|
||||
def test_get_wt_idmethod_not_found(self):
|
||||
self._test_get_wt_idmethod(idmeth_found=False)
|
||||
|
||||
def _test_create_iscsi_target_exception(self, target_exists=False,
|
||||
fail_if_exists=False):
|
||||
fake_file_exists_hres = -0x7ff8ffb0
|
||||
fake_hres = fake_file_exists_hres if target_exists else 1
|
||||
mock_wt_host_cls = self._tgutils._conn_wmi.WT_Host
|
||||
mock_wt_host_cls.NewHost.side_effect = test_base.FakeWMIExc(
|
||||
hresult=fake_hres)
|
||||
|
||||
if target_exists and not fail_if_exists:
|
||||
self._tgutils.create_iscsi_target(mock.sentinel.target_name,
|
||||
fail_if_exists=fail_if_exists)
|
||||
else:
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.create_iscsi_target,
|
||||
mock.sentinel.target_name,
|
||||
fail_if_exists=fail_if_exists)
|
||||
|
||||
mock_wt_host_cls.NewHost.assert_called_once_with(
|
||||
HostName=mock.sentinel.target_name)
|
||||
|
||||
def test_create_iscsi_target_exception(self):
|
||||
self._test_create_iscsi_target_exception()
|
||||
|
||||
def test_create_iscsi_target_already_exists_skipping(self):
|
||||
self._test_create_iscsi_target_exception(target_exists=True)
|
||||
|
||||
def test_create_iscsi_target_already_exists_failing(self):
|
||||
self._test_create_iscsi_target_exception(target_exists=True,
|
||||
fail_if_exists=True)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_host')
|
||||
def test_delete_iscsi_target_exception(self, mock_get_wt_host):
|
||||
mock_wt_host = mock_get_wt_host.return_value
|
||||
mock_wt_host.Delete_.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.delete_iscsi_target,
|
||||
mock.sentinel.target_name)
|
||||
|
||||
mock_wt_host.RemoveAllWTDisks.assert_called_once_with()
|
||||
mock_get_wt_host.assert_called_once_with(mock.sentinel.target_name,
|
||||
fail_if_not_found=False)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_host')
|
||||
def _test_iscsi_target_exists(self, mock_get_wt_host, target_exists=True):
|
||||
mock_get_wt_host.return_value = (mock.sentinel.wt_host
|
||||
if target_exists else None)
|
||||
|
||||
result = self._tgutils.iscsi_target_exists(mock.sentinel.target_name)
|
||||
|
||||
self.assertEqual(target_exists, result)
|
||||
mock_get_wt_host.assert_called_once_with(mock.sentinel.target_name,
|
||||
fail_if_not_found=False)
|
||||
|
||||
def test_iscsi_target_exists(self):
|
||||
self._test_iscsi_target_exists()
|
||||
|
||||
def test_iscsi_target_unexisting(self):
|
||||
self._test_iscsi_target_exists(target_exists=False)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_host')
|
||||
def test_get_target_information(self, mock_get_wt_host):
|
||||
mock_wt_host = mock_get_wt_host.return_value
|
||||
mock_wt_host.EnableCHAP = True
|
||||
mock_wt_host.Status = 1 # connected
|
||||
|
||||
target_info = self._tgutils.get_target_information(
|
||||
mock.sentinel.target_name)
|
||||
|
||||
expected_info = dict(target_iqn=mock_wt_host.TargetIQN,
|
||||
enabled=mock_wt_host.Enabled,
|
||||
connected=True,
|
||||
auth_method='CHAP',
|
||||
auth_username=mock_wt_host.CHAPUserName,
|
||||
auth_password=mock_wt_host.CHAPSecret)
|
||||
self.assertEqual(expected_info, target_info)
|
||||
mock_get_wt_host.assert_called_once_with(mock.sentinel.target_name)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_host')
|
||||
def test_set_chap_credentials_exception(self, mock_get_wt_host):
|
||||
mock_wt_host = mock_get_wt_host.return_value
|
||||
mock_wt_host.put.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.set_chap_credentials,
|
||||
mock.sentinel.target_name,
|
||||
mock.sentinel.chap_username,
|
||||
mock.sentinel.chap_password)
|
||||
|
||||
mock_get_wt_host.assert_called_once_with(mock.sentinel.target_name)
|
||||
self.assertTrue(mock_wt_host.EnableCHAP),
|
||||
self.assertEqual(mock.sentinel.chap_username,
|
||||
mock_wt_host.CHAPUserName)
|
||||
self.assertEqual(mock.sentinel.chap_password,
|
||||
mock_wt_host.CHAPSecret)
|
||||
mock_wt_host.put.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_idmethod')
|
||||
def test_associate_initiator_exception(self, mock_get_wtidmethod):
|
||||
mock_get_wtidmethod.return_value = None
|
||||
mock_wt_idmeth_cls = self._tgutils._conn_wmi.WT_IDMethod
|
||||
mock_wt_idmetod = mock_wt_idmeth_cls.new.return_value
|
||||
mock_wt_idmetod.put.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.associate_initiator_with_iscsi_target,
|
||||
mock.sentinel.initiator, mock.sentinel.target_name,
|
||||
id_method=mock.sentinel.id_method)
|
||||
|
||||
self.assertEqual(mock.sentinel.target_name, mock_wt_idmetod.HostName)
|
||||
self.assertEqual(mock.sentinel.initiator, mock_wt_idmetod.Value)
|
||||
self.assertEqual(mock.sentinel.id_method, mock_wt_idmetod.Method)
|
||||
mock_get_wtidmethod.assert_called_once_with(mock.sentinel.initiator,
|
||||
mock.sentinel.target_name)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_idmethod')
|
||||
def test_already_associated_initiator(self, mock_get_wtidmethod):
|
||||
mock_wt_idmeth_cls = self._tgutils._conn_wmi.WT_IDMethod
|
||||
|
||||
self._tgutils.associate_initiator_with_iscsi_target(
|
||||
mock.sentinel.initiator, mock.sentinel.target_name,
|
||||
id_method=mock.sentinel.id_method)
|
||||
|
||||
self.assertFalse(mock_wt_idmeth_cls.new.called)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_idmethod')
|
||||
def test_deassociate_initiator_exception(self, mock_get_wtidmethod):
|
||||
mock_wt_idmetod = mock_get_wtidmethod.return_value
|
||||
mock_wt_idmetod.Delete_.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.deassociate_initiator,
|
||||
mock.sentinel.initiator, mock.sentinel.target_name)
|
||||
|
||||
mock_get_wtidmethod.assert_called_once_with(mock.sentinel.initiator,
|
||||
mock.sentinel.target_name)
|
||||
|
||||
def test_create_wt_disk_exception(self):
|
||||
mock_wt_disk_cls = self._tgutils._conn_wmi.WT_Disk
|
||||
mock_wt_disk_cls.NewWTDisk.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.create_wt_disk,
|
||||
mock.sentinel.vhd_path, mock.sentinel.wtd_name,
|
||||
mock.sentinel.size_mb)
|
||||
|
||||
mock_wt_disk_cls.NewWTDisk.assert_called_once_with(
|
||||
DevicePath=mock.sentinel.vhd_path,
|
||||
Description=mock.sentinel.wtd_name,
|
||||
SizeInMB=mock.sentinel.size_mb)
|
||||
|
||||
def test_import_wt_disk_exception(self):
|
||||
mock_wt_disk_cls = self._tgutils._conn_wmi.WT_Disk
|
||||
mock_wt_disk_cls.ImportWTDisk.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.import_wt_disk,
|
||||
mock.sentinel.vhd_path, mock.sentinel.wtd_name)
|
||||
|
||||
mock_wt_disk_cls.ImportWTDisk.assert_called_once_with(
|
||||
DevicePath=mock.sentinel.vhd_path,
|
||||
Description=mock.sentinel.wtd_name)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_disk')
|
||||
def test_change_wt_disk_status_exception(self, mock_get_wt_disk):
|
||||
mock_wt_disk = mock_get_wt_disk.return_value
|
||||
mock_wt_disk.put.side_effect = test_base.FakeWMIExc
|
||||
wt_disk_enabled = True
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.change_wt_disk_status,
|
||||
mock.sentinel.wtd_name,
|
||||
enabled=wt_disk_enabled)
|
||||
|
||||
mock_get_wt_disk.assert_called_once_with(mock.sentinel.wtd_name)
|
||||
self.assertEqual(wt_disk_enabled, mock_wt_disk.Enabled)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_disk')
|
||||
def test_remove_wt_disk_exception(self, mock_get_wt_disk):
|
||||
mock_wt_disk = mock_get_wt_disk.return_value
|
||||
mock_wt_disk.Delete_.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.remove_wt_disk,
|
||||
mock.sentinel.wtd_name)
|
||||
|
||||
mock_get_wt_disk.assert_called_once_with(mock.sentinel.wtd_name,
|
||||
fail_if_not_found=False)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_disk')
|
||||
def test_extend_wt_disk_exception(self, mock_get_wt_disk):
|
||||
mock_wt_disk = mock_get_wt_disk.return_value
|
||||
mock_wt_disk.Extend.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.extend_wt_disk,
|
||||
mock.sentinel.wtd_name,
|
||||
mock.sentinel.additional_mb)
|
||||
|
||||
mock_get_wt_disk.assert_called_once_with(mock.sentinel.wtd_name)
|
||||
mock_wt_disk.Extend.assert_called_once_with(
|
||||
mock.sentinel.additional_mb)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_host')
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_disk')
|
||||
def test_add_disk_to_target_exception(self, mock_get_wt_disk,
|
||||
mock_get_wt_host):
|
||||
mock_wt_disk = mock_get_wt_disk.return_value
|
||||
mock_wt_host = mock_get_wt_host.return_value
|
||||
mock_wt_host.AddWTDisk.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.add_disk_to_target,
|
||||
mock.sentinel.wtd_name,
|
||||
mock.sentinel.target_name)
|
||||
|
||||
mock_get_wt_disk.assert_called_once_with(mock.sentinel.wtd_name)
|
||||
mock_get_wt_host.assert_called_once_with(mock.sentinel.target_name)
|
||||
mock_wt_host.AddWTDisk.assert_called_once_with(mock_wt_disk.WTD)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_disk')
|
||||
def test_create_snapshot_exception(self, mock_get_wt_disk):
|
||||
mock_wt_disk = mock_get_wt_disk.return_value
|
||||
mock_wt_snap = mock.Mock()
|
||||
mock_wt_snap.put.side_effect = test_base.FakeWMIExc
|
||||
mock_wt_snap_cls = self._tgutils._conn_wmi.WT_Snapshot
|
||||
mock_wt_snap_cls.return_value = [mock_wt_snap]
|
||||
mock_wt_snap_cls.Create.return_value = [mock.sentinel.snap_id]
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.create_snapshot,
|
||||
mock.sentinel.wtd_name,
|
||||
mock.sentinel.snap_name)
|
||||
|
||||
mock_get_wt_disk.assert_called_once_with(mock.sentinel.wtd_name)
|
||||
mock_wt_snap_cls.Create.assert_called_once_with(WTD=mock_wt_disk.WTD)
|
||||
mock_wt_snap_cls.assert_called_once_with(Id=mock.sentinel.snap_id)
|
||||
self.assertEqual(mock.sentinel.snap_name, mock_wt_snap.Description)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_snapshot')
|
||||
def test_delete_snapshot_exception(self, mock_get_wt_snap):
|
||||
mock_wt_snap = mock_get_wt_snap.return_value
|
||||
mock_wt_snap.Delete_.side_effect = test_base.FakeWMIExc
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.delete_snapshot,
|
||||
mock.sentinel.snap_name)
|
||||
|
||||
mock_get_wt_snap.assert_called_once_with(mock.sentinel.snap_name,
|
||||
fail_if_not_found=False)
|
||||
|
||||
@mock.patch.object(tg_utils.ISCSITargetUtils, '_get_wt_snapshot')
|
||||
def test_export_snapshot_exception(self, mock_get_wt_snap):
|
||||
mock_wt_disk_cls = self._tgutils._conn_wmi.WT_Disk
|
||||
mock_wt_disk = mock.Mock()
|
||||
mock_wt_disk_cls.return_value = [mock_wt_disk]
|
||||
mock_wt_disk.Delete_.side_effect = test_base.FakeWMIExc
|
||||
mock_wt_snap = mock_get_wt_snap.return_value
|
||||
mock_wt_snap.Export.return_value = [mock.sentinel.wt_disk_id]
|
||||
|
||||
self.assertRaises(exceptions.ISCSITargetException,
|
||||
self._tgutils.export_snapshot,
|
||||
mock.sentinel.snap_name,
|
||||
mock.sentinel.dest_path)
|
||||
|
||||
mock_get_wt_snap.assert_called_once_with(mock.sentinel.snap_name)
|
||||
mock_wt_snap.Export.assert_called_once_with()
|
||||
mock_wt_disk_cls.assert_called_once_with(WTD=mock.sentinel.wt_disk_id)
|
||||
|
||||
expected_wt_disk_description = "%s-%s-temp" % (
|
||||
mock.sentinel.snap_name,
|
||||
mock.sentinel.wt_disk_id)
|
||||
self.assertEqual(expected_wt_disk_description,
|
||||
mock_wt_disk.Description)
|
||||
|
||||
mock_wt_disk.put.assert_called_once_with()
|
||||
mock_wt_disk.Delete_.assert_called_once_with()
|
||||
self._tgutils._pathutils.copy.assert_called_once_with(
|
||||
mock_wt_disk.DevicePath, mock.sentinel.dest_path)
|
@ -1,172 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage import diskutils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DiskUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
def setUp(self):
|
||||
super(DiskUtilsTestCase, self).setUp()
|
||||
self._diskutils = diskutils.DiskUtils()
|
||||
self._diskutils._conn_storage = mock.MagicMock()
|
||||
self._diskutils._win32_utils = mock.MagicMock()
|
||||
self._mock_run = self._diskutils._win32_utils.run_and_check_output
|
||||
|
||||
def test_get_disk(self):
|
||||
mock_msft_disk_cls = self._diskutils._conn_storage.Msft_Disk
|
||||
mock_disk = mock_msft_disk_cls.return_value[0]
|
||||
|
||||
resulted_disk = self._diskutils._get_disk(mock.sentinel.disk_number)
|
||||
|
||||
mock_msft_disk_cls.assert_called_once_with(
|
||||
Number=mock.sentinel.disk_number)
|
||||
self.assertEqual(mock_disk, resulted_disk)
|
||||
|
||||
def test_get_unexisting_disk(self):
|
||||
mock_msft_disk_cls = self._diskutils._conn_storage.Msft_Disk
|
||||
mock_msft_disk_cls.return_value = []
|
||||
|
||||
self.assertRaises(exceptions.DiskNotFound,
|
||||
self._diskutils._get_disk,
|
||||
mock.sentinel.disk_number)
|
||||
|
||||
mock_msft_disk_cls.assert_called_once_with(
|
||||
Number=mock.sentinel.disk_number)
|
||||
|
||||
@mock.patch.object(diskutils.DiskUtils, '_get_disk')
|
||||
def test_get_disk_uid_and_uid_type(self, mock_get_disk):
|
||||
mock_disk = mock_get_disk.return_value
|
||||
|
||||
uid, uid_type = self._diskutils.get_disk_uid_and_uid_type(
|
||||
mock.sentinel.disk_number)
|
||||
|
||||
mock_get_disk.assert_called_once_with(mock.sentinel.disk_number)
|
||||
self.assertEqual(mock_disk.UniqueId, uid)
|
||||
self.assertEqual(mock_disk.UniqueIdFormat, uid_type)
|
||||
|
||||
def test_get_disk_uid_and_uid_type_not_found(self):
|
||||
mock_msft_disk_cls = self._diskutils._conn_storage.Msft_Disk
|
||||
mock_msft_disk_cls.return_value = []
|
||||
|
||||
self.assertRaises(exceptions.DiskNotFound,
|
||||
self._diskutils.get_disk_uid_and_uid_type,
|
||||
mock.sentinel.disk_number)
|
||||
|
||||
@mock.patch.object(diskutils.DiskUtils, '_get_disk')
|
||||
def test_refresh_disk(self, mock_get_disk):
|
||||
mock_disk = mock_get_disk.return_value
|
||||
|
||||
self._diskutils.refresh_disk(mock.sentinel.disk_number)
|
||||
|
||||
mock_get_disk.assert_called_once_with(mock.sentinel.disk_number)
|
||||
mock_disk.Refresh.assert_called_once_with()
|
||||
|
||||
def test_get_dev_number_from_dev_name(self):
|
||||
fake_physical_device_name = r'\\.\PhysicalDrive15'
|
||||
expected_device_number = '15'
|
||||
|
||||
get_dev_number = self._diskutils.get_device_number_from_device_name
|
||||
resulted_dev_number = get_dev_number(fake_physical_device_name)
|
||||
self.assertEqual(expected_device_number, resulted_dev_number)
|
||||
|
||||
def test_get_device_number_from_invalid_device_name(self):
|
||||
fake_physical_device_name = ''
|
||||
|
||||
self.assertRaises(exceptions.DiskNotFound,
|
||||
self._diskutils.get_device_number_from_device_name,
|
||||
fake_physical_device_name)
|
||||
|
||||
def _get_mocked_wmi_rescan(self, return_value):
|
||||
conn = self._diskutils._conn_storage
|
||||
rescan_method = conn.Msft_StorageSetting.UpdateHostStorageCache
|
||||
rescan_method.return_value = return_value
|
||||
return rescan_method
|
||||
|
||||
@ddt.data(0, [0], (0,))
|
||||
@mock.patch('time.sleep')
|
||||
def test_rescan_disks(self, return_value, mock_sleep):
|
||||
mock_rescan = self._get_mocked_wmi_rescan(return_value)
|
||||
|
||||
self._diskutils.rescan_disks()
|
||||
|
||||
mock_rescan.assert_called_once_with()
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def test_rescan_disks_error(self, mock_sleep):
|
||||
mock_rescan = self._get_mocked_wmi_rescan(return_value=1)
|
||||
expected_retry_count = 5
|
||||
|
||||
self.assertRaises(exceptions.OSWinException,
|
||||
self._diskutils.rescan_disks)
|
||||
mock_rescan.assert_has_calls([mock.call()] * expected_retry_count)
|
||||
|
||||
@mock.patch.object(diskutils, 'ctypes')
|
||||
@mock.patch.object(diskutils, 'kernel32', create=True)
|
||||
@mock.patch('os.path.abspath')
|
||||
def _test_get_disk_capacity(self, mock_abspath,
|
||||
mock_kernel32, mock_ctypes,
|
||||
raised_exc=None, ignore_errors=False):
|
||||
expected_values = ('total_bytes', 'free_bytes')
|
||||
|
||||
mock_params = [mock.Mock(value=value) for value in expected_values]
|
||||
mock_ctypes.c_ulonglong.side_effect = mock_params
|
||||
mock_ctypes.c_wchar_p = lambda x: (x, 'c_wchar_p')
|
||||
|
||||
self._mock_run.side_effect = raised_exc(
|
||||
func_name='fake_func_name',
|
||||
error_code='fake_error_code',
|
||||
error_message='fake_error_message') if raised_exc else None
|
||||
|
||||
if raised_exc and not ignore_errors:
|
||||
self.assertRaises(raised_exc,
|
||||
self._diskutils.get_disk_capacity,
|
||||
mock.sentinel.disk_path,
|
||||
ignore_errors=ignore_errors)
|
||||
else:
|
||||
ret_val = self._diskutils.get_disk_capacity(
|
||||
mock.sentinel.disk_path,
|
||||
ignore_errors=ignore_errors)
|
||||
expected_ret_val = (0, 0) if raised_exc else expected_values
|
||||
|
||||
self.assertEqual(expected_ret_val, ret_val)
|
||||
|
||||
mock_abspath.assert_called_once_with(mock.sentinel.disk_path)
|
||||
mock_ctypes.pointer.assert_has_calls(
|
||||
[mock.call(param) for param in mock_params])
|
||||
self._mock_run.assert_called_once_with(
|
||||
mock_kernel32.GetDiskFreeSpaceExW,
|
||||
mock_ctypes.c_wchar_p(mock_abspath.return_value),
|
||||
None,
|
||||
mock_ctypes.pointer.return_value,
|
||||
mock_ctypes.pointer.return_value,
|
||||
kernel32_lib_func=True)
|
||||
|
||||
def test_get_disk_capacity_successfully(self):
|
||||
self._test_get_disk_capacity()
|
||||
|
||||
def test_get_disk_capacity_ignored_error(self):
|
||||
self._test_get_disk_capacity(
|
||||
raised_exc=exceptions.Win32Exception,
|
||||
ignore_errors=True)
|
||||
|
||||
def test_get_disk_capacity_raised_exc(self):
|
||||
self._test_get_disk_capacity(
|
||||
raised_exc=exceptions.Win32Exception)
|
@ -1,219 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils.storage import smbutils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class SMBUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
def setUp(self):
|
||||
super(SMBUtilsTestCase, self).setUp()
|
||||
|
||||
self._smbutils = smbutils.SMBUtils()
|
||||
self._smbutils._win32_utils = mock.Mock()
|
||||
self._smbutils._smb_conn = mock.Mock()
|
||||
self._mock_run = self._smbutils._win32_utils.run_and_check_output
|
||||
self._smb_conn = self._smbutils._smb_conn
|
||||
|
||||
@mock.patch.object(smbutils.SMBUtils, 'unmount_smb_share')
|
||||
@mock.patch('os.path.exists')
|
||||
def _test_check_smb_mapping(self, mock_exists, mock_unmount_smb_share,
|
||||
existing_mappings=True, share_available=False):
|
||||
mock_exists.return_value = share_available
|
||||
|
||||
fake_mappings = (
|
||||
[mock.sentinel.smb_mapping] if existing_mappings else [])
|
||||
|
||||
self._smb_conn.Msft_SmbMapping.return_value = fake_mappings
|
||||
|
||||
ret_val = self._smbutils.check_smb_mapping(
|
||||
mock.sentinel.share_path, remove_unavailable_mapping=True)
|
||||
|
||||
self.assertEqual(existing_mappings and share_available, ret_val)
|
||||
if existing_mappings and not share_available:
|
||||
mock_unmount_smb_share.assert_called_once_with(
|
||||
mock.sentinel.share_path, force=True)
|
||||
|
||||
def test_check_mapping(self):
|
||||
self._test_check_smb_mapping()
|
||||
|
||||
def test_remake_unavailable_mapping(self):
|
||||
self._test_check_smb_mapping(existing_mappings=True,
|
||||
share_available=False)
|
||||
|
||||
def test_available_mapping(self):
|
||||
self._test_check_smb_mapping(existing_mappings=True,
|
||||
share_available=True)
|
||||
|
||||
def test_mount_smb_share(self):
|
||||
fake_create = self._smb_conn.Msft_SmbMapping.Create
|
||||
self._smbutils.mount_smb_share(mock.sentinel.share_path,
|
||||
mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
fake_create.assert_called_once_with(
|
||||
RemotePath=mock.sentinel.share_path,
|
||||
UserName=mock.sentinel.username,
|
||||
Password=mock.sentinel.password)
|
||||
|
||||
def test_mount_smb_share_failed(self):
|
||||
self._smb_conn.Msft_SmbMapping.Create.side_effect = exceptions.x_wmi
|
||||
|
||||
self.assertRaises(exceptions.SMBException,
|
||||
self._smbutils.mount_smb_share,
|
||||
mock.sentinel.share_path)
|
||||
|
||||
def _test_unmount_smb_share(self, force=False):
|
||||
fake_mapping = mock.Mock()
|
||||
fake_mapping_attr_err = mock.Mock()
|
||||
fake_mapping_attr_err.side_effect = AttributeError
|
||||
smb_mapping_class = self._smb_conn.Msft_SmbMapping
|
||||
smb_mapping_class.return_value = [fake_mapping, fake_mapping_attr_err]
|
||||
|
||||
self._smbutils.unmount_smb_share(mock.sentinel.share_path,
|
||||
force)
|
||||
|
||||
smb_mapping_class.assert_called_once_with(
|
||||
RemotePath=mock.sentinel.share_path)
|
||||
fake_mapping.Remove.assert_called_once_with(Force=force)
|
||||
|
||||
def test_soft_unmount_smb_share(self):
|
||||
self._test_unmount_smb_share()
|
||||
|
||||
def test_force_unmount_smb_share(self):
|
||||
self._test_unmount_smb_share(force=True)
|
||||
|
||||
def test_unmount_smb_share_wmi_exception(self):
|
||||
fake_mapping = mock.Mock()
|
||||
fake_mapping.Remove.side_effect = exceptions.x_wmi
|
||||
self._smb_conn.Msft_SmbMapping.return_value = [fake_mapping]
|
||||
|
||||
self.assertRaises(exceptions.SMBException,
|
||||
self._smbutils.unmount_smb_share,
|
||||
mock.sentinel.share_path, force=True)
|
||||
|
||||
@mock.patch.object(smbutils, 'ctypes')
|
||||
@mock.patch.object(smbutils, 'kernel32', create=True)
|
||||
@mock.patch('os.path.abspath')
|
||||
def _test_get_share_capacity_info(self, mock_abspath,
|
||||
mock_kernel32, mock_ctypes,
|
||||
raised_exc=None, ignore_errors=False):
|
||||
expected_values = ('total_bytes', 'free_bytes')
|
||||
|
||||
mock_params = [mock.Mock(value=value) for value in expected_values]
|
||||
mock_ctypes.c_ulonglong.side_effect = mock_params
|
||||
mock_ctypes.c_wchar_p = lambda x: (x, 'c_wchar_p')
|
||||
|
||||
self._mock_run.side_effect = raised_exc(
|
||||
func_name='fake_func_name',
|
||||
error_code='fake_error_code',
|
||||
error_message='fake_error_message') if raised_exc else None
|
||||
|
||||
if raised_exc and not ignore_errors:
|
||||
self.assertRaises(raised_exc,
|
||||
self._smbutils.get_share_capacity_info,
|
||||
mock.sentinel.share_path,
|
||||
ignore_errors=ignore_errors)
|
||||
else:
|
||||
ret_val = self._smbutils.get_share_capacity_info(
|
||||
mock.sentinel.share_path,
|
||||
ignore_errors=ignore_errors)
|
||||
expected_ret_val = (0, 0) if raised_exc else expected_values
|
||||
|
||||
self.assertEqual(expected_ret_val, ret_val)
|
||||
|
||||
mock_abspath.assert_called_once_with(mock.sentinel.share_path)
|
||||
mock_ctypes.pointer.assert_has_calls(
|
||||
[mock.call(param) for param in mock_params])
|
||||
self._mock_run.assert_called_once_with(
|
||||
mock_kernel32.GetDiskFreeSpaceExW,
|
||||
mock_ctypes.c_wchar_p(mock_abspath.return_value),
|
||||
None,
|
||||
mock_ctypes.pointer.return_value,
|
||||
mock_ctypes.pointer.return_value,
|
||||
kernel32_lib_func=True)
|
||||
|
||||
def test_get_share_capacity_info_successfully(self):
|
||||
self._test_get_share_capacity_info()
|
||||
|
||||
def test_get_share_capacity_info_ignored_error(self):
|
||||
self._test_get_share_capacity_info(
|
||||
raised_exc=exceptions.Win32Exception,
|
||||
ignore_errors=True)
|
||||
|
||||
def test_get_share_capacity_info_raised_exc(self):
|
||||
self._test_get_share_capacity_info(
|
||||
raised_exc=exceptions.Win32Exception)
|
||||
|
||||
def test_get_smb_share_path(self):
|
||||
fake_share = mock.Mock(Path=mock.sentinel.share_path)
|
||||
self._smb_conn.Msft_SmbShare.return_value = [fake_share]
|
||||
|
||||
share_path = self._smbutils.get_smb_share_path(
|
||||
mock.sentinel.share_name)
|
||||
|
||||
self.assertEqual(mock.sentinel.share_path, share_path)
|
||||
self._smb_conn.Msft_SmbShare.assert_called_once_with(
|
||||
Name=mock.sentinel.share_name)
|
||||
|
||||
def test_get_unexisting_smb_share_path(self):
|
||||
self._smb_conn.Msft_SmbShare.return_value = []
|
||||
|
||||
share_path = self._smbutils.get_smb_share_path(
|
||||
mock.sentinel.share_name)
|
||||
|
||||
self.assertIsNone(share_path)
|
||||
self._smb_conn.Msft_SmbShare.assert_called_once_with(
|
||||
Name=mock.sentinel.share_name)
|
||||
|
||||
@ddt.data({'local_ips': [mock.sentinel.ip0, mock.sentinel.ip1],
|
||||
'dest_ips': [mock.sentinel.ip2, mock.sentinel.ip3],
|
||||
'expected_local': False},
|
||||
{'local_ips': [mock.sentinel.ip0, mock.sentinel.ip1],
|
||||
'dest_ips': [mock.sentinel.ip1, mock.sentinel.ip3],
|
||||
'expected_local': True},
|
||||
{'local_ips': [],
|
||||
'dest_ips': ['127.0.0.1'],
|
||||
'expected_local': True})
|
||||
@ddt.unpack
|
||||
@mock.patch('os_win._utils.get_ips')
|
||||
@mock.patch('socket.gethostname')
|
||||
def test_is_local_share(self, mock_gethostname, mock_get_ips,
|
||||
local_ips, dest_ips, expected_local):
|
||||
fake_share_server = 'fake_share_server'
|
||||
fake_share = '\\\\%s\\fake_share' % fake_share_server
|
||||
|
||||
mock_get_ips.side_effect = (local_ips,
|
||||
['127.0.0.1', '::1'],
|
||||
dest_ips)
|
||||
self._smbutils._loopback_share_map = {}
|
||||
|
||||
is_local = self._smbutils.is_local_share(fake_share)
|
||||
self.assertEqual(expected_local, is_local)
|
||||
|
||||
# We ensure that this value is cached, calling it again
|
||||
# and making sure that we have attempted to resolve the
|
||||
# address only once.
|
||||
self._smbutils.is_local_share(fake_share)
|
||||
|
||||
mock_gethostname.assert_called_once_with()
|
||||
mock_get_ips.assert_has_calls(
|
||||
[mock.call(mock_gethostname.return_value),
|
||||
mock.call('localhost'),
|
||||
mock.call(fake_share_server)])
|
@ -1,762 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
from oslotest import base
|
||||
import six
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils.storage.virtdisk import vhdutils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
|
||||
|
||||
class VHDUtilsTestCase(base.BaseTestCase):
|
||||
"""Unit tests for the Hyper-V VHDUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(VHDUtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._fake_vst_struct = self._vdisk_struct.VIRTUAL_STORAGE_TYPE
|
||||
|
||||
self._vhdutils = vhdutils.VHDUtils()
|
||||
self._vhdutils._win32_utils = mock.Mock()
|
||||
|
||||
self._mock_close = self._vhdutils._win32_utils.close_handle
|
||||
self._mock_run = self._vhdutils._win32_utils.run_and_check_output
|
||||
self._run_args = self._vhdutils._virtdisk_run_args
|
||||
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._vdisk_struct = mock.Mock()
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
self._ctypes.c_wchar_p = lambda x: (x, "c_wchar_p")
|
||||
self._ctypes.c_ulong = lambda x: (x, "c_ulong")
|
||||
|
||||
mock.patch.multiple(vhdutils,
|
||||
ctypes=self._ctypes, kernel32=mock.DEFAULT,
|
||||
wintypes=mock.DEFAULT, virtdisk=mock.DEFAULT,
|
||||
vdisk_struct=self._vdisk_struct,
|
||||
create=True).start()
|
||||
|
||||
def _test_run_and_check_output(self, raised_exc=None):
|
||||
self._mock_run.side_effect = raised_exc(
|
||||
func_name='fake_func_name',
|
||||
error_code='fake_error_code',
|
||||
error_message='fake_error_message') if raised_exc else None
|
||||
|
||||
if raised_exc:
|
||||
self.assertRaises(
|
||||
raised_exc,
|
||||
self._vhdutils._run_and_check_output,
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
cleanup_handle=mock.sentinel.handle)
|
||||
else:
|
||||
ret_val = self._vhdutils._run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
cleanup_handle=mock.sentinel.handle)
|
||||
self.assertEqual(self._mock_run.return_value, ret_val)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
mock.sentinel.func, mock.sentinel.arg, **self._run_args)
|
||||
self._mock_close.assert_called_once_with(mock.sentinel.handle)
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._test_run_and_check_output()
|
||||
|
||||
def test_run_and_check_output_raising_error(self):
|
||||
self._test_run_and_check_output(
|
||||
raised_exc=exceptions.VHDWin32APIException)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhd_device_id')
|
||||
def test_open(self, mock_get_dev_id):
|
||||
fake_vst = self._fake_vst_struct.return_value
|
||||
|
||||
mock_get_dev_id.return_value = mock.sentinel.device_id
|
||||
|
||||
handle = self._vhdutils._open(
|
||||
vhd_path=mock.sentinel.vhd_path,
|
||||
open_flag=mock.sentinel.open_flag,
|
||||
open_access_mask=mock.sentinel.access_mask,
|
||||
open_params=mock.sentinel.open_params)
|
||||
|
||||
self.assertEqual(vhdutils.wintypes.HANDLE.return_value, handle)
|
||||
self._fake_vst_struct.assert_called_once_with(
|
||||
DeviceId=mock.sentinel.device_id,
|
||||
VendorId=w_const.VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.OpenVirtualDisk,
|
||||
self._ctypes.byref(fake_vst),
|
||||
self._ctypes.c_wchar_p(mock.sentinel.vhd_path),
|
||||
mock.sentinel.access_mask,
|
||||
mock.sentinel.open_flag,
|
||||
mock.sentinel.open_params,
|
||||
self._ctypes.byref(vhdutils.wintypes.HANDLE.return_value),
|
||||
**self._run_args)
|
||||
|
||||
def test_close(self):
|
||||
self._vhdutils._close(mock.sentinel.handle)
|
||||
vhdutils.kernel32.CloseHandle.assert_called_once_with(
|
||||
mock.sentinel.handle)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhd_device_id')
|
||||
def _test_create_vhd(self, mock_get_dev_id, new_vhd_type):
|
||||
create_params_struct = (
|
||||
self._vdisk_struct.CREATE_VIRTUAL_DISK_PARAMETERS)
|
||||
mock_handle = vhdutils.wintypes.HANDLE.return_value
|
||||
|
||||
fake_vst = self._fake_vst_struct.return_value
|
||||
fake_create_params = create_params_struct.return_value
|
||||
|
||||
expected_create_vhd_flag = (
|
||||
vhdutils.CREATE_VIRTUAL_DISK_FLAGS.get(new_vhd_type, 0))
|
||||
|
||||
self._vhdutils.create_vhd(
|
||||
new_vhd_path=mock.sentinel.new_vhd_path,
|
||||
new_vhd_type=new_vhd_type,
|
||||
src_path=mock.sentinel.src_path,
|
||||
max_internal_size=mock.sentinel.max_internal_size,
|
||||
parent_path=mock.sentinel.parent_path)
|
||||
|
||||
self._fake_vst_struct.assert_called_once_with(
|
||||
DeviceId=mock_get_dev_id.return_value,
|
||||
VendorId=w_const.VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT)
|
||||
|
||||
self.assertEqual(w_const.CREATE_VIRTUAL_DISK_VERSION_2,
|
||||
fake_create_params.Version)
|
||||
self.assertEqual(mock.sentinel.max_internal_size,
|
||||
fake_create_params.Version2.MaximumSize)
|
||||
self.assertEqual(mock.sentinel.parent_path,
|
||||
fake_create_params.Version2.ParentPath)
|
||||
self.assertEqual(mock.sentinel.src_path,
|
||||
fake_create_params.Version2.SourcePath)
|
||||
self.assertEqual(
|
||||
vhdutils.VIRTUAL_DISK_DEFAULT_PHYS_SECTOR_SIZE,
|
||||
fake_create_params.Version2.PhysicalSectorSizeInBytes)
|
||||
self.assertEqual(
|
||||
w_const.CREATE_VHD_PARAMS_DEFAULT_BLOCK_SIZE,
|
||||
fake_create_params.Version2.BlockSizeInBytes)
|
||||
self.assertEqual(
|
||||
vhdutils.VIRTUAL_DISK_DEFAULT_SECTOR_SIZE,
|
||||
fake_create_params.Version2.SectorSizeInBytes)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.CreateVirtualDisk,
|
||||
self._ctypes.byref(fake_vst),
|
||||
self._ctypes.c_wchar_p(mock.sentinel.new_vhd_path),
|
||||
0,
|
||||
None,
|
||||
expected_create_vhd_flag,
|
||||
0,
|
||||
self._ctypes.byref(fake_create_params),
|
||||
None,
|
||||
self._ctypes.byref(mock_handle),
|
||||
**self._run_args)
|
||||
|
||||
self._mock_close.assert_called_once_with(mock_handle)
|
||||
|
||||
def test_create_dynamic_vhd(self):
|
||||
self._test_create_vhd(new_vhd_type=constants.VHD_TYPE_DYNAMIC)
|
||||
|
||||
def test_create_fixed_vhd(self):
|
||||
self._test_create_vhd(new_vhd_type=constants.VHD_TYPE_FIXED)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'create_vhd')
|
||||
def test_create_dynamic_vhd_helper(self, mock_create_vhd):
|
||||
self._vhdutils.create_dynamic_vhd(mock.sentinel.path,
|
||||
mock.sentinel.size)
|
||||
|
||||
mock_create_vhd.assert_called_once_with(
|
||||
mock.sentinel.path,
|
||||
constants.VHD_TYPE_DYNAMIC,
|
||||
max_internal_size=mock.sentinel.size)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'create_vhd')
|
||||
def test_create_differencing_vhd_helper(self, mock_create_vhd):
|
||||
self._vhdutils.create_differencing_vhd(mock.sentinel.path,
|
||||
mock.sentinel.parent_path)
|
||||
|
||||
mock_create_vhd.assert_called_once_with(
|
||||
mock.sentinel.path,
|
||||
constants.VHD_TYPE_DIFFERENCING,
|
||||
parent_path=mock.sentinel.parent_path)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'create_vhd')
|
||||
def test_convert_vhd(self, mock_create_vhd):
|
||||
self._vhdutils.convert_vhd(mock.sentinel.src,
|
||||
mock.sentinel.dest,
|
||||
mock.sentinel.vhd_type)
|
||||
|
||||
mock_create_vhd.assert_called_once_with(
|
||||
mock.sentinel.dest,
|
||||
mock.sentinel.vhd_type,
|
||||
src_path=mock.sentinel.src)
|
||||
|
||||
def test_get_vhd_format_found_by_ext(self):
|
||||
fake_vhd_path = 'C:\\test.vhd'
|
||||
|
||||
ret_val = self._vhdutils.get_vhd_format(fake_vhd_path)
|
||||
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, ret_val)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhd_format_by_signature')
|
||||
@mock.patch('os.path.exists')
|
||||
def _test_vhd_format_unrecognized_ext(self, mock_exists,
|
||||
mock_get_vhd_fmt_by_sign,
|
||||
signature_available=False):
|
||||
mock_exists.return_value = True
|
||||
fake_vhd_path = 'C:\\test_vhd'
|
||||
mock_get_vhd_fmt_by_sign.return_value = (
|
||||
constants.DISK_FORMAT_VHD if signature_available else None)
|
||||
|
||||
if signature_available:
|
||||
ret_val = self._vhdutils.get_vhd_format(fake_vhd_path)
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, ret_val)
|
||||
else:
|
||||
self.assertRaises(exceptions.VHDException,
|
||||
self._vhdutils.get_vhd_format,
|
||||
fake_vhd_path)
|
||||
|
||||
def test_get_vhd_format_unrecognised_ext_unavailable_signature(self):
|
||||
self._test_vhd_format_unrecognized_ext()
|
||||
|
||||
def test_get_vhd_format_unrecognised_ext_available_signature(self):
|
||||
self._test_vhd_format_unrecognized_ext(signature_available=True)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_format')
|
||||
def test_get_vhd_device_id(self, mock_get_vhd_fmt):
|
||||
mock_get_vhd_fmt.return_value = constants.DISK_FORMAT_VHD
|
||||
|
||||
dev_id = self._vhdutils._get_vhd_device_id(mock.sentinel.vhd_path)
|
||||
|
||||
mock_get_vhd_fmt.assert_called_once_with(mock.sentinel.vhd_path)
|
||||
self.assertEqual(w_const.VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
|
||||
dev_id)
|
||||
|
||||
def _mock_open(self, read_data=None, curr_f_pos=0):
|
||||
mock_open = mock.mock_open()
|
||||
mock.patch.object(vhdutils, 'open', mock_open,
|
||||
create=True).start()
|
||||
|
||||
f = mock_open.return_value
|
||||
f.read.side_effect = read_data
|
||||
f.tell.return_value = curr_f_pos
|
||||
|
||||
return mock_open
|
||||
|
||||
def test_get_vhd_format_by_sig_vhdx(self):
|
||||
read_data = (vhdutils.VHDX_SIGNATURE, )
|
||||
self._mock_open(read_data=read_data)
|
||||
|
||||
fmt = self._vhdutils._get_vhd_format_by_signature(
|
||||
mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(constants.DISK_FORMAT_VHDX, fmt)
|
||||
|
||||
def test_get_vhd_format_by_sig_vhd(self):
|
||||
read_data = ('notthesig', vhdutils.VHD_SIGNATURE)
|
||||
mock_open = self._mock_open(read_data=read_data, curr_f_pos=1024)
|
||||
|
||||
fmt = self._vhdutils._get_vhd_format_by_signature(
|
||||
mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, fmt)
|
||||
mock_open.return_value.seek.assert_has_calls([mock.call(0, 2),
|
||||
mock.call(-512, 2)])
|
||||
|
||||
def test_get_vhd_format_by_sig_invalid_format(self):
|
||||
self._mock_open(read_data='notthesig', curr_f_pos=1024)
|
||||
|
||||
fmt = self._vhdutils._get_vhd_format_by_signature(
|
||||
mock.sentinel.vhd_path)
|
||||
|
||||
self.assertIsNone(fmt)
|
||||
|
||||
def test_get_vhd_format_by_sig_zero_length_file(self):
|
||||
mock_open = self._mock_open(read_data=('', ''))
|
||||
|
||||
fmt = self._vhdutils._get_vhd_format_by_signature(
|
||||
mock.sentinel.vhd_path)
|
||||
|
||||
self.assertIsNone(fmt)
|
||||
mock_open.return_value.seek.assert_called_once_with(0, 2)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_open')
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhd_info_member')
|
||||
def test_get_vhd_info(self, mock_get_vhd_info_member,
|
||||
mock_open):
|
||||
fake_info_member = w_const.GET_VIRTUAL_DISK_INFO_SIZE
|
||||
fake_vhd_info = {'VirtualSize': mock.sentinel.virtual_size}
|
||||
|
||||
mock_open.return_value = mock.sentinel.handle
|
||||
mock_get_vhd_info_member.return_value = fake_vhd_info
|
||||
|
||||
expected_open_flag = w_const.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS
|
||||
expected_access_mask = (w_const.VIRTUAL_DISK_ACCESS_GET_INFO |
|
||||
w_const.VIRTUAL_DISK_ACCESS_DETACH)
|
||||
|
||||
ret_val = self._vhdutils.get_vhd_info(mock.sentinel.vhd_path,
|
||||
[fake_info_member])
|
||||
|
||||
self.assertEqual(fake_vhd_info, ret_val)
|
||||
mock_open.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
open_flag=expected_open_flag,
|
||||
open_access_mask=expected_access_mask)
|
||||
self._vhdutils._get_vhd_info_member.assert_called_once_with(
|
||||
mock.sentinel.handle,
|
||||
fake_info_member)
|
||||
self._mock_close.assert_called_once_with(mock.sentinel.handle)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_parse_vhd_info')
|
||||
def test_get_vhd_info_member(self, mock_parse_vhd_info):
|
||||
get_vd_info_struct = (
|
||||
self._vdisk_struct.GET_VIRTUAL_DISK_INFO)
|
||||
fake_params = get_vd_info_struct.return_value
|
||||
fake_info_size = self._ctypes.sizeof.return_value
|
||||
|
||||
info_member = w_const.GET_VIRTUAL_DISK_INFO_PARENT_LOCATION
|
||||
|
||||
vhd_info = self._vhdutils._get_vhd_info_member(
|
||||
mock.sentinel.vhd_path,
|
||||
info_member)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.GetVirtualDiskInformation,
|
||||
mock.sentinel.vhd_path,
|
||||
self._ctypes.byref(
|
||||
self._ctypes.c_ulong(fake_info_size)),
|
||||
self._ctypes.byref(fake_params), None,
|
||||
ignored_error_codes=[w_const.ERROR_VHD_INVALID_TYPE],
|
||||
**self._run_args)
|
||||
|
||||
self.assertEqual(mock_parse_vhd_info.return_value, vhd_info)
|
||||
mock_parse_vhd_info.assert_called_once_with(fake_params,
|
||||
info_member)
|
||||
|
||||
def test_parse_vhd_info(self):
|
||||
fake_info_member = w_const.GET_VIRTUAL_DISK_INFO_SIZE
|
||||
fake_info = mock.Mock()
|
||||
fake_info.Size._fields_ = [
|
||||
("VirtualSize", vhdutils.wintypes.ULARGE_INTEGER),
|
||||
("PhysicalSize", vhdutils.wintypes.ULARGE_INTEGER)]
|
||||
fake_info.Size.VirtualSize = mock.sentinel.virt_size
|
||||
fake_info.Size.PhysicalSize = mock.sentinel.phys_size
|
||||
|
||||
ret_val = self._vhdutils._parse_vhd_info(fake_info,
|
||||
fake_info_member)
|
||||
expected = {'VirtualSize': mock.sentinel.virt_size,
|
||||
'PhysicalSize': mock.sentinel.phys_size}
|
||||
|
||||
self.assertEqual(expected, ret_val)
|
||||
|
||||
def test_parse_vhd_provider_subtype_member(self):
|
||||
fake_info_member = w_const.GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE
|
||||
fake_info = mock.Mock()
|
||||
fake_info.ProviderSubtype = mock.sentinel.provider_subtype
|
||||
|
||||
ret_val = self._vhdutils._parse_vhd_info(fake_info, fake_info_member)
|
||||
expected = {'ProviderSubtype': mock.sentinel.provider_subtype}
|
||||
|
||||
self.assertEqual(expected, ret_val)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_info')
|
||||
def test_get_vhd_size(self, mock_get_vhd_info):
|
||||
ret_val = self._vhdutils.get_vhd_size(mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(mock_get_vhd_info.return_value, ret_val)
|
||||
mock_get_vhd_info.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
[w_const.GET_VIRTUAL_DISK_INFO_SIZE])
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_info')
|
||||
def test_get_vhd_parent_path(self, mock_get_vhd_info):
|
||||
mock_get_vhd_info.return_value = {
|
||||
'ParentPath': mock.sentinel.parent_path}
|
||||
|
||||
ret_val = self._vhdutils.get_vhd_parent_path(mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(mock.sentinel.parent_path, ret_val)
|
||||
mock_get_vhd_info.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
[w_const.GET_VIRTUAL_DISK_INFO_PARENT_LOCATION])
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_info')
|
||||
def test_get_vhd_type(self, mock_get_vhd_info):
|
||||
mock_get_vhd_info.return_value = {
|
||||
'ProviderSubtype': mock.sentinel.provider_subtype}
|
||||
|
||||
ret_val = self._vhdutils.get_vhd_type(mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(mock.sentinel.provider_subtype, ret_val)
|
||||
mock_get_vhd_info.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
[w_const.GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE])
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_open')
|
||||
@mock.patch('os.remove')
|
||||
def test_merge_vhd(self, mock_remove, mock_open):
|
||||
open_params_struct = (
|
||||
self._vdisk_struct.OPEN_VIRTUAL_DISK_PARAMETERS)
|
||||
merge_params_struct = (
|
||||
self._vdisk_struct.MERGE_VIRTUAL_DISK_PARAMETERS)
|
||||
|
||||
fake_open_params = open_params_struct.return_value
|
||||
fake_merge_params = merge_params_struct.return_value
|
||||
mock_open.return_value = mock.sentinel.handle
|
||||
|
||||
self._vhdutils.merge_vhd(mock.sentinel.vhd_path)
|
||||
|
||||
self.assertEqual(w_const.OPEN_VIRTUAL_DISK_VERSION_1,
|
||||
fake_open_params.Version)
|
||||
self.assertEqual(2,
|
||||
fake_open_params.Version1.RWDepth)
|
||||
|
||||
mock_open.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
open_params=self._ctypes.byref(fake_open_params))
|
||||
|
||||
self.assertEqual(w_const.MERGE_VIRTUAL_DISK_VERSION_1,
|
||||
fake_merge_params.Version)
|
||||
self.assertEqual(1,
|
||||
fake_merge_params.Version1.MergeDepth)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.MergeVirtualDisk,
|
||||
mock.sentinel.handle,
|
||||
0,
|
||||
self._ctypes.byref(fake_merge_params),
|
||||
None,
|
||||
**self._run_args)
|
||||
mock_remove.assert_called_once_with(
|
||||
mock.sentinel.vhd_path)
|
||||
self._mock_close.assert_called_once_with(mock.sentinel.handle)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_open')
|
||||
def test_reconnect_parent_vhd(self, mock_open):
|
||||
set_vdisk_info_struct = (
|
||||
self._vdisk_struct.SET_VIRTUAL_DISK_INFO)
|
||||
open_params_struct = (
|
||||
self._vdisk_struct.OPEN_VIRTUAL_DISK_PARAMETERS)
|
||||
|
||||
fake_set_params = set_vdisk_info_struct.return_value
|
||||
fake_open_params = open_params_struct.return_value
|
||||
mock_open.return_value = mock.sentinel.handle
|
||||
|
||||
self._vhdutils.reconnect_parent_vhd(mock.sentinel.vhd_path,
|
||||
mock.sentinel.parent_path)
|
||||
|
||||
self.assertEqual(w_const.OPEN_VIRTUAL_DISK_VERSION_2,
|
||||
fake_open_params.Version)
|
||||
self.assertFalse(fake_open_params.Version2.GetInfoOnly)
|
||||
|
||||
self._vhdutils._open.assert_called_once_with(
|
||||
mock.sentinel.vhd_path,
|
||||
open_flag=w_const.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS,
|
||||
open_access_mask=0,
|
||||
open_params=vhdutils.ctypes.byref(fake_open_params))
|
||||
|
||||
self.assertEqual(w_const.SET_VIRTUAL_DISK_INFO_PARENT_PATH,
|
||||
fake_set_params.Version)
|
||||
self.assertEqual(mock.sentinel.parent_path,
|
||||
fake_set_params.ParentFilePath)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.SetVirtualDiskInformation,
|
||||
mock.sentinel.handle,
|
||||
vhdutils.ctypes.byref(fake_set_params),
|
||||
**self._run_args)
|
||||
self._mock_close.assert_called_once_with(mock.sentinel.handle)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_internal_vhd_size_by_file_size')
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_resize_vhd')
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_check_resize_needed')
|
||||
def _test_resize_vhd(self, mock_check_resize_needed,
|
||||
mock_resize_helper, mock_get_internal_size,
|
||||
is_file_max_size=True, resize_needed=True):
|
||||
mock_check_resize_needed.return_value = resize_needed
|
||||
|
||||
self._vhdutils.resize_vhd(mock.sentinel.vhd_path,
|
||||
mock.sentinel.new_size,
|
||||
is_file_max_size,
|
||||
validate_new_size=True)
|
||||
|
||||
if is_file_max_size:
|
||||
mock_get_internal_size.assert_called_once_with(
|
||||
mock.sentinel.vhd_path, mock.sentinel.new_size)
|
||||
expected_new_size = mock_get_internal_size.return_value
|
||||
else:
|
||||
expected_new_size = mock.sentinel.new_size
|
||||
|
||||
mock_check_resize_needed.assert_called_once_with(
|
||||
mock.sentinel.vhd_path, expected_new_size)
|
||||
if resize_needed:
|
||||
mock_resize_helper.assert_called_once_with(mock.sentinel.vhd_path,
|
||||
expected_new_size)
|
||||
else:
|
||||
self.assertFalse(mock_resize_helper.called)
|
||||
|
||||
def test_resize_vhd_specifying_internal_size(self):
|
||||
self._test_resize_vhd(is_file_max_size=False)
|
||||
|
||||
def test_resize_vhd_specifying_file_max_size(self):
|
||||
self._test_resize_vhd()
|
||||
|
||||
def test_resize_vhd_already_having_requested_size(self):
|
||||
self._test_resize_vhd(resize_needed=False)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_size')
|
||||
def _test_check_resize_needed(self, mock_get_vhd_size,
|
||||
current_size=1, new_size=2):
|
||||
mock_get_vhd_size.return_value = dict(VirtualSize=current_size)
|
||||
|
||||
if current_size > new_size:
|
||||
self.assertRaises(exceptions.VHDException,
|
||||
self._vhdutils._check_resize_needed,
|
||||
mock.sentinel.vhd_path,
|
||||
new_size)
|
||||
else:
|
||||
resize_needed = self._vhdutils._check_resize_needed(
|
||||
mock.sentinel.vhd_path, new_size)
|
||||
self.assertEqual(current_size < new_size, resize_needed)
|
||||
|
||||
def test_check_resize_needed_smaller_new_size(self):
|
||||
self._test_check_resize_needed(current_size=2, new_size=1)
|
||||
|
||||
def test_check_resize_needed_bigger_new_size(self):
|
||||
self._test_check_resize_needed()
|
||||
|
||||
def test_check_resize_needed_smaller_equal_size(self):
|
||||
self._test_check_resize_needed(current_size=1, new_size=1)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_open')
|
||||
def test_resize_vhd_helper(self, mock_open):
|
||||
resize_vdisk_struct = (
|
||||
self._vdisk_struct.RESIZE_VIRTUAL_DISK_PARAMETERS)
|
||||
fake_params = resize_vdisk_struct.return_value
|
||||
|
||||
mock_open.return_value = mock.sentinel.handle
|
||||
|
||||
self._vhdutils._resize_vhd(mock.sentinel.vhd_path,
|
||||
mock.sentinel.new_size)
|
||||
|
||||
self.assertEqual(w_const.RESIZE_VIRTUAL_DISK_VERSION_1,
|
||||
fake_params.Version)
|
||||
self.assertEqual(mock.sentinel.new_size,
|
||||
fake_params.Version1.NewSize)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
vhdutils.virtdisk.ResizeVirtualDisk,
|
||||
mock.sentinel.handle,
|
||||
0,
|
||||
vhdutils.ctypes.byref(fake_params),
|
||||
None,
|
||||
**self._run_args)
|
||||
self._mock_close.assert_called_once_with(mock.sentinel.handle)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'get_vhd_info')
|
||||
@mock.patch.object(vhdutils.VHDUtils,
|
||||
'_get_internal_vhd_size_by_file_size')
|
||||
@mock.patch.object(vhdutils.VHDUtils,
|
||||
'_get_internal_vhdx_size_by_file_size')
|
||||
def _test_get_int_sz_by_file_size(
|
||||
self, mock_get_vhdx_int_size,
|
||||
mock_get_vhd_int_size, mock_get_vhd_info,
|
||||
vhd_dev_id=w_const.VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
|
||||
vhd_type=constants.VHD_TYPE_DYNAMIC):
|
||||
fake_vhd_info = dict(ProviderSubtype=vhd_type,
|
||||
ParentPath=mock.sentinel.parent_path,
|
||||
DeviceId=vhd_dev_id)
|
||||
mock_get_vhd_info.side_effect = [fake_vhd_info]
|
||||
exppected_vhd_info_calls = [mock.call(mock.sentinel.vhd_path)]
|
||||
expected_vhd_checked = mock.sentinel.vhd_path
|
||||
expected_checked_vhd_info = fake_vhd_info
|
||||
|
||||
if vhd_type == constants.VHD_TYPE_DIFFERENCING:
|
||||
expected_checked_vhd_info = dict(
|
||||
fake_vhd_info, vhd_type=constants.VHD_TYPE_DYNAMIC)
|
||||
mock_get_vhd_info.side_effect.append(
|
||||
expected_checked_vhd_info)
|
||||
exppected_vhd_info_calls.append(
|
||||
mock.call(mock.sentinel.parent_path))
|
||||
expected_vhd_checked = mock.sentinel.parent_path
|
||||
|
||||
is_vhd = vhd_dev_id == w_const.VIRTUAL_STORAGE_TYPE_DEVICE_VHD
|
||||
expected_helper = (mock_get_vhd_int_size
|
||||
if is_vhd
|
||||
else mock_get_vhdx_int_size)
|
||||
|
||||
ret_val = self._vhdutils.get_internal_vhd_size_by_file_size(
|
||||
mock.sentinel.vhd_path, mock.sentinel.vhd_size)
|
||||
|
||||
mock_get_vhd_info.assert_has_calls(exppected_vhd_info_calls)
|
||||
expected_helper.assert_called_once_with(expected_vhd_checked,
|
||||
mock.sentinel.vhd_size,
|
||||
expected_checked_vhd_info)
|
||||
self.assertEqual(expected_helper.return_value, ret_val)
|
||||
|
||||
def test_get_int_sz_by_file_size_vhd(self):
|
||||
self._test_get_int_sz_by_file_size()
|
||||
|
||||
def test_get_int_sz_by_file_size_vhdx(self):
|
||||
self._test_get_int_sz_by_file_size(
|
||||
vhd_dev_id=w_const.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX)
|
||||
|
||||
def test_get_int_sz_by_file_size_differencing(self):
|
||||
self._test_get_int_sz_by_file_size(
|
||||
vhd_dev_id=w_const.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX)
|
||||
|
||||
def _mocked_get_internal_vhd_size(self, root_vhd_size, vhd_type):
|
||||
fake_vhd_info = dict(ProviderSubtype=vhd_type,
|
||||
BlockSize=2097152,
|
||||
ParentPath=mock.sentinel.parent_path)
|
||||
|
||||
return self._vhdutils._get_internal_vhd_size_by_file_size(
|
||||
mock.sentinel.vhd_path, root_vhd_size, fake_vhd_info)
|
||||
|
||||
def test_get_internal_vhd_size_by_file_size_fixed(self):
|
||||
root_vhd_size = 1 << 30
|
||||
real_size = self._mocked_get_internal_vhd_size(
|
||||
root_vhd_size=root_vhd_size,
|
||||
vhd_type=constants.VHD_TYPE_FIXED)
|
||||
|
||||
expected_vhd_size = root_vhd_size - 512
|
||||
self.assertEqual(expected_vhd_size, real_size)
|
||||
|
||||
def test_get_internal_vhd_size_by_file_size_dynamic(self):
|
||||
root_vhd_size = 20 << 30
|
||||
real_size = self._mocked_get_internal_vhd_size(
|
||||
root_vhd_size=root_vhd_size,
|
||||
vhd_type=constants.VHD_TYPE_DYNAMIC)
|
||||
|
||||
expected_md_size = 43008
|
||||
expected_vhd_size = root_vhd_size - expected_md_size
|
||||
self.assertEqual(expected_vhd_size, real_size)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhdx_block_size')
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhdx_log_size')
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhdx_metadata_size_and_offset')
|
||||
def test_get_vhdx_internal_size(self, mock_get_vhdx_md_sz_and_off,
|
||||
mock_get_vhdx_log_sz,
|
||||
mock_get_vhdx_block_size):
|
||||
self._mock_open()
|
||||
fake_log_sz = 1 << 20
|
||||
fake_block_sz = 32 << 20
|
||||
fake_md_sz = 1 << 20
|
||||
fake_logical_sector_sz = 4096
|
||||
new_vhd_sz = 1 << 30
|
||||
# We expect less than a block to be reserved for internal metadata.
|
||||
expected_max_int_sz = new_vhd_sz - fake_block_sz
|
||||
|
||||
fake_vhd_info = dict(SectorSize=fake_logical_sector_sz)
|
||||
|
||||
mock_get_vhdx_block_size.return_value = fake_block_sz
|
||||
mock_get_vhdx_log_sz.return_value = fake_log_sz
|
||||
mock_get_vhdx_md_sz_and_off.return_value = fake_md_sz, None
|
||||
|
||||
internal_size = self._vhdutils._get_internal_vhdx_size_by_file_size(
|
||||
mock.sentinel.vhd_path, new_vhd_sz, fake_vhd_info)
|
||||
|
||||
self.assertIn(type(internal_size), six.integer_types)
|
||||
self.assertEqual(expected_max_int_sz, internal_size)
|
||||
|
||||
def test_get_vhdx_internal_size_exception(self):
|
||||
mock_open = self._mock_open()
|
||||
mock_open.side_effect = IOError
|
||||
func = self._vhdutils._get_internal_vhdx_size_by_file_size
|
||||
self.assertRaises(exceptions.VHDException,
|
||||
func,
|
||||
mock.sentinel.vhd_path,
|
||||
mock.sentinel.vhd_size,
|
||||
mock.sentinel.vhd_info)
|
||||
|
||||
def _get_mock_file_handle(self, *args):
|
||||
mock_file_handle = mock.Mock()
|
||||
mock_file_handle.read.side_effect = args
|
||||
return mock_file_handle
|
||||
|
||||
def test_get_vhdx_current_header(self):
|
||||
# The current header has the maximum sequence number.
|
||||
fake_seq_numbers = [
|
||||
bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00'),
|
||||
bytearray(b'\x02\x00\x00\x00\x00\x00\x00\x00')]
|
||||
mock_handle = self._get_mock_file_handle(*fake_seq_numbers)
|
||||
|
||||
offset = self._vhdutils._get_vhdx_current_header_offset(mock_handle)
|
||||
|
||||
self.assertEqual(vhdutils.VHDX_HEADER_OFFSETS[1], offset)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, '_get_vhdx_current_header_offset')
|
||||
def test_get_log_size(self, mock_get_vhdx_curr_hd_offset):
|
||||
fake_curr_header_offset = vhdutils.VHDX_HEADER_OFFSETS[0]
|
||||
fake_log_sz = bytearray(b'\x01\x00\x00\x00')
|
||||
|
||||
mock_get_vhdx_curr_hd_offset.return_value = fake_curr_header_offset
|
||||
mock_handle = self._get_mock_file_handle(fake_log_sz)
|
||||
|
||||
log_size = self._vhdutils._get_vhdx_log_size(mock_handle)
|
||||
|
||||
self.assertEqual(log_size, 1)
|
||||
|
||||
def test_get_vhdx_metadata_size(self):
|
||||
fake_md_offset = bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x00')
|
||||
fake_md_sz = bytearray(b'\x01\x00\x00\x00')
|
||||
|
||||
mock_handle = self._get_mock_file_handle(fake_md_offset,
|
||||
fake_md_sz)
|
||||
|
||||
md_sz, md_offset = self._vhdutils._get_vhdx_metadata_size_and_offset(
|
||||
mock_handle)
|
||||
|
||||
self.assertEqual(1, md_sz)
|
||||
self.assertEqual(1, md_offset)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils,
|
||||
'_get_vhdx_metadata_size_and_offset')
|
||||
def test_get_block_size(self, mock_get_md_sz_and_offset):
|
||||
mock_get_md_sz_and_offset.return_value = (mock.sentinel.md_sz, 1024)
|
||||
fake_block_size = bytearray(b'\x01\x00\x00\x00')
|
||||
fake_offset = bytearray(b'\x02\x00\x00\x00')
|
||||
mock_handle = self._get_mock_file_handle(fake_offset,
|
||||
fake_block_size)
|
||||
|
||||
block_size = self._vhdutils._get_vhdx_block_size(mock_handle)
|
||||
self.assertEqual(block_size, 1)
|
||||
|
||||
@mock.patch.object(vhdutils.VHDUtils, 'convert_vhd')
|
||||
@mock.patch.object(os, 'unlink')
|
||||
@mock.patch.object(os, 'rename')
|
||||
def test_flatten_vhd(self, mock_rename, mock_unlink, mock_convert):
|
||||
fake_vhd_path = r'C:\test.vhd'
|
||||
expected_tmp_path = r'C:\test.tmp.vhd'
|
||||
|
||||
self._vhdutils.flatten_vhd(fake_vhd_path)
|
||||
|
||||
mock_convert.assert_called_once_with(fake_vhd_path, expected_tmp_path)
|
||||
mock_unlink.assert_called_once_with(fake_vhd_path)
|
||||
mock_rename.assert_called_once_with(expected_tmp_path, fake_vhd_path)
|
||||
|
||||
def test_get_best_supported_vhd_format(self):
|
||||
fmt = self._vhdutils.get_best_supported_vhd_format()
|
||||
self.assertEqual(constants.DISK_FORMAT_VHDX, fmt)
|
@ -1,125 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import _acl_utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ACLUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
def setUp(self):
|
||||
super(ACLUtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._acl_utils = _acl_utils.ACLUtils()
|
||||
self._acl_utils._win32_utils = mock.Mock()
|
||||
self._mock_run = self._acl_utils._win32_utils.run_and_check_output
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._ctypes = mock.Mock()
|
||||
self._ctypes.c_wchar_p = lambda x: (x, "c_wchar_p")
|
||||
self._ctypes.c_uint = lambda x: (x, 'c_uint')
|
||||
self._ctypes.c_ulong = lambda x: (x, 'c_ulong')
|
||||
|
||||
mock.patch.multiple(_acl_utils,
|
||||
ctypes=self._ctypes,
|
||||
advapi32=mock.DEFAULT,
|
||||
kernel32=mock.DEFAULT,
|
||||
create=True).start()
|
||||
|
||||
def test_get_void_pp(self):
|
||||
pp_void = self._acl_utils._get_void_pp()
|
||||
|
||||
self.assertEqual(pp_void, self._ctypes.pointer.return_value)
|
||||
self._ctypes.pointer.assert_called_once_with(
|
||||
self._ctypes.c_void_p.return_value)
|
||||
self._ctypes.c_void_p.assert_called_once_with()
|
||||
|
||||
@ddt.data(
|
||||
{'security_info_flags':
|
||||
(w_const.OWNER_SECURITY_INFORMATION |
|
||||
w_const.GROUP_SECURITY_INFORMATION |
|
||||
w_const.DACL_SECURITY_INFORMATION),
|
||||
'expected_info': ['pp_sid_owner', 'pp_sid_group',
|
||||
'pp_dacl', 'pp_sec_desc']},
|
||||
{'security_info_flags': w_const.SACL_SECURITY_INFORMATION,
|
||||
'expected_info': ['pp_sacl', 'pp_sec_desc']})
|
||||
@ddt.unpack
|
||||
@mock.patch.object(_acl_utils.ACLUtils, '_get_void_pp')
|
||||
def test_get_named_security_info(self, mock_get_void_pp,
|
||||
security_info_flags,
|
||||
expected_info):
|
||||
sec_info = self._acl_utils.get_named_security_info(
|
||||
mock.sentinel.obj_name,
|
||||
mock.sentinel.obj_type,
|
||||
security_info_flags)
|
||||
|
||||
self.assertEqual(set(expected_info), set(sec_info.keys()))
|
||||
for field in expected_info:
|
||||
self.assertEqual(sec_info[field],
|
||||
mock_get_void_pp.return_value)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
_acl_utils.advapi32.GetNamedSecurityInfoW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.obj_name),
|
||||
mock.sentinel.obj_type,
|
||||
security_info_flags,
|
||||
sec_info.get('pp_sid_owner'),
|
||||
sec_info.get('pp_sid_group'),
|
||||
sec_info.get('pp_dacl'),
|
||||
sec_info.get('pp_sacl'),
|
||||
sec_info['pp_sec_desc'])
|
||||
|
||||
@mock.patch.object(_acl_utils.ACLUtils, '_get_void_pp')
|
||||
def test_set_entries_in_acl(self, mock_get_void_pp):
|
||||
new_acl = mock_get_void_pp.return_value
|
||||
|
||||
returned_acl = self._acl_utils.set_entries_in_acl(
|
||||
mock.sentinel.entry_count,
|
||||
mock.sentinel.entry_list,
|
||||
mock.sentinel.old_acl)
|
||||
|
||||
self.assertEqual(new_acl, returned_acl)
|
||||
self._mock_run.assert_called_once_with(
|
||||
_acl_utils.advapi32.SetEntriesInAclW,
|
||||
mock.sentinel.entry_count,
|
||||
mock.sentinel.entry_list,
|
||||
mock.sentinel.old_acl,
|
||||
new_acl)
|
||||
mock_get_void_pp.assert_called_once_with()
|
||||
|
||||
def test_set_named_security_info(self):
|
||||
self._acl_utils.set_named_security_info(
|
||||
mock.sentinel.obj_name,
|
||||
mock.sentinel.obj_type,
|
||||
mock.sentinel.security_info_flags,
|
||||
mock.sentinel.p_sid_owner,
|
||||
mock.sentinel.p_sid_group,
|
||||
mock.sentinel.p_dacl,
|
||||
mock.sentinel.p_sacl)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
_acl_utils.advapi32.SetNamedSecurityInfoW,
|
||||
self._ctypes.c_wchar_p(mock.sentinel.obj_name),
|
||||
mock.sentinel.obj_type,
|
||||
mock.sentinel.security_info_flags,
|
||||
mock.sentinel.p_sid_owner,
|
||||
mock.sentinel.p_sid_group,
|
||||
mock.sentinel.p_dacl,
|
||||
mock.sentinel.p_sacl)
|
@ -1,150 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 mock
|
||||
import six
|
||||
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import baseutils
|
||||
|
||||
|
||||
class BaseUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the os-win BaseUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(BaseUtilsTestCase, self).setUp()
|
||||
self.utils = baseutils.BaseUtils()
|
||||
self.utils._conn = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(baseutils, 'wmi', create=True)
|
||||
def test_get_wmi_obj(self, mock_wmi):
|
||||
result = self.utils._get_wmi_obj(mock.sentinel.moniker)
|
||||
|
||||
self.assertEqual(mock_wmi.WMI.return_value, result)
|
||||
mock_wmi.WMI.assert_called_once_with(moniker=mock.sentinel.moniker)
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtils, '_get_wmi_obj')
|
||||
@mock.patch.object(baseutils, 'sys')
|
||||
def _check_get_wmi_conn(self, mock_sys, mock_get_wmi_obj, **kwargs):
|
||||
mock_sys.platform = 'win32'
|
||||
result = self.utils._get_wmi_conn(mock.sentinel.moniker, **kwargs)
|
||||
|
||||
self.assertEqual(mock_get_wmi_obj.return_value, result)
|
||||
mock_get_wmi_obj.assert_called_once_with(mock.sentinel.moniker,
|
||||
**kwargs)
|
||||
|
||||
def test_get_wmi_conn_kwargs(self):
|
||||
self.utils._WMI_CONS.clear()
|
||||
self._check_get_wmi_conn(privileges=mock.sentinel.privileges)
|
||||
self.assertNotIn(mock.sentinel.moniker, baseutils.BaseUtils._WMI_CONS)
|
||||
|
||||
def test_get_wmi_conn(self):
|
||||
self._check_get_wmi_conn()
|
||||
self.assertIn(mock.sentinel.moniker, baseutils.BaseUtils._WMI_CONS)
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtils, '_get_wmi_obj')
|
||||
@mock.patch.object(baseutils, 'sys')
|
||||
def test_get_wmi_conn_cached(self, mock_sys, mock_get_wmi_obj):
|
||||
mock_sys.platform = 'win32'
|
||||
baseutils.BaseUtils._WMI_CONS[mock.sentinel.moniker] = (
|
||||
mock.sentinel.conn)
|
||||
result = self.utils._get_wmi_conn(mock.sentinel.moniker)
|
||||
|
||||
self.assertEqual(mock.sentinel.conn, result)
|
||||
self.assertFalse(mock_get_wmi_obj.called)
|
||||
|
||||
@mock.patch.object(baseutils, 'sys')
|
||||
def test_get_wmi_conn_linux(self, mock_sys):
|
||||
mock_sys.platform = 'linux'
|
||||
result = self.utils._get_wmi_conn(mock.sentinel.moniker)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class BaseUtilsVirtTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the os-win BaseUtilsVirt class."""
|
||||
|
||||
def setUp(self):
|
||||
super(BaseUtilsVirtTestCase, self).setUp()
|
||||
self.utils = baseutils.BaseUtilsVirt()
|
||||
self.utils._conn_attr = mock.MagicMock()
|
||||
baseutils.BaseUtilsVirt._os_version = None
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtilsVirt, '_get_wmi_conn')
|
||||
def test_conn(self, mock_get_wmi_conn):
|
||||
self.utils._conn_attr = None
|
||||
|
||||
self.assertEqual(mock_get_wmi_conn.return_value, self.utils._conn)
|
||||
mock_get_wmi_conn.assert_called_once_with(
|
||||
self.utils._wmi_namespace % '.')
|
||||
|
||||
def test_vs_man_svc(self):
|
||||
mock_os = mock.MagicMock(Version='6.3.0')
|
||||
self._mock_wmi.WMI.return_value.Win32_OperatingSystem.return_value = [
|
||||
mock_os]
|
||||
expected = self.utils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
self.assertEqual(expected, self.utils._vs_man_svc)
|
||||
self.assertEqual(expected, self.utils._vs_man_svc_attr)
|
||||
|
||||
@mock.patch.object(baseutils, 'imp')
|
||||
@mock.patch.object(baseutils, 'wmi', create=True)
|
||||
def test_vs_man_svc_2012(self, mock_wmi, mock_imp):
|
||||
baseutils.BaseUtilsVirt._old_wmi = None
|
||||
mock_os = mock.MagicMock(Version='6.2.0')
|
||||
mock_wmi.WMI.return_value.Win32_OperatingSystem.return_value = [
|
||||
mock_os]
|
||||
fake_module_path = '/fake/path/to/module'
|
||||
mock_wmi.__path__ = [fake_module_path]
|
||||
old_conn = mock_imp.load_source.return_value.WMI.return_value
|
||||
|
||||
expected = old_conn.Msvm_VirtualSystemManagementService()[0]
|
||||
self.assertEqual(expected, self.utils._vs_man_svc)
|
||||
self.assertIsNone(self.utils._vs_man_svc_attr)
|
||||
mock_imp.load_source.assert_called_once_with(
|
||||
'old_wmi', '%s.py' % fake_module_path)
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtilsVirt, '_get_wmi_compat_conn')
|
||||
def test_get_wmi_obj_compatibility_6_3(self, mock_get_wmi_compat):
|
||||
mock_os = mock.MagicMock(Version='6.3.0')
|
||||
self._mock_wmi.WMI.return_value.Win32_OperatingSystem.return_value = [
|
||||
mock_os]
|
||||
|
||||
result = self.utils._get_wmi_obj(mock.sentinel.moniker, True)
|
||||
self.assertEqual(self._mock_wmi.WMI.return_value, result)
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtilsVirt, '_get_wmi_compat_conn')
|
||||
def test_get_wmi_obj_no_compatibility_6_2(self, mock_get_wmi_compat):
|
||||
baseutils.BaseUtilsVirt._os_version = [6, 2]
|
||||
result = self.utils._get_wmi_obj(mock.sentinel.moniker, False)
|
||||
self.assertEqual(self._mock_wmi.WMI.return_value, result)
|
||||
|
||||
@mock.patch.object(baseutils.BaseUtilsVirt, '_get_wmi_compat_conn')
|
||||
def test_get_wmi_obj_compatibility_6_2(self, mock_get_wmi_compat):
|
||||
baseutils.BaseUtilsVirt._os_version = [6, 2]
|
||||
result = self.utils._get_wmi_obj(mock.sentinel.moniker, True)
|
||||
self.assertEqual(mock_get_wmi_compat.return_value, result)
|
||||
|
||||
|
||||
class SynchronizedMetaTestCase(test_base.OsWinBaseTestCase):
|
||||
@mock.patch.object(baseutils.threading, 'RLock')
|
||||
def test_synchronized_meta(self, mock_rlock_cls):
|
||||
fake_cls = type('fake_cls', (object, ),
|
||||
dict(method1=lambda x: None, method2=lambda y: None))
|
||||
fake_cls = six.add_metaclass(baseutils.SynchronizedMeta)(fake_cls)
|
||||
|
||||
fake_cls().method1()
|
||||
fake_cls().method2()
|
||||
|
||||
mock_rlock_cls.assert_called_once_with()
|
||||
self.assertEqual(2, mock_rlock_cls.return_value.__exit__.call_count)
|
@ -1,301 +0,0 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import hostutils
|
||||
|
||||
|
||||
class FakeCPUSpec(object):
|
||||
"""Fake CPU Spec for unit tests."""
|
||||
|
||||
Architecture = mock.sentinel.cpu_arch
|
||||
Name = mock.sentinel.cpu_name
|
||||
Manufacturer = mock.sentinel.cpu_man
|
||||
MaxClockSpeed = mock.sentinel.max_clock_speed
|
||||
NumberOfCores = mock.sentinel.cpu_cores
|
||||
NumberOfLogicalProcessors = mock.sentinel.cpu_procs
|
||||
|
||||
|
||||
class HostUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V hostutils class."""
|
||||
|
||||
_DEVICE_ID = "Microsoft:UUID\\0\\0"
|
||||
_NODE_ID = "Microsoft:PhysicalNode\\0"
|
||||
|
||||
_FAKE_MEMORY_TOTAL = 1024
|
||||
_FAKE_MEMORY_FREE = 512
|
||||
_FAKE_DISK_SIZE = 1024
|
||||
_FAKE_DISK_FREE = 512
|
||||
_FAKE_VERSION_GOOD = '6.2.0'
|
||||
_FAKE_VERSION_BAD = '6.1.9'
|
||||
|
||||
def setUp(self):
|
||||
self._hostutils = hostutils.HostUtils()
|
||||
self._hostutils._conn_cimv2 = mock.MagicMock()
|
||||
self._hostutils._conn_attr = mock.MagicMock()
|
||||
|
||||
super(HostUtilsTestCase, self).setUp()
|
||||
|
||||
@mock.patch('os_win.utils.hostutils.kernel32')
|
||||
def test_get_host_tick_count64(self, mock_kernel32):
|
||||
tick_count64 = "100"
|
||||
mock_kernel32.GetTickCount64.return_value = tick_count64
|
||||
response = self._hostutils.get_host_tick_count64()
|
||||
self.assertEqual(tick_count64, response)
|
||||
|
||||
def test_get_cpus_info(self):
|
||||
cpu = mock.MagicMock(spec=FakeCPUSpec)
|
||||
self._hostutils._conn_cimv2.query.return_value = [cpu]
|
||||
cpu_list = self._hostutils.get_cpus_info()
|
||||
self.assertEqual([cpu._mock_children], cpu_list)
|
||||
|
||||
def test_get_memory_info(self):
|
||||
memory = mock.MagicMock()
|
||||
type(memory).TotalVisibleMemorySize = mock.PropertyMock(
|
||||
return_value=self._FAKE_MEMORY_TOTAL)
|
||||
type(memory).FreePhysicalMemory = mock.PropertyMock(
|
||||
return_value=self._FAKE_MEMORY_FREE)
|
||||
|
||||
self._hostutils._conn_cimv2.query.return_value = [memory]
|
||||
total_memory, free_memory = self._hostutils.get_memory_info()
|
||||
|
||||
self.assertEqual(self._FAKE_MEMORY_TOTAL, total_memory)
|
||||
self.assertEqual(self._FAKE_MEMORY_FREE, free_memory)
|
||||
|
||||
def test_get_volume_info(self):
|
||||
disk = mock.MagicMock()
|
||||
type(disk).Size = mock.PropertyMock(return_value=self._FAKE_DISK_SIZE)
|
||||
type(disk).FreeSpace = mock.PropertyMock(
|
||||
return_value=self._FAKE_DISK_FREE)
|
||||
|
||||
self._hostutils._conn_cimv2.query.return_value = [disk]
|
||||
(total_memory, free_memory) = self._hostutils.get_volume_info(
|
||||
mock.sentinel.FAKE_DRIVE)
|
||||
|
||||
self.assertEqual(self._FAKE_DISK_SIZE, total_memory)
|
||||
self.assertEqual(self._FAKE_DISK_FREE, free_memory)
|
||||
|
||||
def test_check_min_windows_version_true(self):
|
||||
self._test_check_min_windows_version(self._FAKE_VERSION_GOOD, True)
|
||||
|
||||
def test_check_min_windows_version_false(self):
|
||||
self._test_check_min_windows_version(self._FAKE_VERSION_BAD, False)
|
||||
|
||||
def _test_check_min_windows_version(self, version, expected):
|
||||
os = mock.MagicMock()
|
||||
os.Version = version
|
||||
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [os]
|
||||
hostutils.HostUtils._windows_version = None
|
||||
self.assertEqual(expected,
|
||||
self._hostutils.check_min_windows_version(6, 2))
|
||||
|
||||
def test_get_windows_version(self):
|
||||
os = mock.MagicMock()
|
||||
os.Version = self._FAKE_VERSION_GOOD
|
||||
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [os]
|
||||
hostutils.HostUtils._windows_version = None
|
||||
self.assertEqual(self._FAKE_VERSION_GOOD,
|
||||
self._hostutils.get_windows_version())
|
||||
|
||||
@mock.patch('socket.gethostname')
|
||||
@mock.patch('os_win._utils.get_ips')
|
||||
def test_get_local_ips(self, mock_get_ips, mock_gethostname):
|
||||
local_ips = self._hostutils.get_local_ips()
|
||||
|
||||
self.assertEqual(mock_get_ips.return_value, local_ips)
|
||||
mock_gethostname.assert_called_once_with()
|
||||
mock_get_ips.assert_called_once_with(mock_gethostname.return_value)
|
||||
|
||||
def _test_host_power_action(self, action):
|
||||
fake_win32 = mock.MagicMock()
|
||||
fake_win32.Win32Shutdown = mock.MagicMock()
|
||||
|
||||
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [
|
||||
fake_win32]
|
||||
|
||||
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
|
||||
self._hostutils.host_power_action(action)
|
||||
fake_win32.Win32Shutdown.assert_called_with(
|
||||
self._hostutils._HOST_FORCED_SHUTDOWN)
|
||||
elif action == constants.HOST_POWER_ACTION_REBOOT:
|
||||
self._hostutils.host_power_action(action)
|
||||
fake_win32.Win32Shutdown.assert_called_with(
|
||||
self._hostutils._HOST_FORCED_REBOOT)
|
||||
else:
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._hostutils.host_power_action, action)
|
||||
|
||||
def test_host_shutdown(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_SHUTDOWN)
|
||||
|
||||
def test_host_reboot(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_REBOOT)
|
||||
|
||||
def test_host_startup(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_STARTUP)
|
||||
|
||||
def test_get_supported_vm_types_2012_r2(self):
|
||||
with mock.patch.object(self._hostutils,
|
||||
'check_min_windows_version') as mock_check_win:
|
||||
mock_check_win.return_value = True
|
||||
result = self._hostutils.get_supported_vm_types()
|
||||
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1,
|
||||
constants.IMAGE_PROP_VM_GEN_2], result)
|
||||
|
||||
def test_get_supported_vm_types(self):
|
||||
with mock.patch.object(self._hostutils,
|
||||
'check_min_windows_version') as mock_check_win:
|
||||
mock_check_win.return_value = False
|
||||
result = self._hostutils.get_supported_vm_types()
|
||||
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1], result)
|
||||
|
||||
def test_check_server_feature(self):
|
||||
mock_sv_feature_cls = self._hostutils._conn_cimv2.Win32_ServerFeature
|
||||
mock_sv_feature_cls.return_value = [mock.sentinel.sv_feature]
|
||||
|
||||
feature_enabled = self._hostutils.check_server_feature(
|
||||
mock.sentinel.feature_id)
|
||||
self.assertTrue(feature_enabled)
|
||||
|
||||
mock_sv_feature_cls.assert_called_once_with(
|
||||
ID=mock.sentinel.feature_id)
|
||||
|
||||
def _check_get_numa_nodes_missing_info(self):
|
||||
numa_node = mock.MagicMock()
|
||||
self._hostutils._conn.Msvm_NumaNode.return_value = [
|
||||
numa_node, numa_node]
|
||||
|
||||
nodes_info = self._hostutils.get_numa_nodes()
|
||||
self.assertEqual([], nodes_info)
|
||||
|
||||
@mock.patch.object(hostutils.HostUtils, '_get_numa_memory_info')
|
||||
def test_get_numa_nodes_missing_memory_info(self, mock_get_memory_info):
|
||||
mock_get_memory_info.return_value = None
|
||||
self._check_get_numa_nodes_missing_info()
|
||||
|
||||
@mock.patch.object(hostutils.HostUtils, '_get_numa_cpu_info')
|
||||
@mock.patch.object(hostutils.HostUtils, '_get_numa_memory_info')
|
||||
def test_get_numa_nodes_missing_cpu_info(self, mock_get_memory_info,
|
||||
mock_get_cpu_info):
|
||||
mock_get_cpu_info.return_value = None
|
||||
self._check_get_numa_nodes_missing_info()
|
||||
|
||||
@mock.patch.object(hostutils.HostUtils, '_get_numa_cpu_info')
|
||||
@mock.patch.object(hostutils.HostUtils, '_get_numa_memory_info')
|
||||
def test_get_numa_nodes(self, mock_get_memory_info, mock_get_cpu_info):
|
||||
numa_memory = mock_get_memory_info.return_value
|
||||
host_cpu = mock.MagicMock(DeviceID=self._DEVICE_ID)
|
||||
mock_get_cpu_info.return_value = [host_cpu]
|
||||
numa_node = mock.MagicMock(NodeID=self._NODE_ID)
|
||||
self._hostutils._conn.Msvm_NumaNode.return_value = [
|
||||
numa_node, numa_node]
|
||||
|
||||
nodes_info = self._hostutils.get_numa_nodes()
|
||||
|
||||
expected_info = {
|
||||
'id': self._DEVICE_ID.split('\\')[-1],
|
||||
'memory': numa_memory.NumberOfBlocks,
|
||||
'memory_usage': numa_node.CurrentlyConsumableMemoryBlocks,
|
||||
'cpuset': set([self._DEVICE_ID.split('\\')[-1]]),
|
||||
'cpu_usage': 0,
|
||||
}
|
||||
|
||||
self.assertEqual([expected_info, expected_info], nodes_info)
|
||||
|
||||
def test_get_numa_memory_info(self):
|
||||
system_memory = mock.MagicMock()
|
||||
system_memory.path_.return_value = 'fake_wmi_obj_path'
|
||||
numa_node_memory = mock.MagicMock()
|
||||
numa_node_memory.path_.return_value = 'fake_wmi_obj_path1'
|
||||
numa_node_assoc = [system_memory]
|
||||
memory_info = self._hostutils._get_numa_memory_info(
|
||||
numa_node_assoc, [system_memory, numa_node_memory])
|
||||
|
||||
self.assertEqual(system_memory, memory_info)
|
||||
|
||||
def test_get_numa_memory_info_not_found(self):
|
||||
other = mock.MagicMock()
|
||||
memory_info = self._hostutils._get_numa_memory_info([], [other])
|
||||
|
||||
self.assertIsNone(memory_info)
|
||||
|
||||
def test_get_numa_cpu_info(self):
|
||||
host_cpu = mock.MagicMock()
|
||||
host_cpu.path_.return_value = 'fake_wmi_obj_path'
|
||||
vm_cpu = mock.MagicMock()
|
||||
vm_cpu.path_.return_value = 'fake_wmi_obj_path1'
|
||||
numa_node_assoc = [host_cpu]
|
||||
cpu_info = self._hostutils._get_numa_cpu_info(numa_node_assoc,
|
||||
[host_cpu, vm_cpu])
|
||||
|
||||
self.assertEqual([host_cpu], cpu_info)
|
||||
|
||||
def test_get_numa_cpu_info_not_found(self):
|
||||
other = mock.MagicMock()
|
||||
cpu_info = self._hostutils._get_numa_cpu_info([], [other])
|
||||
|
||||
self.assertEqual([], cpu_info)
|
||||
|
||||
def test_get_remotefx_gpu_info(self):
|
||||
fake_gpu = mock.MagicMock()
|
||||
fake_gpu.Name = mock.sentinel.Fake_gpu_name
|
||||
fake_gpu.TotalVideoMemory = mock.sentinel.Fake_gpu_total_memory
|
||||
fake_gpu.AvailableVideoMemory = mock.sentinel.Fake_gpu_available_memory
|
||||
fake_gpu.DirectXVersion = mock.sentinel.Fake_gpu_directx
|
||||
fake_gpu.DriverVersion = mock.sentinel.Fake_gpu_driver_version
|
||||
|
||||
mock_phys_3d_proc = (
|
||||
self._hostutils._conn.Msvm_Physical3dGraphicsProcessor)
|
||||
mock_phys_3d_proc.return_value = [fake_gpu]
|
||||
|
||||
return_gpus = self._hostutils.get_remotefx_gpu_info()
|
||||
self.assertEqual(mock.sentinel.Fake_gpu_name, return_gpus[0]['name'])
|
||||
self.assertEqual(mock.sentinel.Fake_gpu_driver_version,
|
||||
return_gpus[0]['driver_version'])
|
||||
self.assertEqual(mock.sentinel.Fake_gpu_total_memory,
|
||||
return_gpus[0]['total_video_ram'])
|
||||
self.assertEqual(mock.sentinel.Fake_gpu_available_memory,
|
||||
return_gpus[0]['available_video_ram'])
|
||||
self.assertEqual(mock.sentinel.Fake_gpu_directx,
|
||||
return_gpus[0]['directx_version'])
|
||||
|
||||
def _set_verify_host_remotefx_capability_mocks(self, isGpuCapable=True,
|
||||
isSlatCapable=True):
|
||||
s3d_video_pool = self._hostutils._conn.Msvm_Synth3dVideoPool()[0]
|
||||
s3d_video_pool.IsGpuCapable = isGpuCapable
|
||||
s3d_video_pool.IsSlatCapable = isSlatCapable
|
||||
|
||||
def test_verify_host_remotefx_capability_unsupported_gpu(self):
|
||||
self._set_verify_host_remotefx_capability_mocks(isGpuCapable=False)
|
||||
self.assertRaises(exceptions.HyperVRemoteFXException,
|
||||
self._hostutils.verify_host_remotefx_capability)
|
||||
|
||||
def test_verify_host_remotefx_capability_no_slat(self):
|
||||
self._set_verify_host_remotefx_capability_mocks(isSlatCapable=False)
|
||||
self.assertRaises(exceptions.HyperVRemoteFXException,
|
||||
self._hostutils.verify_host_remotefx_capability)
|
||||
|
||||
def test_verify_host_remotefx_capability(self):
|
||||
self._set_verify_host_remotefx_capability_mocks()
|
||||
self._hostutils.verify_host_remotefx_capability()
|
||||
|
||||
def test_supports_nested_virtualization(self):
|
||||
self.assertFalse(self._hostutils.supports_nested_virtualization())
|
||||
|
||||
def test_get_pci_passthrough_devices(self):
|
||||
self.assertEqual([], self._hostutils.get_pci_passthrough_devices())
|
@ -1,135 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 re
|
||||
|
||||
import mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import hostutils10
|
||||
|
||||
|
||||
class HostUtils10TestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V HostUtils10 class."""
|
||||
|
||||
def setUp(self):
|
||||
super(HostUtils10TestCase, self).setUp()
|
||||
self._hostutils = hostutils10.HostUtils10()
|
||||
self._hostutils._conn_hgs_attr = mock.MagicMock()
|
||||
self._hostutils._conn_attr = mock.MagicMock()
|
||||
self._hostutils._conn_cimv2 = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(hostutils10.HostUtils10, '_get_wmi_conn')
|
||||
def test_conn_hgs(self, mock_get_wmi_conn):
|
||||
self._hostutils._conn_hgs_attr = None
|
||||
self.assertEqual(mock_get_wmi_conn.return_value,
|
||||
self._hostutils._conn_hgs)
|
||||
|
||||
mock_get_wmi_conn.assert_called_once_with(
|
||||
self._hostutils._HGS_NAMESPACE % self._hostutils._host)
|
||||
|
||||
@mock.patch.object(hostutils10.HostUtils10, '_get_wmi_conn')
|
||||
def test_conn_hgs_no_namespace(self, mock_get_wmi_conn):
|
||||
self._hostutils._conn_hgs_attr = None
|
||||
|
||||
mock_get_wmi_conn.side_effect = [exceptions.OSWinException]
|
||||
self.assertRaises(exceptions.OSWinException,
|
||||
lambda: self._hostutils._conn_hgs)
|
||||
mock_get_wmi_conn.assert_called_once_with(
|
||||
self._hostutils._HGS_NAMESPACE % self._hostutils._host)
|
||||
|
||||
def _test_is_host_guarded(self, return_code=0, is_host_guarded=True):
|
||||
hgs_config = self._hostutils._conn_hgs.MSFT_HgsClientConfiguration
|
||||
hgs_config.Get.return_value = (return_code,
|
||||
mock.MagicMock
|
||||
(IsHostGuarded=is_host_guarded))
|
||||
expected_result = is_host_guarded and not return_code
|
||||
|
||||
result = self._hostutils.is_host_guarded()
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_is_guarded_host_config_error(self):
|
||||
self._test_is_host_guarded(return_code=mock.sentinel.return_code)
|
||||
|
||||
def test_is_guarded_host(self):
|
||||
self._test_is_host_guarded()
|
||||
|
||||
def test_is_not_guarded_host(self):
|
||||
self._test_is_host_guarded(is_host_guarded=False)
|
||||
|
||||
def test_supports_nested_virtualization(self):
|
||||
self.assertTrue(self._hostutils.supports_nested_virtualization())
|
||||
|
||||
@mock.patch.object(hostutils10.HostUtils10, '_get_pci_device_address')
|
||||
def test_get_pci_passthrough_devices(self, mock_get_pci_device_address):
|
||||
mock_pci_dev = mock.MagicMock(
|
||||
DeviceInstancePath='PCIP\\VEN_15B3&DEV_1007&SUBSYS_001815B3')
|
||||
self._hostutils._conn.Msvm_PciExpress.return_value = [mock_pci_dev] * 3
|
||||
mock_get_pci_device_address.side_effect = [
|
||||
None, mock.sentinel.address, mock.sentinel.address]
|
||||
|
||||
pci_devices = self._hostutils.get_pci_passthrough_devices()
|
||||
|
||||
expected_pci_dev = {
|
||||
'address': mock.sentinel.address,
|
||||
'vendor_id': '15B3',
|
||||
'product_id': '1007',
|
||||
'dev_id': mock_pci_dev.DeviceID}
|
||||
self.assertEqual([expected_pci_dev], pci_devices)
|
||||
self._hostutils._conn.Msvm_PciExpress.assert_called_once_with()
|
||||
mock_get_pci_device_address.has_calls(
|
||||
[mock.call(mock_pci_dev.DeviceInstancePath)] * 3)
|
||||
|
||||
def _check_get_pci_device_address_None(self, return_code=0):
|
||||
pnp_device = mock.MagicMock()
|
||||
pnp_device.GetDeviceProperties.return_value = (
|
||||
return_code, [mock.MagicMock()])
|
||||
self._hostutils._conn_cimv2.Win32_PnPEntity.return_value = [pnp_device]
|
||||
|
||||
pci_dev_address = self._hostutils._get_pci_device_address(
|
||||
mock.sentinel.pci_device_path)
|
||||
self.assertIsNone(pci_dev_address)
|
||||
|
||||
def test_get_pci_device_address_error(self):
|
||||
self._check_get_pci_device_address_None(return_code=1)
|
||||
|
||||
def test_get_pci_device_address_exception(self):
|
||||
self._check_get_pci_device_address_None()
|
||||
|
||||
def test_get_pci_device_address(self):
|
||||
pnp_device = mock.MagicMock()
|
||||
pnp_device_properties = [
|
||||
mock.MagicMock(KeyName='DEVPKEY_Device_LocationInfo',
|
||||
Data="bus 2, domain 4, function 0"),
|
||||
mock.MagicMock(KeyName='DEVPKEY_Device_Address',
|
||||
Data=0)]
|
||||
pnp_device.GetDeviceProperties.return_value = (
|
||||
0, pnp_device_properties)
|
||||
self._hostutils._conn_cimv2.Win32_PnPEntity.return_value = [pnp_device]
|
||||
|
||||
result = self._hostutils._get_pci_device_address(
|
||||
mock.sentinel.device_instance_path)
|
||||
|
||||
pnp_props = {prop.KeyName: prop.Data for prop in pnp_device_properties}
|
||||
location_info = pnp_props['DEVPKEY_Device_LocationInfo']
|
||||
slot = pnp_props['DEVPKEY_Device_Address']
|
||||
[bus, domain, function] = re.findall(r'\b\d+\b', location_info)
|
||||
expected_result = "%04x:%02x:%02x.%1x" % (
|
||||
int(domain), int(bus), int(slot), int(function))
|
||||
|
||||
self.assertEqual(expected_result, result)
|
||||
self._hostutils._conn_cimv2.Win32_PnPEntity.assert_called_once_with(
|
||||
DeviceID=mock.sentinel.device_instance_path)
|
@ -1,296 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import jobutils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class JobUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V JobUtils class."""
|
||||
|
||||
_FAKE_RET_VAL = 0
|
||||
|
||||
_FAKE_JOB_STATUS_BAD = -1
|
||||
_FAKE_JOB_DESCRIPTION = "fake_job_description"
|
||||
_FAKE_JOB_PATH = 'fake_job_path'
|
||||
_FAKE_ERROR = "fake_error"
|
||||
_FAKE_ELAPSED_TIME = 0
|
||||
|
||||
def setUp(self):
|
||||
super(JobUtilsTestCase, self).setUp()
|
||||
self.jobutils = jobutils.JobUtils()
|
||||
self.jobutils._conn_attr = mock.MagicMock()
|
||||
|
||||
@mock.patch.object(jobutils.JobUtils, '_wait_for_job')
|
||||
def test_check_ret_val_started(self, mock_wait_for_job):
|
||||
self.jobutils.check_ret_val(constants.WMI_JOB_STATUS_STARTED,
|
||||
mock.sentinel.job_path)
|
||||
mock_wait_for_job.assert_called_once_with(mock.sentinel.job_path)
|
||||
|
||||
@mock.patch.object(jobutils.JobUtils, '_wait_for_job')
|
||||
def test_check_ret_val_ok(self, mock_wait_for_job):
|
||||
self.jobutils.check_ret_val(self._FAKE_RET_VAL,
|
||||
mock.sentinel.job_path)
|
||||
self.assertFalse(mock_wait_for_job.called)
|
||||
|
||||
def test_check_ret_val_exception(self):
|
||||
self.assertRaises(exceptions.WMIJobFailed,
|
||||
self.jobutils.check_ret_val,
|
||||
mock.sentinel.ret_val_bad,
|
||||
mock.sentinel.job_path)
|
||||
|
||||
def test_wait_for_job_ok(self):
|
||||
mock_job = self._prepare_wait_for_job(
|
||||
constants.JOB_STATE_COMPLETED_WITH_WARNINGS)
|
||||
job = self.jobutils._wait_for_job(self._FAKE_JOB_PATH)
|
||||
self.assertEqual(mock_job, job)
|
||||
|
||||
def test_wait_for_job_error_state(self):
|
||||
self._prepare_wait_for_job(
|
||||
constants.JOB_STATE_TERMINATED)
|
||||
self.assertRaises(exceptions.WMIJobFailed,
|
||||
self.jobutils._wait_for_job,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_wait_for_job_error_code(self):
|
||||
self._prepare_wait_for_job(
|
||||
constants.JOB_STATE_COMPLETED_WITH_WARNINGS,
|
||||
error_code=1)
|
||||
self.assertRaises(exceptions.WMIJobFailed,
|
||||
self.jobutils._wait_for_job,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_get_pending_jobs(self):
|
||||
mock_killed_job = mock.Mock(JobState=constants.JOB_STATE_KILLED)
|
||||
mock_running_job = mock.Mock(JobState=constants.WMI_JOB_STATE_RUNNING)
|
||||
mock_error_st_job = mock.Mock(JobState=constants.JOB_STATE_EXCEPTION)
|
||||
mappings = [mock.Mock(AffectingElement=None),
|
||||
mock.Mock(AffectingElement=mock_killed_job),
|
||||
mock.Mock(AffectingElement=mock_running_job),
|
||||
mock.Mock(AffectingElement=mock_error_st_job)]
|
||||
self.jobutils._conn.Msvm_AffectedJobElement.return_value = mappings
|
||||
|
||||
mock_affected_element = mock.Mock()
|
||||
|
||||
expected_pending_jobs = [mock_running_job]
|
||||
pending_jobs = self.jobutils._get_pending_jobs_affecting_element(
|
||||
mock_affected_element)
|
||||
self.assertEqual(expected_pending_jobs, pending_jobs)
|
||||
|
||||
self.jobutils._conn.Msvm_AffectedJobElement.assert_called_once_with(
|
||||
AffectedElement=mock_affected_element.path_.return_value)
|
||||
|
||||
@mock.patch.object(jobutils._utils, '_is_not_found_exc')
|
||||
def test_get_pending_jobs_ignored(self, mock_is_not_found_exc):
|
||||
mock_not_found_mapping = mock.MagicMock()
|
||||
type(mock_not_found_mapping).AffectingElement = mock.PropertyMock(
|
||||
side_effect=exceptions.x_wmi)
|
||||
self.jobutils._conn.Msvm_AffectedJobElement.return_value = [
|
||||
mock_not_found_mapping]
|
||||
|
||||
pending_jobs = self.jobutils._get_pending_jobs_affecting_element(
|
||||
mock.MagicMock())
|
||||
self.assertEqual([], pending_jobs)
|
||||
|
||||
@mock.patch.object(jobutils._utils, '_is_not_found_exc')
|
||||
def test_get_pending_jobs_reraised(self, mock_is_not_found_exc):
|
||||
mock_is_not_found_exc.return_value = False
|
||||
mock_not_found_mapping = mock.MagicMock()
|
||||
type(mock_not_found_mapping).AffectingElement = mock.PropertyMock(
|
||||
side_effect=exceptions.x_wmi)
|
||||
self.jobutils._conn.Msvm_AffectedJobElement.return_value = [
|
||||
mock_not_found_mapping]
|
||||
|
||||
self.assertRaises(exceptions.x_wmi,
|
||||
self.jobutils._get_pending_jobs_affecting_element,
|
||||
mock.MagicMock())
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch.object(jobutils.JobUtils,
|
||||
'_get_pending_jobs_affecting_element')
|
||||
def test_stop_jobs_helper(self, jobs_ended, mock_get_pending_jobs):
|
||||
mock_job1 = mock.Mock(Cancellable=True)
|
||||
mock_job2 = mock.Mock(Cancellable=True)
|
||||
mock_job3 = mock.Mock(Cancellable=False)
|
||||
|
||||
pending_jobs = [mock_job1, mock_job2, mock_job3]
|
||||
mock_get_pending_jobs.side_effect = (
|
||||
pending_jobs,
|
||||
pending_jobs if not jobs_ended else [])
|
||||
|
||||
mock_job1.RequestStateChange.side_effect = (
|
||||
test_base.FakeWMIExc(hresult=jobutils._utils._WBEM_E_NOT_FOUND))
|
||||
mock_job2.RequestStateChange.side_effect = (
|
||||
test_base.FakeWMIExc(hresult=mock.sentinel.hresult))
|
||||
|
||||
if jobs_ended:
|
||||
self.jobutils._stop_jobs(mock.sentinel.vm)
|
||||
else:
|
||||
self.assertRaises(exceptions.JobTerminateFailed,
|
||||
self.jobutils._stop_jobs,
|
||||
mock.sentinel.vm)
|
||||
|
||||
mock_get_pending_jobs.assert_has_calls(
|
||||
[mock.call(mock.sentinel.vm)] * 2)
|
||||
|
||||
mock_job1.RequestStateChange.assert_called_once_with(
|
||||
self.jobutils._KILL_JOB_STATE_CHANGE_REQUEST)
|
||||
mock_job2.RequestStateChange.assert_called_once_with(
|
||||
self.jobutils._KILL_JOB_STATE_CHANGE_REQUEST)
|
||||
self.assertFalse(mock_job3.RequestStateqqChange.called)
|
||||
|
||||
@mock.patch.object(jobutils.JobUtils, '_stop_jobs')
|
||||
def test_stop_jobs(self, mock_stop_jobs_helper):
|
||||
fake_timeout = 1
|
||||
self.jobutils.stop_jobs(mock.sentinel.element, fake_timeout)
|
||||
mock_stop_jobs_helper.assert_called_once_with(mock.sentinel.element)
|
||||
|
||||
def test_is_job_completed_true(self):
|
||||
job = mock.MagicMock(JobState=constants.WMI_JOB_STATE_COMPLETED)
|
||||
|
||||
self.assertTrue(self.jobutils._is_job_completed(job))
|
||||
|
||||
def test_is_job_completed_false(self):
|
||||
job = mock.MagicMock(JobState=constants.WMI_JOB_STATE_RUNNING)
|
||||
|
||||
self.assertFalse(self.jobutils._is_job_completed(job))
|
||||
|
||||
def _prepare_wait_for_job(self, state=_FAKE_JOB_STATUS_BAD,
|
||||
error_code=0):
|
||||
mock_job = mock.MagicMock()
|
||||
mock_job.JobState = state
|
||||
mock_job.ErrorCode = error_code
|
||||
mock_job.Description = self._FAKE_JOB_DESCRIPTION
|
||||
mock_job.ElapsedTime = self._FAKE_ELAPSED_TIME
|
||||
|
||||
wmi_patcher = mock.patch.object(jobutils.JobUtils, '_get_wmi_obj')
|
||||
mock_wmi = wmi_patcher.start()
|
||||
self.addCleanup(wmi_patcher.stop)
|
||||
mock_wmi.return_value = mock_job
|
||||
return mock_job
|
||||
|
||||
def test_modify_virt_resource(self):
|
||||
side_effect = [
|
||||
(self._FAKE_JOB_PATH, mock.MagicMock(), self._FAKE_RET_VAL)]
|
||||
self._check_modify_virt_resource_max_retries(side_effect=side_effect)
|
||||
|
||||
def test_modify_virt_resource_max_retries_exception(self):
|
||||
side_effect = exceptions.HyperVException('expected failure.')
|
||||
self._check_modify_virt_resource_max_retries(
|
||||
side_effect=side_effect, num_calls=6, expected_fail=True)
|
||||
|
||||
def test_modify_virt_resource_max_retries(self):
|
||||
side_effect = [exceptions.HyperVException('expected failure.')] * 5 + [
|
||||
(self._FAKE_JOB_PATH, mock.MagicMock(), self._FAKE_RET_VAL)]
|
||||
self._check_modify_virt_resource_max_retries(side_effect=side_effect,
|
||||
num_calls=5)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
def _check_modify_virt_resource_max_retries(
|
||||
self, mock_sleep, side_effect, num_calls=1, expected_fail=False):
|
||||
mock_svc = mock.MagicMock()
|
||||
self.jobutils._vs_man_svc_attr = mock_svc
|
||||
mock_svc.ModifyResourceSettings.side_effect = side_effect
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.GetText_.return_value = mock.sentinel.res_data
|
||||
|
||||
if expected_fail:
|
||||
self.assertRaises(exceptions.HyperVException,
|
||||
self.jobutils.modify_virt_resource,
|
||||
mock_res_setting_data)
|
||||
else:
|
||||
self.jobutils.modify_virt_resource(mock_res_setting_data)
|
||||
|
||||
mock_calls = [
|
||||
mock.call(ResourceSettings=[mock.sentinel.res_data])] * num_calls
|
||||
mock_svc.ModifyResourceSettings.has_calls(mock_calls)
|
||||
mock_sleep.has_calls(mock.call(1) * num_calls)
|
||||
|
||||
def test_add_virt_resource(self):
|
||||
self._test_virt_method('AddResourceSettings', 3, 'add_virt_resource',
|
||||
True, mock.sentinel.vm_path,
|
||||
[mock.sentinel.res_data])
|
||||
|
||||
def test_remove_virt_resource(self):
|
||||
self._test_virt_method('RemoveResourceSettings', 2,
|
||||
'remove_virt_resource', False,
|
||||
ResourceSettings=[mock.sentinel.res_path])
|
||||
|
||||
def test_add_virt_feature(self):
|
||||
self._test_virt_method('AddFeatureSettings', 3, 'add_virt_feature',
|
||||
True, mock.sentinel.vm_path,
|
||||
[mock.sentinel.res_data])
|
||||
|
||||
def test_remove_virt_feature(self):
|
||||
self._test_virt_method('RemoveFeatureSettings', 2,
|
||||
'remove_virt_feature', False,
|
||||
FeatureSettings=[mock.sentinel.res_path])
|
||||
|
||||
def _test_virt_method(self, vsms_method_name, return_count,
|
||||
utils_method_name, with_mock_vm, *args, **kwargs):
|
||||
mock_svc = mock.MagicMock()
|
||||
self.jobutils._vs_man_svc_attr = mock_svc
|
||||
vsms_method = getattr(mock_svc, vsms_method_name)
|
||||
mock_rsd = self._mock_vsms_method(vsms_method, return_count)
|
||||
if with_mock_vm:
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_vm.path_.return_value = mock.sentinel.vm_path
|
||||
getattr(self.jobutils, utils_method_name)(mock_rsd, mock_vm)
|
||||
else:
|
||||
getattr(self.jobutils, utils_method_name)(mock_rsd)
|
||||
|
||||
if args:
|
||||
vsms_method.assert_called_once_with(*args)
|
||||
else:
|
||||
vsms_method.assert_called_once_with(**kwargs)
|
||||
|
||||
def _mock_vsms_method(self, vsms_method, return_count):
|
||||
args = None
|
||||
if return_count == 3:
|
||||
args = (
|
||||
mock.sentinel.job_path, mock.MagicMock(), self._FAKE_RET_VAL)
|
||||
else:
|
||||
args = (mock.sentinel.job_path, self._FAKE_RET_VAL)
|
||||
|
||||
vsms_method.return_value = args
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.GetText_.return_value = mock.sentinel.res_data
|
||||
mock_res_setting_data.path_.return_value = mock.sentinel.res_path
|
||||
|
||||
self.jobutils.check_ret_val = mock.MagicMock()
|
||||
|
||||
return mock_res_setting_data
|
||||
|
||||
@mock.patch.object(jobutils.JobUtils, 'check_ret_val')
|
||||
def test_remove_multiple_virt_resources_not_found(self, mock_check_ret):
|
||||
excepinfo = [None] * 5 + [jobutils._utils._WBEM_E_NOT_FOUND]
|
||||
mock_check_ret.side_effect = exceptions.x_wmi(
|
||||
'expected error', com_error=mock.Mock(excepinfo=excepinfo))
|
||||
vsms_method = self.jobutils._vs_man_svc.RemoveResourceSettings
|
||||
vsms_method.return_value = (mock.sentinel.job, mock.sentinel.ret_val)
|
||||
mock_virt_res = mock.Mock()
|
||||
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
self.jobutils.remove_virt_resource, mock_virt_res)
|
||||
|
||||
vsms_method.assert_called_once_with(
|
||||
ResourceSettings=[mock_virt_res.path_.return_value])
|
||||
mock_check_ret.assert_called_once_with(mock.sentinel.ret_val,
|
||||
mock.sentinel.job)
|
@ -1,350 +0,0 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 ctypes
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import pathutils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi.libs import advapi32 as advapi32_def
|
||||
|
||||
|
||||
class PathUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
"""Unit tests for the Hyper-V PathUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(PathUtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._pathutils = pathutils.PathUtils()
|
||||
self._pathutils._win32_utils = mock.Mock()
|
||||
self._pathutils._acl_utils = mock.Mock()
|
||||
self._mock_run = self._pathutils._win32_utils.run_and_check_output
|
||||
self._acl_utils = self._pathutils._acl_utils
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._ctypes = mock.Mock()
|
||||
self._wintypes = mock.Mock()
|
||||
|
||||
self._wintypes.BOOL = lambda x: (x, 'BOOL')
|
||||
self._ctypes.c_wchar_p = lambda x: (x, "c_wchar_p")
|
||||
self._ctypes.pointer = lambda x: (x, 'pointer')
|
||||
|
||||
self._ctypes_patcher = mock.patch.object(
|
||||
pathutils, 'ctypes', new=self._ctypes)
|
||||
self._ctypes_patcher.start()
|
||||
|
||||
mock.patch.multiple(pathutils,
|
||||
wintypes=self._wintypes,
|
||||
kernel32=mock.DEFAULT,
|
||||
create=True).start()
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'copy')
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
@mock.patch.object(os, 'listdir')
|
||||
@mock.patch.object(pathutils.PathUtils, 'check_create_dir')
|
||||
def test_copy_folder_files(self, mock_check_create_dir, mock_listdir,
|
||||
mock_isfile, mock_copy):
|
||||
src_dir = 'src'
|
||||
dest_dir = 'dest'
|
||||
fname = 'tmp_file.txt'
|
||||
subdir = 'tmp_folder'
|
||||
src_fname = os.path.join(src_dir, fname)
|
||||
dest_fname = os.path.join(dest_dir, fname)
|
||||
|
||||
# making sure src_subdir is not copied.
|
||||
mock_listdir.return_value = [fname, subdir]
|
||||
mock_isfile.side_effect = [True, False]
|
||||
|
||||
self._pathutils.copy_folder_files(src_dir, dest_dir)
|
||||
|
||||
mock_check_create_dir.assert_called_once_with(dest_dir)
|
||||
mock_copy.assert_called_once_with(src_fname, dest_fname)
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'rename')
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
@mock.patch.object(os, 'listdir')
|
||||
def test_move_folder_files(self, mock_listdir, mock_isfile, mock_rename):
|
||||
src_dir = 'src'
|
||||
dest_dir = 'dest'
|
||||
fname = 'tmp_file.txt'
|
||||
subdir = 'tmp_folder'
|
||||
src_fname = os.path.join(src_dir, fname)
|
||||
dest_fname = os.path.join(dest_dir, fname)
|
||||
|
||||
# making sure src_subdir is not moved.
|
||||
mock_listdir.return_value = [fname, subdir]
|
||||
mock_isfile.side_effect = [True, False]
|
||||
|
||||
self._pathutils.move_folder_files(src_dir, dest_dir)
|
||||
mock_rename.assert_called_once_with(src_fname, dest_fname)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch.object(pathutils.shutil, 'rmtree')
|
||||
def test_rmtree(self, mock_rmtree, mock_sleep):
|
||||
exc = exceptions.WindowsError()
|
||||
exc.winerror = w_const.ERROR_DIR_IS_NOT_EMPTY
|
||||
mock_rmtree.side_effect = [exc] * 5 + [None]
|
||||
|
||||
self._pathutils.rmtree(mock.sentinel.FAKE_PATH)
|
||||
|
||||
mock_rmtree.assert_has_calls([mock.call(mock.sentinel.FAKE_PATH)] * 6)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch.object(pathutils.shutil, 'rmtree')
|
||||
def _check_rmtree(self, mock_rmtree, mock_sleep, side_effect):
|
||||
mock_rmtree.side_effect = side_effect
|
||||
self.assertRaises(exceptions.OSWinException, self._pathutils.rmtree,
|
||||
mock.sentinel.FAKE_PATH)
|
||||
|
||||
def test_rmtree_unexpected(self):
|
||||
self._check_rmtree(side_effect=exceptions.WindowsError)
|
||||
|
||||
def test_rmtree_exceeded(self):
|
||||
exc = exceptions.WindowsError()
|
||||
exc.winerror = w_const.ERROR_DIR_IS_NOT_EMPTY
|
||||
self._check_rmtree(side_effect=[exc] * 6)
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'makedirs')
|
||||
@mock.patch.object(pathutils.PathUtils, 'exists')
|
||||
def test_check_create_dir(self, mock_exists, mock_makedirs):
|
||||
fake_dir = 'dir'
|
||||
mock_exists.return_value = False
|
||||
self._pathutils.check_create_dir(fake_dir)
|
||||
|
||||
mock_exists.assert_called_once_with(fake_dir)
|
||||
mock_makedirs.assert_called_once_with(fake_dir)
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'rmtree')
|
||||
@mock.patch.object(pathutils.PathUtils, 'exists')
|
||||
def test_check_remove_dir(self, mock_exists, mock_rmtree):
|
||||
fake_dir = 'dir'
|
||||
self._pathutils.check_remove_dir(fake_dir)
|
||||
|
||||
mock_exists.assert_called_once_with(fake_dir)
|
||||
mock_rmtree.assert_called_once_with(fake_dir)
|
||||
|
||||
@mock.patch('os.path.isdir')
|
||||
@mock.patch('os.path.islink')
|
||||
def _test_check_symlink(self, mock_is_symlink, mock_is_dir,
|
||||
is_symlink=True, python_version=(2, 7),
|
||||
is_dir=True):
|
||||
fake_path = r'c:\\fake_path'
|
||||
if is_symlink:
|
||||
f_attr = 0x400
|
||||
else:
|
||||
f_attr = 0x80
|
||||
|
||||
mock_is_dir.return_value = is_dir
|
||||
mock_is_symlink.return_value = is_symlink
|
||||
self._mock_run.return_value = f_attr
|
||||
|
||||
with mock.patch('sys.version_info', python_version):
|
||||
ret_value = self._pathutils.is_symlink(fake_path)
|
||||
|
||||
if python_version >= (3, 2):
|
||||
mock_is_symlink.assert_called_once_with(fake_path)
|
||||
else:
|
||||
self._mock_run.assert_called_once_with(
|
||||
pathutils.kernel32.GetFileAttributesW,
|
||||
fake_path,
|
||||
error_ret_vals=[w_const.INVALID_FILE_ATTRIBUTES],
|
||||
kernel32_lib_func=True)
|
||||
|
||||
self.assertEqual(is_symlink, ret_value)
|
||||
|
||||
def test_is_symlink(self):
|
||||
self._test_check_symlink()
|
||||
|
||||
def test_is_not_symlink(self):
|
||||
self._test_check_symlink(is_symlink=False)
|
||||
|
||||
def test_is_symlink_python_gt_3_2(self):
|
||||
self._test_check_symlink(python_version=(3, 3))
|
||||
|
||||
def test_create_sym_link(self):
|
||||
tg_is_dir = False
|
||||
self._pathutils.create_sym_link(mock.sentinel.path,
|
||||
mock.sentinel.target,
|
||||
target_is_dir=tg_is_dir)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
pathutils.kernel32.CreateSymbolicLinkW,
|
||||
mock.sentinel.path,
|
||||
mock.sentinel.target,
|
||||
tg_is_dir,
|
||||
kernel32_lib_func=True)
|
||||
|
||||
@mock.patch('os.path.isdir')
|
||||
def _test_copy(self, mock_isdir, dest_isdir=False):
|
||||
mock_isdir.return_value = dest_isdir
|
||||
fail_if_exists = False
|
||||
|
||||
fake_src = r'fake_src_fname'
|
||||
fake_dest = r'fake_dest'
|
||||
|
||||
expected_dest = (os.path.join(fake_dest, fake_src)
|
||||
if dest_isdir else fake_dest)
|
||||
|
||||
self._pathutils.copy(fake_src, fake_dest,
|
||||
fail_if_exists=fail_if_exists)
|
||||
|
||||
self._mock_run.assert_called_once_with(
|
||||
pathutils.kernel32.CopyFileW,
|
||||
self._ctypes.c_wchar_p(fake_src),
|
||||
self._ctypes.c_wchar_p(expected_dest),
|
||||
self._wintypes.BOOL(fail_if_exists),
|
||||
kernel32_lib_func=True)
|
||||
|
||||
def test_copy_dest_is_fpath(self):
|
||||
self._test_copy()
|
||||
|
||||
def test_copy_dest_is_dir(self):
|
||||
self._test_copy(dest_isdir=True)
|
||||
|
||||
@mock.patch('os.path.isdir')
|
||||
def test_copy_exc(self, mock_isdir):
|
||||
mock_isdir.return_value = False
|
||||
self._mock_run.side_effect = exceptions.Win32Exception(
|
||||
func_name='mock_copy',
|
||||
error_code='fake_error_code',
|
||||
error_message='fake_error_msg')
|
||||
self.assertRaises(IOError,
|
||||
self._pathutils.copy,
|
||||
mock.sentinel.src,
|
||||
mock.sentinel.dest)
|
||||
|
||||
@mock.patch('os.close')
|
||||
@mock.patch('tempfile.mkstemp')
|
||||
def test_create_temporary_file(self, mock_mkstemp, mock_close):
|
||||
fd = mock.sentinel.file_descriptor
|
||||
path = mock.sentinel.absolute_pathname
|
||||
mock_mkstemp.return_value = (fd, path)
|
||||
|
||||
output = self._pathutils.create_temporary_file(
|
||||
suffix=mock.sentinel.suffix)
|
||||
|
||||
self.assertEqual(path, output)
|
||||
mock_close.assert_called_once_with(fd)
|
||||
mock_mkstemp.assert_called_once_with(suffix=mock.sentinel.suffix)
|
||||
|
||||
@mock.patch('oslo_utils.fileutils.delete_if_exists')
|
||||
def test_temporary_file(self, mock_delete):
|
||||
self._pathutils.create_temporary_file = mock.MagicMock()
|
||||
self._pathutils.create_temporary_file.return_value = (
|
||||
mock.sentinel.temporary_file)
|
||||
with self._pathutils.temporary_file() as tmp_file:
|
||||
self.assertEqual(mock.sentinel.temporary_file, tmp_file)
|
||||
self.assertFalse(mock_delete.called)
|
||||
mock_delete.assert_called_once_with(mock.sentinel.temporary_file)
|
||||
|
||||
@mock.patch.object(shutil, 'copytree')
|
||||
@mock.patch('os.path.abspath')
|
||||
def test_copy_dir(self, mock_abspath, mock_copytree):
|
||||
mock_abspath.side_effect = [mock.sentinel.src, mock.sentinel.dest]
|
||||
self._pathutils.copy_dir(mock.sentinel.src, mock.sentinel.dest)
|
||||
|
||||
mock_abspath.has_calls(
|
||||
[mock.call(mock.sentinel.src), mock.call(mock.sentinel.dest)])
|
||||
mock_copytree.assert_called_once_with(mock.sentinel.src,
|
||||
mock.sentinel.dest)
|
||||
|
||||
def test_add_acl_rule(self):
|
||||
# We raise an expected exception in order to
|
||||
# easily verify the resource cleanup.
|
||||
raised_exc = exceptions.OSWinException
|
||||
self._ctypes_patcher.stop()
|
||||
|
||||
fake_trustee = 'FAKEDOMAIN\\FakeUser'
|
||||
mock_sec_info = dict(pp_sec_desc=mock.Mock(),
|
||||
pp_dacl=mock.Mock())
|
||||
self._acl_utils.get_named_security_info.return_value = mock_sec_info
|
||||
self._acl_utils.set_named_security_info.side_effect = raised_exc
|
||||
pp_new_dacl = self._acl_utils.set_entries_in_acl.return_value
|
||||
|
||||
self.assertRaises(raised_exc,
|
||||
self._pathutils.add_acl_rule,
|
||||
path=mock.sentinel.path,
|
||||
trustee_name=fake_trustee,
|
||||
access_rights=constants.ACE_GENERIC_READ,
|
||||
access_mode=constants.ACE_GRANT_ACCESS,
|
||||
inheritance_flags=constants.ACE_OBJECT_INHERIT)
|
||||
|
||||
self._acl_utils.get_named_security_info.assert_called_once_with(
|
||||
obj_name=mock.sentinel.path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION)
|
||||
self._acl_utils.set_entries_in_acl.assert_called_once_with(
|
||||
entry_count=1,
|
||||
p_explicit_entry_list=mock.ANY,
|
||||
p_old_acl=mock_sec_info['pp_dacl'].contents)
|
||||
self._acl_utils.set_named_security_info.assert_called_once_with(
|
||||
obj_name=mock.sentinel.path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION,
|
||||
p_dacl=pp_new_dacl.contents)
|
||||
|
||||
p_access = self._acl_utils.set_entries_in_acl.call_args_list[0][1][
|
||||
'p_explicit_entry_list']
|
||||
access = ctypes.cast(
|
||||
p_access,
|
||||
ctypes.POINTER(advapi32_def.EXPLICIT_ACCESS)).contents
|
||||
|
||||
self.assertEqual(constants.ACE_GENERIC_READ,
|
||||
access.grfAccessPermissions)
|
||||
self.assertEqual(constants.ACE_GRANT_ACCESS,
|
||||
access.grfAccessMode)
|
||||
self.assertEqual(constants.ACE_OBJECT_INHERIT,
|
||||
access.grfInheritance)
|
||||
self.assertEqual(w_const.TRUSTEE_IS_NAME,
|
||||
access.Trustee.TrusteeForm)
|
||||
self.assertEqual(fake_trustee,
|
||||
access.Trustee.pstrName)
|
||||
|
||||
self._pathutils._win32_utils.local_free.assert_has_calls(
|
||||
[mock.call(pointer)
|
||||
for pointer in [mock_sec_info['pp_sec_desc'].contents,
|
||||
pp_new_dacl.contents]])
|
||||
|
||||
def test_copy_acls(self):
|
||||
raised_exc = exceptions.OSWinException
|
||||
|
||||
mock_sec_info = dict(pp_sec_desc=mock.Mock(),
|
||||
pp_dacl=mock.Mock())
|
||||
self._acl_utils.get_named_security_info.return_value = mock_sec_info
|
||||
self._acl_utils.set_named_security_info.side_effect = raised_exc
|
||||
|
||||
self.assertRaises(raised_exc,
|
||||
self._pathutils.copy_acls,
|
||||
mock.sentinel.src,
|
||||
mock.sentinel.dest)
|
||||
|
||||
self._acl_utils.get_named_security_info.assert_called_once_with(
|
||||
obj_name=mock.sentinel.src,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION)
|
||||
self._acl_utils.set_named_security_info.assert_called_once_with(
|
||||
obj_name=mock.sentinel.dest,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION,
|
||||
p_dacl=mock_sec_info['pp_dacl'].contents)
|
||||
|
||||
self._pathutils._win32_utils.local_free.assert_called_once_with(
|
||||
mock_sec_info['pp_sec_desc'].contents)
|
@ -1,210 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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 ddt
|
||||
import mock
|
||||
from oslotest import base
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class Win32UtilsTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(Win32UtilsTestCase, self).setUp()
|
||||
self._setup_lib_mocks()
|
||||
|
||||
self._win32_utils = win32utils.Win32Utils()
|
||||
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _setup_lib_mocks(self):
|
||||
self._ctypes = mock.Mock()
|
||||
# This is used in order to easily make assertions on the variables
|
||||
# passed by reference.
|
||||
self._ctypes.byref = lambda x: (x, "byref")
|
||||
|
||||
self._ctypes_patcher = mock.patch.multiple(
|
||||
win32utils, ctypes=self._ctypes)
|
||||
self._ctypes_patcher.start()
|
||||
|
||||
mock.patch.multiple(win32utils,
|
||||
kernel32=mock.DEFAULT,
|
||||
wintypes=mock.DEFAULT,
|
||||
create=True).start()
|
||||
|
||||
@mock.patch.object(win32utils.Win32Utils, 'get_error_message')
|
||||
@mock.patch.object(win32utils.Win32Utils, 'get_last_error')
|
||||
def _test_run_and_check_output(self, mock_get_last_err, mock_get_err_msg,
|
||||
ret_val=0, expected_exc=None,
|
||||
**kwargs):
|
||||
self._ctypes_patcher.stop()
|
||||
|
||||
mock_func = mock.Mock()
|
||||
mock_func.return_value = ret_val
|
||||
|
||||
if expected_exc:
|
||||
self.assertRaises(expected_exc,
|
||||
self._win32_utils.run_and_check_output,
|
||||
mock_func,
|
||||
mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg,
|
||||
**kwargs)
|
||||
else:
|
||||
actual_ret_val = self._win32_utils.run_and_check_output(
|
||||
mock_func,
|
||||
mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg,
|
||||
**kwargs)
|
||||
self.assertEqual(ret_val, actual_ret_val)
|
||||
|
||||
mock_func.assert_called_once_with(mock.sentinel.arg,
|
||||
kwarg=mock.sentinel.kwarg)
|
||||
|
||||
return mock_get_last_err, mock_get_err_msg
|
||||
|
||||
def test_run_and_check_output(self):
|
||||
self._test_run_and_check_output()
|
||||
|
||||
def test_run_and_check_output_fail_on_nonzero_ret_val(self):
|
||||
ret_val = 1
|
||||
|
||||
(mock_get_last_err,
|
||||
mock_get_err_msg) = self._test_run_and_check_output(
|
||||
ret_val=ret_val,
|
||||
expected_exc=exceptions.VHDWin32APIException,
|
||||
failure_exc=exceptions.VHDWin32APIException)
|
||||
|
||||
mock_get_err_msg.assert_called_once_with(ret_val)
|
||||
|
||||
def test_run_and_check_output_explicit_error_ret_vals(self):
|
||||
ret_val = 1
|
||||
error_ret_vals = [ret_val]
|
||||
|
||||
(mock_get_last_err,
|
||||
mock_get_err_msg) = self._test_run_and_check_output(
|
||||
ret_val=ret_val,
|
||||
error_ret_vals=error_ret_vals,
|
||||
ret_val_is_err_code=False,
|
||||
expected_exc=exceptions.Win32Exception)
|
||||
|
||||
mock_get_err_msg.assert_called_once_with(
|
||||
win32utils.ctypes.c_ulong(mock_get_last_err).value)
|
||||
|
||||
def test_run_and_check_output_ignored_error(self):
|
||||
ret_val = 1
|
||||
ignored_err_codes = [ret_val]
|
||||
|
||||
self._test_run_and_check_output(ret_val=ret_val,
|
||||
ignored_error_codes=ignored_err_codes)
|
||||
|
||||
def test_run_and_check_output_kernel32_lib_func(self):
|
||||
ret_val = 0
|
||||
self._test_run_and_check_output(ret_val=ret_val,
|
||||
expected_exc=exceptions.Win32Exception,
|
||||
kernel32_lib_func=True)
|
||||
|
||||
def test_run_and_check_output_with_err_msg_dict(self):
|
||||
self._ctypes_patcher.stop()
|
||||
|
||||
err_code = 1
|
||||
err_msg = 'fake_err_msg'
|
||||
err_msg_dict = {err_code: err_msg}
|
||||
|
||||
mock_func = mock.Mock()
|
||||
mock_func.return_value = err_code
|
||||
|
||||
try:
|
||||
self._win32_utils.run_and_check_output(mock_func,
|
||||
mock.sentinel.arg,
|
||||
error_msg_src=err_msg_dict)
|
||||
except Exception as ex:
|
||||
self.assertIsInstance(ex, exceptions.Win32Exception)
|
||||
self.assertIn(err_msg, ex.message)
|
||||
|
||||
@mock.patch.object(win32utils.Win32Utils, '_run_and_check_output')
|
||||
def test_run_and_check_output_eventlet_nb_mode_disabled(self, mock_helper):
|
||||
self._win32_utils.run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
eventlet_nonblocking_mode=False)
|
||||
mock_helper.assert_called_once_with(mock.sentinel.func,
|
||||
mock.sentinel.arg)
|
||||
|
||||
@mock.patch.object(_utils, 'avoid_blocking_call')
|
||||
def test_run_and_check_output_eventlet_nb_mode_enabled(self, mock_helper):
|
||||
self._win32_utils.run_and_check_output(
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg,
|
||||
eventlet_nonblocking_mode=True)
|
||||
mock_helper.assert_called_once_with(
|
||||
self._win32_utils._run_and_check_output,
|
||||
mock.sentinel.func,
|
||||
mock.sentinel.arg)
|
||||
|
||||
def test_get_error_message(self):
|
||||
err_msg = self._win32_utils.get_error_message(mock.sentinel.err_code)
|
||||
|
||||
fake_msg_buff = win32utils.ctypes.c_char_p.return_value
|
||||
|
||||
expected_flags = (w_const.FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
w_const.FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
w_const.FORMAT_MESSAGE_IGNORE_INSERTS)
|
||||
|
||||
win32utils.kernel32.FormatMessageA.assert_called_once_with(
|
||||
expected_flags, None, mock.sentinel.err_code, 0,
|
||||
win32utils.ctypes.byref(fake_msg_buff), 0, None)
|
||||
self.assertEqual(fake_msg_buff.value, err_msg)
|
||||
|
||||
def test_get_last_error(self):
|
||||
last_err = self._win32_utils.get_last_error()
|
||||
|
||||
self.assertEqual(win32utils.kernel32.GetLastError.return_value,
|
||||
last_err)
|
||||
win32utils.kernel32.SetLastError.assert_called_once_with(0)
|
||||
|
||||
def test_hresult_to_err_code(self):
|
||||
# This could differ based on the error source.
|
||||
# Only the last 2 bytes of the hresult the error code.
|
||||
fake_file_exists_hres = -0x7ff8ffb0
|
||||
file_exists_err_code = 0x50
|
||||
|
||||
ret_val = self._win32_utils.hresult_to_err_code(fake_file_exists_hres)
|
||||
self.assertEqual(file_exists_err_code, ret_val)
|
||||
|
||||
@mock.patch.object(win32utils._utils, 'get_com_error_hresult')
|
||||
@mock.patch.object(win32utils.Win32Utils, 'hresult_to_err_code')
|
||||
def test_get_com_err_code(self, mock_hres_to_err_code, mock_get_hresult):
|
||||
ret_val = self._win32_utils.get_com_err_code(mock.sentinel.com_err)
|
||||
|
||||
self.assertEqual(mock_hres_to_err_code.return_value, ret_val)
|
||||
mock_get_hresult.assert_called_once_with(mock.sentinel.com_err)
|
||||
mock_hres_to_err_code.assert_called_once_with(
|
||||
mock_get_hresult.return_value)
|
||||
|
||||
@ddt.data(0, 1)
|
||||
@mock.patch.object(win32utils.LOG, 'exception')
|
||||
def test_local_free(self, ret_val, mock_log_exc):
|
||||
mock_localfree = win32utils.kernel32.LocalFree
|
||||
mock_localfree.return_value = ret_val
|
||||
|
||||
self._win32_utils.local_free(mock.sentinel.handle)
|
||||
|
||||
mock_localfree.assert_any_call(mock.sentinel.handle)
|
||||
self.assertEqual(bool(ret_val), mock_log_exc.called)
|
@ -1,52 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 mock
|
||||
|
||||
from os_win import exceptions
|
||||
from os_win.tests.unit import test_base
|
||||
from os_win.utils import _wqlutils
|
||||
|
||||
|
||||
class WqlUtilsTestCase(test_base.OsWinBaseTestCase):
|
||||
def _test_get_element_associated_class(self, fields=None):
|
||||
mock_conn = mock.MagicMock()
|
||||
_wqlutils.get_element_associated_class(
|
||||
mock_conn, mock.sentinel.class_name,
|
||||
element_instance_id=mock.sentinel.instance_id,
|
||||
fields=fields)
|
||||
|
||||
expected_fields = ", ".join(fields) if fields else '*'
|
||||
expected_query = (
|
||||
"SELECT %(expected_fields)s FROM %(class_name)s "
|
||||
"WHERE InstanceID LIKE '%(instance_id)s%%'" %
|
||||
{'expected_fields': expected_fields,
|
||||
'class_name': mock.sentinel.class_name,
|
||||
'instance_id': mock.sentinel.instance_id})
|
||||
mock_conn.query.assert_called_once_with(expected_query)
|
||||
|
||||
def test_get_element_associated_class(self):
|
||||
self._test_get_element_associated_class()
|
||||
|
||||
def test_get_element_associated_class_specific_fields(self):
|
||||
self._test_get_element_associated_class(
|
||||
fields=['field', 'another_field'])
|
||||
|
||||
def test_get_element_associated_class_invalid_element(self):
|
||||
self.assertRaises(
|
||||
exceptions.WqlException,
|
||||
_wqlutils.get_element_associated_class,
|
||||
mock.sentinel.conn,
|
||||
mock.sentinel.class_name)
|
@ -1,94 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ctypes
|
||||
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
|
||||
advapi32 = w_lib.get_shared_lib_handle(w_lib.ADVAPI32)
|
||||
|
||||
|
||||
class ACLUtils(object):
|
||||
def __init__(self):
|
||||
self._win32_utils = win32utils.Win32Utils()
|
||||
|
||||
@staticmethod
|
||||
def _get_void_pp():
|
||||
return ctypes.pointer(ctypes.c_void_p())
|
||||
|
||||
def get_named_security_info(self, obj_name, obj_type, security_info_flags):
|
||||
"""Retrieve object security information.
|
||||
|
||||
:param security_info_flags: specifies which information will
|
||||
be retrieved.
|
||||
:param ret_val: dict, containing pointers to the requested structures.
|
||||
Note that the returned security descriptor will have
|
||||
to be freed using LocalFree.
|
||||
Some requested information may not be present, in
|
||||
which case the according pointers will be NULL.
|
||||
"""
|
||||
sec_info = {}
|
||||
|
||||
if security_info_flags & w_const.OWNER_SECURITY_INFORMATION:
|
||||
sec_info['pp_sid_owner'] = self._get_void_pp()
|
||||
if security_info_flags & w_const.GROUP_SECURITY_INFORMATION:
|
||||
sec_info['pp_sid_group'] = self._get_void_pp()
|
||||
if security_info_flags & w_const.DACL_SECURITY_INFORMATION:
|
||||
sec_info['pp_dacl'] = self._get_void_pp()
|
||||
if security_info_flags & w_const.SACL_SECURITY_INFORMATION:
|
||||
sec_info['pp_sacl'] = self._get_void_pp()
|
||||
sec_info['pp_sec_desc'] = self._get_void_pp()
|
||||
|
||||
self._win32_utils.run_and_check_output(
|
||||
advapi32.GetNamedSecurityInfoW,
|
||||
ctypes.c_wchar_p(obj_name),
|
||||
obj_type,
|
||||
security_info_flags,
|
||||
sec_info.get('pp_sid_owner'),
|
||||
sec_info.get('pp_sid_group'),
|
||||
sec_info.get('pp_dacl'),
|
||||
sec_info.get('pp_sacl'),
|
||||
sec_info['pp_sec_desc'])
|
||||
|
||||
return sec_info
|
||||
|
||||
def set_entries_in_acl(self, entry_count, p_explicit_entry_list,
|
||||
p_old_acl):
|
||||
"""Merge new ACEs into an existing ACL, returning a new ACL."""
|
||||
pp_new_acl = self._get_void_pp()
|
||||
|
||||
self._win32_utils.run_and_check_output(
|
||||
advapi32.SetEntriesInAclW,
|
||||
entry_count,
|
||||
p_explicit_entry_list,
|
||||
p_old_acl,
|
||||
pp_new_acl)
|
||||
|
||||
return pp_new_acl
|
||||
|
||||
def set_named_security_info(self, obj_name, obj_type, security_info_flags,
|
||||
p_sid_owner=None, p_sid_group=None,
|
||||
p_dacl=None, p_sacl=None):
|
||||
self._win32_utils.run_and_check_output(
|
||||
advapi32.SetNamedSecurityInfoW,
|
||||
ctypes.c_wchar_p(obj_name),
|
||||
obj_type,
|
||||
security_info_flags,
|
||||
p_sid_owner,
|
||||
p_sid_group,
|
||||
p_dacl,
|
||||
p_sacl)
|
@ -1,44 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 os_win._i18n import _
|
||||
from os_win import exceptions
|
||||
|
||||
|
||||
def get_element_associated_class(conn, class_name, element_instance_id=None,
|
||||
element_uuid=None, fields=None):
|
||||
"""Returns the objects associated to an element as a list.
|
||||
|
||||
:param conn: connection to be used to execute the query
|
||||
:param class_name: object's class type name to be retrieved
|
||||
:param element_instance_id: element class InstanceID
|
||||
:param element_uuid: UUID of the element
|
||||
:param fields: specific class attributes to be retrieved
|
||||
"""
|
||||
if element_instance_id:
|
||||
instance_id = element_instance_id
|
||||
elif element_uuid:
|
||||
instance_id = "Microsoft:%s" % element_uuid
|
||||
else:
|
||||
err_msg = _("Could not get element associated class. Either element "
|
||||
"instance id or element uuid must be specified.")
|
||||
raise exceptions.WqlException(err_msg)
|
||||
fields = ", ".join(fields) if fields else "*"
|
||||
return conn.query(
|
||||
"SELECT %(fields)s FROM %(class_name)s WHERE InstanceID "
|
||||
"LIKE '%(instance_id)s%%'" % {
|
||||
'fields': fields,
|
||||
'class_name': class_name,
|
||||
'instance_id': instance_id})
|
@ -1,160 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base WMI utility class.
|
||||
"""
|
||||
|
||||
import imp
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import reflection
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseUtils(object):
|
||||
|
||||
_WMI_CONS = {}
|
||||
|
||||
def _get_wmi_obj(self, moniker, **kwargs):
|
||||
return wmi.WMI(moniker=moniker, **kwargs)
|
||||
|
||||
def _get_wmi_conn(self, moniker, **kwargs):
|
||||
if sys.platform != 'win32':
|
||||
return None
|
||||
if kwargs:
|
||||
return self._get_wmi_obj(moniker, **kwargs)
|
||||
if moniker in self._WMI_CONS:
|
||||
return self._WMI_CONS[moniker]
|
||||
|
||||
wmi_conn = self._get_wmi_obj(moniker)
|
||||
self._WMI_CONS[moniker] = wmi_conn
|
||||
return wmi_conn
|
||||
|
||||
|
||||
class BaseUtilsVirt(BaseUtils):
|
||||
|
||||
_wmi_namespace = '//%s/root/virtualization/v2'
|
||||
_os_version = None
|
||||
_old_wmi = None
|
||||
|
||||
def __init__(self, host='.'):
|
||||
self._vs_man_svc_attr = None
|
||||
self._host = host
|
||||
self._conn_attr = None
|
||||
self._compat_conn_attr = None
|
||||
|
||||
@property
|
||||
def _conn(self):
|
||||
if not self._conn_attr:
|
||||
self._conn_attr = self._get_wmi_conn(
|
||||
self._wmi_namespace % self._host)
|
||||
return self._conn_attr
|
||||
|
||||
@property
|
||||
def _compat_conn(self):
|
||||
if not self._compat_conn_attr:
|
||||
if not BaseUtilsVirt._os_version:
|
||||
# hostutils cannot be used for this, it would end up in
|
||||
# a circular import.
|
||||
os_version = wmi.WMI().Win32_OperatingSystem()[0].Version
|
||||
BaseUtilsVirt._os_version = list(
|
||||
map(int, os_version.split('.')))
|
||||
|
||||
if BaseUtilsVirt._os_version >= [6, 3]:
|
||||
self._compat_conn_attr = self._conn
|
||||
else:
|
||||
self._compat_conn_attr = self._get_wmi_compat_conn(
|
||||
moniker=self._wmi_namespace % self._host)
|
||||
|
||||
return self._compat_conn_attr
|
||||
|
||||
@property
|
||||
def _vs_man_svc(self):
|
||||
if self._vs_man_svc_attr:
|
||||
return self._vs_man_svc_attr
|
||||
|
||||
vs_man_svc = self._compat_conn.Msvm_VirtualSystemManagementService()[0]
|
||||
if BaseUtilsVirt._os_version >= [6, 3]:
|
||||
# NOTE(claudiub): caching this property on Windows / Hyper-V Server
|
||||
# 2012 (using the old WMI) can lead to memory leaks. PyMI doesn't
|
||||
# have those issues, so we can safely cache it.
|
||||
self._vs_man_svc_attr = vs_man_svc
|
||||
return vs_man_svc
|
||||
|
||||
def _get_wmi_compat_conn(self, moniker, **kwargs):
|
||||
# old WMI should be used on Windows / Hyper-V Server 2012 whenever
|
||||
# .GetText_ is used (e.g.: AddResourceSettings). PyMI's and WMI's
|
||||
# .GetText_ have different results.
|
||||
if not BaseUtilsVirt._old_wmi:
|
||||
old_wmi_path = "%s.py" % wmi.__path__[0]
|
||||
BaseUtilsVirt._old_wmi = imp.load_source('old_wmi', old_wmi_path)
|
||||
return BaseUtilsVirt._old_wmi.WMI(moniker=moniker, **kwargs)
|
||||
|
||||
def _get_wmi_obj(self, moniker, compatibility_mode=False, **kwargs):
|
||||
if not BaseUtilsVirt._os_version:
|
||||
# hostutils cannot be used for this, it would end up in
|
||||
# a circular import.
|
||||
os_version = wmi.WMI().Win32_OperatingSystem()[0].Version
|
||||
BaseUtilsVirt._os_version = list(map(int, os_version.split('.')))
|
||||
|
||||
if not compatibility_mode or BaseUtilsVirt._os_version >= [6, 3]:
|
||||
return wmi.WMI(moniker=moniker, **kwargs)
|
||||
return self._get_wmi_compat_conn(moniker=moniker, **kwargs)
|
||||
|
||||
|
||||
class SynchronizedMeta(type):
|
||||
"""Use an rlock to synchronize all class methods."""
|
||||
|
||||
def __init__(cls, cls_name, bases, attrs):
|
||||
super(SynchronizedMeta, cls).__init__(cls_name, bases, attrs)
|
||||
rlock = threading.RLock()
|
||||
|
||||
for attr_name in attrs:
|
||||
attr = getattr(cls, attr_name)
|
||||
if callable(attr):
|
||||
decorated = SynchronizedMeta._synchronize(
|
||||
attr, cls_name, rlock)
|
||||
setattr(cls, attr_name, decorated)
|
||||
|
||||
@staticmethod
|
||||
def _synchronize(func, cls_name, rlock):
|
||||
def wrapper(*args, **kwargs):
|
||||
f_qual_name = reflection.get_callable_name(func)
|
||||
|
||||
t_request = time.time()
|
||||
try:
|
||||
with rlock:
|
||||
t_acquire = time.time()
|
||||
LOG.debug("Method %(method_name)s acquired rlock. "
|
||||
"Waited %(time_wait)0.3fs",
|
||||
dict(method_name=f_qual_name,
|
||||
time_wait=t_acquire - t_request))
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
t_release = time.time()
|
||||
LOG.debug("Method %(method_name)s released rlock. "
|
||||
"Held %(time_held)0.3fs",
|
||||
dict(method_name=f_qual_name,
|
||||
time_held=t_release - t_acquire))
|
||||
return wrapper
|
@ -1,370 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 ctypes
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
from os_win.utils.winapi.libs import clusapi as clusapi_def
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
clusapi = w_lib.get_shared_lib_handle(w_lib.CLUSAPI)
|
||||
|
||||
|
||||
class ClusApiUtils(object):
|
||||
_open_handle_check_flags = dict(ret_val_is_err_code=False,
|
||||
error_on_nonzero_ret_val=False,
|
||||
error_ret_vals=[0, None])
|
||||
|
||||
def __init__(self):
|
||||
self._win32utils = win32utils.Win32Utils()
|
||||
|
||||
def _run_and_check_output(self, *args, **kwargs):
|
||||
kwargs['failure_exc'] = exceptions.ClusterWin32Exception
|
||||
return self._win32utils.run_and_check_output(*args, **kwargs)
|
||||
|
||||
def _dword_align(self, value):
|
||||
return (value + 3) & ~3
|
||||
|
||||
def _get_clusprop_value_struct(self, val_type):
|
||||
def _get_padding():
|
||||
# The cluster property entries must be 4B aligned.
|
||||
val_sz = ctypes.sizeof(val_type)
|
||||
return self._dword_align(val_sz) - val_sz
|
||||
|
||||
# For convenience, as opposed to the homonymous ClusAPI
|
||||
# structure, we add the actual value as well.
|
||||
class CLUSPROP_VALUE(ctypes.Structure):
|
||||
_fields_ = [('syntax', wintypes.DWORD),
|
||||
('length', wintypes.DWORD),
|
||||
('value', val_type),
|
||||
('_padding', ctypes.c_ubyte * _get_padding())]
|
||||
return CLUSPROP_VALUE
|
||||
|
||||
def get_property_list_entry(self, name, syntax, value):
|
||||
# The value argument must have a ctypes type.
|
||||
name_len = len(name) + 1
|
||||
val_sz = ctypes.sizeof(value)
|
||||
|
||||
class CLUSPROP_LIST_ENTRY(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('name', self._get_clusprop_value_struct(
|
||||
val_type=ctypes.c_wchar * name_len)),
|
||||
('value', self._get_clusprop_value_struct(
|
||||
val_type=ctypes.c_ubyte * val_sz)),
|
||||
('_endmark', wintypes.DWORD)
|
||||
]
|
||||
|
||||
entry = CLUSPROP_LIST_ENTRY()
|
||||
entry.name.syntax = w_const.CLUSPROP_SYNTAX_NAME
|
||||
entry.name.length = name_len * ctypes.sizeof(ctypes.c_wchar)
|
||||
entry.name.value = name
|
||||
|
||||
entry.value.syntax = syntax
|
||||
entry.value.length = val_sz
|
||||
entry.value.value[0:val_sz] = bytearray(value)
|
||||
|
||||
entry._endmark = w_const.CLUSPROP_SYNTAX_ENDMARK
|
||||
|
||||
return entry
|
||||
|
||||
def get_property_list(self, property_entries):
|
||||
prop_entries_sz = sum([ctypes.sizeof(entry)
|
||||
for entry in property_entries])
|
||||
|
||||
class CLUSPROP_LIST(ctypes.Structure):
|
||||
_fields_ = [('count', wintypes.DWORD),
|
||||
('entries_buff', ctypes.c_ubyte * prop_entries_sz)]
|
||||
|
||||
prop_list = CLUSPROP_LIST(count=len(property_entries))
|
||||
|
||||
pos = 0
|
||||
for prop_entry in property_entries:
|
||||
prop_entry_sz = ctypes.sizeof(prop_entry)
|
||||
prop_list.entries_buff[pos:prop_entry_sz + pos] = bytearray(
|
||||
prop_entry)
|
||||
pos += prop_entry_sz
|
||||
|
||||
return prop_list
|
||||
|
||||
def open_cluster(self, cluster_name=None):
|
||||
"""Returns a handle for the requested cluster.
|
||||
|
||||
:param cluster_name: (Optional) specifies the name of the cluster
|
||||
to be opened. If None, the cluster that the
|
||||
local node belongs to will be opened.
|
||||
"""
|
||||
p_clus_name = ctypes.c_wchar_p(cluster_name) if cluster_name else None
|
||||
handle = self._run_and_check_output(clusapi.OpenCluster,
|
||||
p_clus_name,
|
||||
**self._open_handle_check_flags)
|
||||
return handle
|
||||
|
||||
def open_cluster_group(self, cluster_handle, group_name):
|
||||
handle = self._run_and_check_output(clusapi.OpenClusterGroup,
|
||||
cluster_handle,
|
||||
ctypes.c_wchar_p(group_name),
|
||||
**self._open_handle_check_flags)
|
||||
return handle
|
||||
|
||||
def open_cluster_node(self, cluster_handle, node_name):
|
||||
handle = self._run_and_check_output(clusapi.OpenClusterNode,
|
||||
cluster_handle,
|
||||
ctypes.c_wchar_p(node_name),
|
||||
**self._open_handle_check_flags)
|
||||
return handle
|
||||
|
||||
def close_cluster(self, cluster_handle):
|
||||
# This function will always return 'True'. Closing the cluster
|
||||
# handle will also invalidate handles opened using it.
|
||||
clusapi.CloseCluster(cluster_handle)
|
||||
|
||||
def close_cluster_group(self, group_handle):
|
||||
# TODO(lpetrut): The following functions can fail, in which case
|
||||
# 'False' will be returned. We may want to handle this situation.
|
||||
clusapi.CloseClusterGroup(group_handle)
|
||||
|
||||
def close_cluster_node(self, node_handle):
|
||||
clusapi.CloseClusterNode(node_handle)
|
||||
|
||||
def cancel_cluster_group_operation(self, group_handle):
|
||||
"""Requests a pending move operation to be canceled.
|
||||
|
||||
This only applies to move operations requested by
|
||||
MoveClusterGroup(Ex), thus it will not apply to fail overs.
|
||||
|
||||
return: True if the cancel request completed successfuly,
|
||||
False if it's still in progress.
|
||||
"""
|
||||
ret_val = self._run_and_check_output(
|
||||
clusapi.CancelClusterGroupOperation,
|
||||
group_handle,
|
||||
0, # cancel flags (reserved for future use by MS)
|
||||
ignored_error_codes=[w_const.ERROR_IO_PENDING])
|
||||
|
||||
cancel_completed = ret_val != w_const.ERROR_IO_PENDING
|
||||
return cancel_completed
|
||||
|
||||
def move_cluster_group(self, group_handle, destination_node_handle,
|
||||
move_flags, property_list):
|
||||
prop_list_p = ctypes.byref(property_list) if property_list else None
|
||||
prop_list_sz = ctypes.sizeof(property_list) if property_list else 0
|
||||
|
||||
self._run_and_check_output(clusapi.MoveClusterGroupEx,
|
||||
group_handle,
|
||||
destination_node_handle,
|
||||
move_flags,
|
||||
prop_list_p,
|
||||
prop_list_sz,
|
||||
ignored_error_codes=[
|
||||
w_const.ERROR_IO_PENDING])
|
||||
|
||||
def get_cluster_group_state(self, group_handle):
|
||||
node_name_len = wintypes.DWORD(w_const.MAX_PATH)
|
||||
node_name_buff = (ctypes.c_wchar * node_name_len.value)()
|
||||
|
||||
group_state = self._run_and_check_output(
|
||||
clusapi.GetClusterGroupState,
|
||||
group_handle,
|
||||
node_name_buff,
|
||||
ctypes.byref(node_name_len),
|
||||
error_ret_vals=[constants.CLUSTER_GROUP_STATE_UNKNOWN],
|
||||
error_on_nonzero_ret_val=False,
|
||||
ret_val_is_err_code=False)
|
||||
|
||||
return {'state': group_state,
|
||||
'owner_node': node_name_buff.value}
|
||||
|
||||
def create_cluster_notify_port_v2(self, cluster_handle, notif_filters,
|
||||
notif_port_h=None, notif_key=None):
|
||||
"""Creates or updates a cluster notify port.
|
||||
|
||||
This allows us to subscribe to specific types of cluster events.
|
||||
|
||||
:param cluster_handle: an open cluster handle, for which we'll
|
||||
receive events. This handle must remain open
|
||||
while fetching events.
|
||||
:param notif_filters: an array of NOTIFY_FILTER_AND_TYPE structures,
|
||||
specifying the event types we're listening to.
|
||||
:param notif_port_h: an open cluster notify port handle, when adding
|
||||
new filters to an existing cluster notify port,
|
||||
or INVALID_HANDLE_VALUE when creating a new
|
||||
notify port.
|
||||
:param notif_key: a DWORD value that will be mapped to a specific
|
||||
event type. When fetching events, the cluster API
|
||||
will send us back a reference to the according
|
||||
notification key. For this reason, we must ensure
|
||||
that this variable will not be garbage collected
|
||||
while waiting for events.
|
||||
:return: the requested notify port handle,
|
||||
"""
|
||||
notif_port_h = notif_port_h or w_const.INVALID_HANDLE_VALUE
|
||||
notif_filters_len = (len(notif_filters)
|
||||
if isinstance(notif_filters, ctypes.Array)
|
||||
else 1)
|
||||
notif_key_p = (ctypes.byref(notif_key)
|
||||
if notif_key is not None else None)
|
||||
# If INVALID_HANDLE_VALUE is passed as the notification handle,
|
||||
# a new one will be created. Otherwise, new events are added to the
|
||||
# specified notification port.
|
||||
notif_port_h = self._run_and_check_output(
|
||||
clusapi.CreateClusterNotifyPortV2,
|
||||
notif_port_h,
|
||||
cluster_handle,
|
||||
ctypes.byref(notif_filters),
|
||||
ctypes.c_ulong(notif_filters_len),
|
||||
notif_key_p,
|
||||
**self._open_handle_check_flags)
|
||||
return notif_port_h
|
||||
|
||||
def close_cluster_notify_port(self, notif_port_h):
|
||||
# Always returns True.
|
||||
clusapi.CloseClusterNotifyPort(notif_port_h)
|
||||
|
||||
def get_cluster_notify_v2(self, notif_port_h, timeout_ms):
|
||||
filter_and_type = clusapi_def.NOTIFY_FILTER_AND_TYPE()
|
||||
obj_name_buff_sz = ctypes.c_ulong(w_const.MAX_PATH)
|
||||
notif_key_p = wintypes.PDWORD()
|
||||
buff_sz = ctypes.c_ulong(w_const.MAX_PATH)
|
||||
|
||||
# Event notification buffer. The notification format depends
|
||||
# on the event type and filter flags.
|
||||
buff = (wintypes.BYTE * buff_sz.value)()
|
||||
obj_name_buff = (ctypes.c_wchar * obj_name_buff_sz.value)()
|
||||
|
||||
def get_args(buff, obj_name_buff):
|
||||
return (clusapi.GetClusterNotifyV2,
|
||||
notif_port_h,
|
||||
ctypes.byref(notif_key_p),
|
||||
ctypes.byref(filter_and_type),
|
||||
buff,
|
||||
ctypes.byref(buff_sz),
|
||||
None, # object id
|
||||
None, # object id sz
|
||||
None, # parent id
|
||||
None, # parent id sz
|
||||
obj_name_buff,
|
||||
ctypes.byref(obj_name_buff_sz),
|
||||
None, # object type
|
||||
None, # object type sz
|
||||
timeout_ms)
|
||||
try:
|
||||
self._run_and_check_output(*get_args(buff, obj_name_buff))
|
||||
except exceptions.ClusterWin32Exception as ex:
|
||||
if ex.error_code == w_const.ERROR_MORE_DATA:
|
||||
# This function will specify the buffer sizes it needs using
|
||||
# the references we pass.
|
||||
buff = (wintypes.BYTE * buff_sz.value)()
|
||||
obj_name_buff = (ctypes.c_wchar * obj_name_buff_sz.value)()
|
||||
|
||||
self._run_and_check_output(*get_args(buff, obj_name_buff))
|
||||
else:
|
||||
raise
|
||||
|
||||
# We'll leverage notification key values instead of their addresses,
|
||||
# although this returns us the address we passed in when setting up
|
||||
# the notification port.
|
||||
notif_key = notif_key_p.contents.value
|
||||
event = {'cluster_object_name': obj_name_buff.value,
|
||||
'object_type': filter_and_type.dwObjectType,
|
||||
'filter_flags': filter_and_type.FilterFlags,
|
||||
'buff': buff,
|
||||
'buff_sz': buff_sz.value,
|
||||
'notif_key': notif_key}
|
||||
return event
|
||||
|
||||
def get_prop_list_entry_p(self, prop_list_p, prop_list_sz, property_name):
|
||||
# We may add a nice property list parser at some point.
|
||||
# ResUtilFindULargeIntegerProperty is also helpful for our use case
|
||||
# but it's available only starting with WS 2016.
|
||||
#
|
||||
# NOTE(lpetrut): in most cases, we're using 'byref' when passing
|
||||
# references to DLL functions. The issue is that those pointers
|
||||
# cannot be used directly, for which reason we have a cast here.
|
||||
prop_list_p = ctypes.cast(
|
||||
prop_list_p, ctypes.POINTER(ctypes.c_ubyte * prop_list_sz))
|
||||
wb_prop_name = bytearray(ctypes.create_unicode_buffer(property_name))
|
||||
|
||||
prop_list_addr = ctypes.addressof(prop_list_p.contents)
|
||||
prop_name_pos = bytearray(prop_list_p.contents).find(wb_prop_name)
|
||||
if prop_name_pos == -1:
|
||||
raise exceptions.ClusterPropertyListEntryNotFound(
|
||||
property_name=property_name)
|
||||
|
||||
prop_name_len_pos = prop_name_pos - ctypes.sizeof(wintypes.DWORD)
|
||||
prop_name_len_addr = prop_list_addr + prop_name_len_pos
|
||||
prop_name_len = self._dword_align(
|
||||
wintypes.DWORD.from_address(prop_name_len_addr).value)
|
||||
prop_addr = prop_name_len_addr + prop_name_len + ctypes.sizeof(
|
||||
wintypes.DWORD)
|
||||
if (prop_addr + ctypes.sizeof(wintypes.DWORD * 3) >
|
||||
prop_list_addr + prop_list_sz):
|
||||
raise exceptions.ClusterPropertyListParsingError()
|
||||
|
||||
prop_entry = {
|
||||
'syntax': wintypes.DWORD.from_address(prop_addr).value,
|
||||
'length': wintypes.DWORD.from_address(
|
||||
prop_addr + ctypes.sizeof(wintypes.DWORD)).value,
|
||||
'val_p': ctypes.c_void_p(prop_addr + 2 * ctypes.sizeof(
|
||||
wintypes.DWORD))
|
||||
}
|
||||
|
||||
return prop_entry
|
||||
|
||||
def cluster_group_control(self, group_handle, control_code,
|
||||
node_handle=None,
|
||||
in_buff_p=None, in_buff_sz=0):
|
||||
out_buff_sz = ctypes.c_ulong(w_const.MAX_PATH)
|
||||
out_buff = (ctypes.c_ubyte * out_buff_sz.value)()
|
||||
|
||||
def get_args(out_buff):
|
||||
return (clusapi.ClusterGroupControl,
|
||||
group_handle,
|
||||
node_handle,
|
||||
control_code,
|
||||
in_buff_p,
|
||||
in_buff_sz,
|
||||
out_buff,
|
||||
out_buff_sz,
|
||||
ctypes.byref(out_buff_sz))
|
||||
|
||||
try:
|
||||
self._run_and_check_output(*get_args(out_buff))
|
||||
except exceptions.ClusterWin32Exception as ex:
|
||||
if ex.error_code == w_const.ERROR_MORE_DATA:
|
||||
out_buff = (ctypes.c_ubyte * out_buff_sz.value)()
|
||||
self._run_and_check_output(*get_args(out_buff))
|
||||
else:
|
||||
raise
|
||||
|
||||
return out_buff, out_buff_sz.value
|
||||
|
||||
def get_cluster_group_status_info(self, prop_list_p, prop_list_sz):
|
||||
prop_entry = self.get_prop_list_entry_p(
|
||||
prop_list_p, prop_list_sz,
|
||||
w_const.CLUSREG_NAME_GRP_STATUS_INFORMATION)
|
||||
|
||||
if (prop_entry['length'] != ctypes.sizeof(ctypes.c_ulonglong) or
|
||||
prop_entry['syntax'] !=
|
||||
w_const.CLUSPROP_SYNTAX_LIST_VALUE_ULARGE_INTEGER):
|
||||
raise exceptions.ClusterPropertyListParsingError()
|
||||
|
||||
status_info_p = prop_entry['val_p']
|
||||
status_info = ctypes.c_ulonglong.from_address(
|
||||
status_info_p.value).value
|
||||
return status_info
|
@ -1,678 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Utility class for VM related operations on Hyper-V Clusters.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from eventlet import patcher
|
||||
from eventlet import tpool
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from six.moves import queue
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils.compute import _clusapi_utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi.libs import clusapi as clusapi_def
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClusterUtils(baseutils.BaseUtils):
|
||||
|
||||
_MSCLUSTER_NODE = 'MSCluster_Node'
|
||||
_MSCLUSTER_RES = 'MSCluster_Resource'
|
||||
|
||||
_VM_BASE_NAME = 'Virtual Machine %s'
|
||||
_VM_TYPE = 'Virtual Machine'
|
||||
_VM_GROUP_TYPE = 111
|
||||
|
||||
_MS_CLUSTER_NAMESPACE = '//%s/root/MSCluster'
|
||||
|
||||
_LIVE_MIGRATION_TYPE = 4
|
||||
_IGNORE_LOCKED = 1
|
||||
_DESTROY_GROUP = 1
|
||||
|
||||
_FAILBACK_WINDOW_MIN = 0
|
||||
_FAILBACK_WINDOW_MAX = 23
|
||||
|
||||
_WMI_EVENT_TIMEOUT_MS = 100
|
||||
_WMI_EVENT_CHECK_INTERVAL = 2
|
||||
|
||||
def __init__(self, host='.'):
|
||||
self._instance_name_regex = re.compile('Virtual Machine (.*)')
|
||||
self._clusapi_utils = _clusapi_utils.ClusApiUtils()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
self._init_hyperv_conn(host)
|
||||
self._watcher = self._get_failover_watcher()
|
||||
|
||||
def _init_hyperv_conn(self, host):
|
||||
try:
|
||||
self._conn_cluster = self._get_wmi_conn(
|
||||
self._MS_CLUSTER_NAMESPACE % host)
|
||||
self._cluster = self._conn_cluster.MSCluster_Cluster()[0]
|
||||
|
||||
# extract this node name from cluster's path
|
||||
path = self._cluster.path_()
|
||||
self._this_node = re.search(r'\\\\(.*)\\root', path,
|
||||
re.IGNORECASE).group(1)
|
||||
except AttributeError:
|
||||
raise exceptions.HyperVClusterException(
|
||||
_("Could not initialize cluster wmi connection."))
|
||||
|
||||
def _get_failover_watcher(self):
|
||||
raw_query = ("SELECT * FROM __InstanceModificationEvent "
|
||||
"WITHIN %(wmi_check_interv)s WHERE TargetInstance ISA "
|
||||
"'%(cluster_res)s' AND "
|
||||
"TargetInstance.Type='%(cluster_res_type)s' AND "
|
||||
"TargetInstance.OwnerNode != PreviousInstance.OwnerNode" %
|
||||
{'wmi_check_interv': self._WMI_EVENT_CHECK_INTERVAL,
|
||||
'cluster_res': self._MSCLUSTER_RES,
|
||||
'cluster_res_type': self._VM_TYPE})
|
||||
return self._conn_cluster.watch_for(raw_wql=raw_query)
|
||||
|
||||
def check_cluster_state(self):
|
||||
if len(self._get_cluster_nodes()) < 1:
|
||||
raise exceptions.HyperVClusterException(
|
||||
_("Not enough cluster nodes."))
|
||||
|
||||
def get_node_name(self):
|
||||
return self._this_node
|
||||
|
||||
def _get_cluster_nodes(self):
|
||||
cluster_assoc = self._conn_cluster.MSCluster_ClusterToNode(
|
||||
Antecedent=self._cluster.path_())
|
||||
return [x.Dependent for x in cluster_assoc]
|
||||
|
||||
def _get_vm_groups(self):
|
||||
assocs = self._conn_cluster.MSCluster_ClusterToResourceGroup(
|
||||
GroupComponent=self._cluster.path_())
|
||||
resources = [a.PartComponent for a in assocs]
|
||||
return (r for r in resources if
|
||||
hasattr(r, 'GroupType') and
|
||||
r.GroupType == self._VM_GROUP_TYPE)
|
||||
|
||||
def _lookup_vm_group_check(self, vm_name):
|
||||
vm = self._lookup_vm_group(vm_name)
|
||||
if not vm:
|
||||
raise exceptions.HyperVVMNotFoundException(vm_name=vm_name)
|
||||
return vm
|
||||
|
||||
def _lookup_vm_group(self, vm_name):
|
||||
return self._lookup_res(self._conn_cluster.MSCluster_ResourceGroup,
|
||||
vm_name)
|
||||
|
||||
def _lookup_vm_check(self, vm_name):
|
||||
vm = self._lookup_vm(vm_name)
|
||||
if not vm:
|
||||
raise exceptions.HyperVVMNotFoundException(vm_name=vm_name)
|
||||
return vm
|
||||
|
||||
def _lookup_vm(self, vm_name):
|
||||
vm_name = self._VM_BASE_NAME % vm_name
|
||||
return self._lookup_res(self._conn_cluster.MSCluster_Resource, vm_name)
|
||||
|
||||
def _lookup_res(self, resource_source, res_name):
|
||||
res = resource_source(Name=res_name)
|
||||
n = len(res)
|
||||
if n == 0:
|
||||
return None
|
||||
elif n > 1:
|
||||
raise exceptions.HyperVClusterException(
|
||||
_('Duplicate resource name %s found.') % res_name)
|
||||
else:
|
||||
return res[0]
|
||||
|
||||
def get_cluster_node_names(self):
|
||||
nodes = self._get_cluster_nodes()
|
||||
return [n.Name for n in nodes]
|
||||
|
||||
def get_vm_host(self, vm_name):
|
||||
return self._lookup_vm_group_check(vm_name).OwnerNode
|
||||
|
||||
def list_instances(self):
|
||||
return [r.Name for r in self._get_vm_groups()]
|
||||
|
||||
def list_instance_uuids(self):
|
||||
return [r.Id for r in self._get_vm_groups()]
|
||||
|
||||
def add_vm_to_cluster(self, vm_name, max_failover_count=1,
|
||||
failover_period=6, auto_failback=True):
|
||||
"""Adds the VM to the Hyper-V Cluster.
|
||||
|
||||
:param vm_name: The name of the VM to be added to the Hyper-V Cluster
|
||||
:param max_failover_count: The number of times the Hyper-V Cluster will
|
||||
try to failover the VM within the given failover period. If the VM
|
||||
will try to failover more than this number of the given
|
||||
failover_period, the VM will end up in a failed state.
|
||||
:param failover_period: The period (hours) over which the given
|
||||
max_failover_count failovers can occur. After this period expired,
|
||||
the failover count for the given VM is reset.
|
||||
:param auto_failback: boolean, whether the VM will be allowed to
|
||||
move back to its original host when it is available again.
|
||||
"""
|
||||
LOG.debug("Add vm to cluster called for vm %s" % vm_name)
|
||||
self._cluster.AddVirtualMachine(vm_name)
|
||||
|
||||
vm_group = self._lookup_vm_group_check(vm_name)
|
||||
vm_group.FailoverThreshold = max_failover_count
|
||||
vm_group.FailoverPeriod = failover_period
|
||||
vm_group.PersistentState = True
|
||||
vm_group.AutoFailbackType = int(bool(auto_failback))
|
||||
# set the earliest and latest time that the group can be moved
|
||||
# back to its preferred node. The unit is in hours.
|
||||
vm_group.FailbackWindowStart = self._FAILBACK_WINDOW_MIN
|
||||
vm_group.FailbackWindowEnd = self._FAILBACK_WINDOW_MAX
|
||||
vm_group.put()
|
||||
|
||||
def bring_online(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vm.BringOnline()
|
||||
|
||||
def take_offline(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vm.TakeOffline()
|
||||
|
||||
def delete(self, vm_name):
|
||||
vm = self._lookup_vm_group_check(vm_name)
|
||||
vm.DestroyGroup(self._DESTROY_GROUP)
|
||||
|
||||
def vm_exists(self, vm_name):
|
||||
return self._lookup_vm(vm_name) is not None
|
||||
|
||||
def live_migrate_vm(self, vm_name, new_host, timeout=None):
|
||||
self._migrate_vm(vm_name, new_host, self._LIVE_MIGRATION_TYPE,
|
||||
constants.CLUSTER_GROUP_ONLINE,
|
||||
timeout)
|
||||
|
||||
def _migrate_vm(self, vm_name, new_host, migration_type,
|
||||
exp_state_after_migr, timeout):
|
||||
syntax = w_const.CLUSPROP_SYNTAX_LIST_VALUE_DWORD
|
||||
migr_type = wintypes.DWORD(migration_type)
|
||||
|
||||
prop_entries = [
|
||||
self._clusapi_utils.get_property_list_entry(
|
||||
w_const.CLUS_RESTYPE_NAME_VM, syntax, migr_type),
|
||||
self._clusapi_utils.get_property_list_entry(
|
||||
w_const.CLUS_RESTYPE_NAME_VM_CONFIG, syntax, migr_type)
|
||||
]
|
||||
prop_list = self._clusapi_utils.get_property_list(prop_entries)
|
||||
|
||||
flags = (
|
||||
w_const.CLUSAPI_GROUP_MOVE_RETURN_TO_SOURCE_NODE_ON_ERROR |
|
||||
w_const.CLUSAPI_GROUP_MOVE_QUEUE_ENABLED |
|
||||
w_const.CLUSAPI_GROUP_MOVE_HIGH_PRIORITY_START)
|
||||
|
||||
cluster_handle = None
|
||||
group_handle = None
|
||||
dest_node_handle = None
|
||||
|
||||
try:
|
||||
cluster_handle = self._clusapi_utils.open_cluster()
|
||||
group_handle = self._clusapi_utils.open_cluster_group(
|
||||
cluster_handle, vm_name)
|
||||
dest_node_handle = self._clusapi_utils.open_cluster_node(
|
||||
cluster_handle, new_host)
|
||||
|
||||
with _ClusterGroupStateChangeListener(cluster_handle,
|
||||
vm_name) as listener:
|
||||
self._clusapi_utils.move_cluster_group(group_handle,
|
||||
dest_node_handle,
|
||||
flags,
|
||||
prop_list)
|
||||
try:
|
||||
self._wait_for_cluster_group_migration(
|
||||
listener,
|
||||
vm_name,
|
||||
group_handle,
|
||||
exp_state_after_migr,
|
||||
timeout)
|
||||
except exceptions.ClusterGroupMigrationTimeOut:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
self._cancel_cluster_group_migration(
|
||||
listener, vm_name, group_handle,
|
||||
exp_state_after_migr, timeout)
|
||||
|
||||
# This is rather unlikely to happen but we're
|
||||
# covering it out.
|
||||
try:
|
||||
self._validate_migration(group_handle,
|
||||
vm_name,
|
||||
exp_state_after_migr,
|
||||
new_host)
|
||||
LOG.warning(
|
||||
'Cluster group migration completed '
|
||||
'successfuly after cancel attempt. '
|
||||
'Suppressing timeout exception.')
|
||||
ctxt.reraise = False
|
||||
except exceptions.ClusterGroupMigrationFailed:
|
||||
pass
|
||||
else:
|
||||
self._validate_migration(group_handle,
|
||||
vm_name,
|
||||
exp_state_after_migr,
|
||||
new_host)
|
||||
finally:
|
||||
if group_handle:
|
||||
self._clusapi_utils.close_cluster_group(group_handle)
|
||||
if dest_node_handle:
|
||||
self._clusapi_utils.close_cluster_node(dest_node_handle)
|
||||
if cluster_handle:
|
||||
self._clusapi_utils.close_cluster(cluster_handle)
|
||||
|
||||
def _validate_migration(self, group_handle, group_name,
|
||||
expected_state, expected_node):
|
||||
state_info = self._clusapi_utils.get_cluster_group_state(group_handle)
|
||||
owner_node = state_info['owner_node']
|
||||
group_state = state_info['state']
|
||||
|
||||
if (expected_state != group_state or
|
||||
expected_node.lower() != owner_node.lower()):
|
||||
raise exceptions.ClusterGroupMigrationFailed(
|
||||
group_name=group_name,
|
||||
expected_state=expected_state,
|
||||
expected_node=expected_node,
|
||||
group_state=group_state,
|
||||
owner_node=owner_node)
|
||||
|
||||
def cancel_cluster_group_migration(self, group_name, expected_state,
|
||||
timeout=None):
|
||||
cluster_handle = None
|
||||
group_handle = None
|
||||
|
||||
try:
|
||||
cluster_handle = self._clusapi_utils.open_cluster()
|
||||
group_handle = self._clusapi_utils.open_cluster_group(
|
||||
cluster_handle, group_name)
|
||||
|
||||
with _ClusterGroupStateChangeListener(cluster_handle,
|
||||
group_name) as listener:
|
||||
self._cancel_cluster_group_migration(
|
||||
listener, group_name, group_handle,
|
||||
expected_state, timeout)
|
||||
finally:
|
||||
if group_handle:
|
||||
self._clusapi_utils.close_cluster_group(group_handle)
|
||||
if cluster_handle:
|
||||
self._clusapi_utils.close_cluster(cluster_handle)
|
||||
|
||||
def _cancel_cluster_group_migration(self, event_listener,
|
||||
group_name, group_handle,
|
||||
expected_state,
|
||||
timeout=None):
|
||||
LOG.info("Canceling cluster group '%s' migration", group_name)
|
||||
try:
|
||||
cancel_finished = (
|
||||
self._clusapi_utils.cancel_cluster_group_operation(
|
||||
group_handle))
|
||||
except exceptions.Win32Exception as ex:
|
||||
group_state_info = self._get_cluster_group_state(group_handle)
|
||||
migration_pending = self._is_migration_pending(
|
||||
group_state_info['state'],
|
||||
group_state_info['status_info'],
|
||||
expected_state)
|
||||
|
||||
if (ex.error_code == w_const.ERROR_INVALID_STATE and
|
||||
not migration_pending):
|
||||
LOG.debug('Ignoring group migration cancel error. '
|
||||
'No migration is pending.')
|
||||
cancel_finished = True
|
||||
else:
|
||||
raise
|
||||
|
||||
if not cancel_finished:
|
||||
LOG.debug("Waiting for group migration to be canceled.")
|
||||
try:
|
||||
self._wait_for_cluster_group_migration(
|
||||
event_listener, group_name, group_handle,
|
||||
expected_state,
|
||||
timeout=timeout)
|
||||
except Exception:
|
||||
LOG.exception("Failed to cancel cluster group migration.")
|
||||
raise exceptions.JobTerminateFailed()
|
||||
|
||||
LOG.info("Cluster group migration canceled.")
|
||||
|
||||
def _is_migration_queued(self, group_status_info):
|
||||
return bool(
|
||||
group_status_info &
|
||||
w_const.CLUSGRP_STATUS_WAITING_IN_QUEUE_FOR_MOVE)
|
||||
|
||||
def _is_migration_pending(self, group_state, group_status_info,
|
||||
expected_state):
|
||||
migration_pending = (
|
||||
group_state != expected_state or
|
||||
self._is_migration_queued(group_status_info))
|
||||
return migration_pending
|
||||
|
||||
def _wait_for_cluster_group_migration(self, event_listener,
|
||||
group_name, group_handle,
|
||||
expected_state,
|
||||
timeout=None):
|
||||
time_start = time.time()
|
||||
time_left = timeout if timeout else 'undefined'
|
||||
|
||||
group_state_info = self._get_cluster_group_state(group_handle)
|
||||
group_state = group_state_info['state']
|
||||
group_status_info = group_state_info['status_info']
|
||||
|
||||
migration_pending = self._is_migration_pending(
|
||||
group_state,
|
||||
group_status_info,
|
||||
expected_state)
|
||||
if not migration_pending:
|
||||
return
|
||||
|
||||
while not timeout or time_left > 0:
|
||||
time_elapsed = time.time() - time_start
|
||||
time_left = timeout - time_elapsed if timeout else 'undefined'
|
||||
|
||||
LOG.debug("Waiting for cluster group '%(group_name)s' "
|
||||
"migration to finish. "
|
||||
"Time left: %(time_left)s.",
|
||||
dict(group_name=group_name,
|
||||
time_left=time_left))
|
||||
|
||||
try:
|
||||
event = event_listener.get(time_left if timeout else None)
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
group_state = event.get('state', group_state)
|
||||
group_status_info = event.get('status_info', group_status_info)
|
||||
|
||||
migration_pending = self._is_migration_pending(group_state,
|
||||
group_status_info,
|
||||
expected_state)
|
||||
if not migration_pending:
|
||||
return
|
||||
|
||||
LOG.error("Cluster group migration timed out.")
|
||||
raise exceptions.ClusterGroupMigrationTimeOut(
|
||||
group_name=group_name,
|
||||
time_elapsed=time.time() - time_start)
|
||||
|
||||
def get_cluster_group_state_info(self, group_name):
|
||||
"""Gets cluster group state info.
|
||||
|
||||
:return: a dict containing the following keys:
|
||||
['state', 'migration_queued', 'owner_node']
|
||||
"""
|
||||
cluster_handle = None
|
||||
group_handle = None
|
||||
|
||||
try:
|
||||
cluster_handle = self._clusapi_utils.open_cluster()
|
||||
group_handle = self._clusapi_utils.open_cluster_group(
|
||||
cluster_handle, group_name)
|
||||
|
||||
state_info = self._get_cluster_group_state(group_handle)
|
||||
migration_queued = self._is_migration_queued(
|
||||
state_info['status_info'])
|
||||
|
||||
return dict(owner_node=state_info['owner_node'],
|
||||
state=state_info['state'],
|
||||
migration_queued=migration_queued)
|
||||
finally:
|
||||
if group_handle:
|
||||
self._clusapi_utils.close_cluster_group(group_handle)
|
||||
if cluster_handle:
|
||||
self._clusapi_utils.close_cluster(cluster_handle)
|
||||
|
||||
def _get_cluster_group_state(self, group_handle):
|
||||
state_info = self._clusapi_utils.get_cluster_group_state(group_handle)
|
||||
|
||||
buff, buff_sz = self._clusapi_utils.cluster_group_control(
|
||||
group_handle,
|
||||
w_const.CLUSCTL_GROUP_GET_RO_COMMON_PROPERTIES)
|
||||
status_info = self._clusapi_utils.get_cluster_group_status_info(
|
||||
ctypes.byref(buff), buff_sz)
|
||||
|
||||
state_info['status_info'] = status_info
|
||||
return state_info
|
||||
|
||||
def monitor_vm_failover(self, callback,
|
||||
event_timeout_ms=_WMI_EVENT_TIMEOUT_MS):
|
||||
"""Creates a monitor to check for new WMI MSCluster_Resource
|
||||
|
||||
events.
|
||||
|
||||
This method will poll the last _WMI_EVENT_CHECK_INTERVAL + 1
|
||||
seconds for new events and listens for _WMI_EVENT_TIMEOUT_MS
|
||||
milliseconds, since listening is a thread blocking action.
|
||||
|
||||
Any event object caught will then be processed.
|
||||
"""
|
||||
|
||||
# TODO(lpetrut): mark this method as private once compute-hyperv
|
||||
# stops using it. We should also remove the instance '_watcher'
|
||||
# attribute since we end up spawning unused event listeners.
|
||||
|
||||
vm_name = None
|
||||
new_host = None
|
||||
try:
|
||||
# wait for new event for _WMI_EVENT_TIMEOUT_MS milliseconds.
|
||||
if patcher.is_monkey_patched('thread'):
|
||||
wmi_object = tpool.execute(self._watcher,
|
||||
event_timeout_ms)
|
||||
else:
|
||||
wmi_object = self._watcher(event_timeout_ms)
|
||||
|
||||
old_host = wmi_object.previous.OwnerNode
|
||||
new_host = wmi_object.OwnerNode
|
||||
# wmi_object.Name field is of the form:
|
||||
# 'Virtual Machine nova-instance-template'
|
||||
# wmi_object.Name filed is a key and as such is not affected
|
||||
# by locale, so it will always be 'Virtual Machine'
|
||||
match = self._instance_name_regex.search(wmi_object.Name)
|
||||
if match:
|
||||
vm_name = match.group(1)
|
||||
|
||||
if vm_name:
|
||||
try:
|
||||
callback(vm_name, old_host, new_host)
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
"Exception during failover callback.")
|
||||
except exceptions.x_wmi_timed_out:
|
||||
pass
|
||||
|
||||
def get_vm_owner_change_listener(self):
|
||||
def listener(callback):
|
||||
while True:
|
||||
# We avoid setting an infinite timeout in order to let
|
||||
# the process gracefully stop. Note that the os-win WMI
|
||||
# event listeners are meant to be used as long running
|
||||
# daemons, so no stop API is provided ATM.
|
||||
try:
|
||||
self.monitor_vm_failover(
|
||||
callback,
|
||||
constants.DEFAULT_WMI_EVENT_TIMEOUT_MS)
|
||||
except Exception:
|
||||
LOG.exception("The VM cluster group owner change "
|
||||
"event listener encountered an "
|
||||
"unexpected exception.")
|
||||
time.sleep(constants.DEFAULT_WMI_EVENT_TIMEOUT_MS / 1000)
|
||||
|
||||
return listener
|
||||
|
||||
|
||||
# At the moment, those event listeners are not meant to be used outside
|
||||
# os-win, mostly because of the underlying API limitations.
|
||||
class _ClusterEventListener(object):
|
||||
_notif_keys = {}
|
||||
_notif_port_h = None
|
||||
_cluster_handle = None
|
||||
_running = False
|
||||
|
||||
def __init__(self, cluster_handle, notif_filters_list):
|
||||
self._cluster_handle = cluster_handle
|
||||
self._notif_filters_list = notif_filters_list
|
||||
|
||||
self._clusapi_utils = _clusapi_utils.ClusApiUtils()
|
||||
self._event_queue = queue.Queue()
|
||||
|
||||
self._setup()
|
||||
|
||||
def __enter__(self):
|
||||
self._ensure_listener_running()
|
||||
return self
|
||||
|
||||
def _get_notif_key_dw(self, notif_key):
|
||||
notif_key_dw = self._notif_keys.get(notif_key)
|
||||
if notif_key_dw is None:
|
||||
notif_key_dw = wintypes.DWORD(notif_key)
|
||||
# We have to make sure those addresses are preserved.
|
||||
self._notif_keys[notif_key] = notif_key_dw
|
||||
return notif_key_dw
|
||||
|
||||
def _add_filter(self, notif_filter, notif_key=0):
|
||||
notif_key_dw = self._get_notif_key_dw(notif_key)
|
||||
|
||||
# We'll get a notification handle if not already existing.
|
||||
self._notif_port_h = self._clusapi_utils.create_cluster_notify_port_v2(
|
||||
self._cluster_handle, notif_filter,
|
||||
self._notif_port_h, notif_key_dw)
|
||||
|
||||
def _setup_notif_port(self):
|
||||
for notif_filter in self._notif_filters_list:
|
||||
filter_struct = clusapi_def.NOTIFY_FILTER_AND_TYPE(
|
||||
dwObjectType=notif_filter['object_type'],
|
||||
FilterFlags=notif_filter['filter_flags'])
|
||||
notif_key = notif_filter.get('notif_key', 0)
|
||||
|
||||
self._add_filter(filter_struct, notif_key)
|
||||
|
||||
def _setup(self):
|
||||
self._setup_notif_port()
|
||||
|
||||
# If eventlet monkey patching is used, this will actually be a
|
||||
# greenthread. We just don't want to enforce eventlet usage.
|
||||
worker = threading.Thread(target=self._listen)
|
||||
worker.setDaemon(True)
|
||||
|
||||
self._running = True
|
||||
worker.start()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.stop()
|
||||
|
||||
def _signal_stopped(self):
|
||||
self._running = False
|
||||
self._event_queue.put(None)
|
||||
|
||||
def stop(self):
|
||||
self._signal_stopped()
|
||||
|
||||
if self._notif_port_h:
|
||||
self._clusapi_utils.close_cluster_notify_port(self._notif_port_h)
|
||||
|
||||
def _listen(self):
|
||||
while self._running:
|
||||
try:
|
||||
# We're using an indefinite timeout here. When the listener is
|
||||
# closed, this will raise an 'invalid handle value' error,
|
||||
# which we're going to ignore.
|
||||
event = _utils.avoid_blocking_call(
|
||||
self._clusapi_utils.get_cluster_notify_v2,
|
||||
self._notif_port_h,
|
||||
timeout_ms=-1)
|
||||
|
||||
processed_event = self._process_event(event)
|
||||
if processed_event:
|
||||
self._event_queue.put(processed_event)
|
||||
except Exception:
|
||||
if self._running:
|
||||
LOG.exception(
|
||||
"Unexpected exception in event listener loop. "
|
||||
"The cluster event listener will now close.")
|
||||
self._signal_stopped()
|
||||
|
||||
def _process_event(self, event):
|
||||
return event
|
||||
|
||||
def get(self, timeout=None):
|
||||
self._ensure_listener_running()
|
||||
|
||||
event = self._event_queue.get(timeout=timeout)
|
||||
|
||||
self._ensure_listener_running()
|
||||
return event
|
||||
|
||||
def _ensure_listener_running(self):
|
||||
if not self._running:
|
||||
raise exceptions.OSWinException(
|
||||
_("Cluster event listener is not running."))
|
||||
|
||||
|
||||
class _ClusterGroupStateChangeListener(_ClusterEventListener):
|
||||
_NOTIF_KEY_GROUP_STATE = 0
|
||||
_NOTIF_KEY_GROUP_COMMON_PROP = 1
|
||||
|
||||
_notif_filters_list = [
|
||||
dict(object_type=w_const.CLUSTER_OBJECT_TYPE_GROUP,
|
||||
filter_flags=w_const.CLUSTER_CHANGE_GROUP_STATE_V2,
|
||||
notif_key=_NOTIF_KEY_GROUP_STATE),
|
||||
dict(object_type=w_const.CLUSTER_OBJECT_TYPE_GROUP,
|
||||
filter_flags=w_const.CLUSTER_CHANGE_GROUP_COMMON_PROPERTY_V2,
|
||||
notif_key=_NOTIF_KEY_GROUP_COMMON_PROP)]
|
||||
|
||||
def __init__(self, cluster_handle, group_name=None):
|
||||
self._group_name = group_name
|
||||
|
||||
super(_ClusterGroupStateChangeListener, self).__init__(
|
||||
cluster_handle, self._notif_filters_list)
|
||||
|
||||
def _process_event(self, event):
|
||||
group_name = event['cluster_object_name']
|
||||
if self._group_name and self._group_name.lower() != group_name.lower():
|
||||
return
|
||||
|
||||
preserved_keys = ['cluster_object_name', 'object_type',
|
||||
'filter_flags', 'notif_key']
|
||||
processed_event = {key: event[key] for key in preserved_keys}
|
||||
|
||||
notif_key = event['notif_key']
|
||||
if notif_key == self._NOTIF_KEY_GROUP_STATE:
|
||||
if event['buff_sz'] != ctypes.sizeof(wintypes.DWORD):
|
||||
raise exceptions.ClusterPropertyRetrieveFailed()
|
||||
state_p = ctypes.cast(event['buff'], wintypes.PDWORD)
|
||||
state = state_p.contents.value
|
||||
processed_event['state'] = state
|
||||
return processed_event
|
||||
elif notif_key == self._NOTIF_KEY_GROUP_COMMON_PROP:
|
||||
try:
|
||||
status_info = (
|
||||
self._clusapi_utils.get_cluster_group_status_info(
|
||||
ctypes.byref(event['buff']), event['buff_sz']))
|
||||
processed_event['status_info'] = status_info
|
||||
return processed_event
|
||||
except exceptions.ClusterPropertyListEntryNotFound:
|
||||
# At the moment, we only care about the 'StatusInformation'
|
||||
# common property.
|
||||
pass
|
@ -1,221 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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 platform
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import exceptions
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils.compute import migrationutils
|
||||
from os_win.utils.compute import vmutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LiveMigrationUtils(migrationutils.MigrationUtils):
|
||||
_STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData'
|
||||
_CIM_RES_ALLOC_SETTING_DATA_CLASS = 'CIM_ResourceAllocationSettingData'
|
||||
|
||||
_MIGRATION_TYPE_VIRTUAL_SYSTEM = 32768
|
||||
_MIGRATION_TYPE_VIRTUAL_SYSTEM_AND_STORAGE = 32771
|
||||
_MIGRATION_TYPE_STAGED = 32770
|
||||
|
||||
def __init__(self):
|
||||
super(LiveMigrationUtils, self).__init__()
|
||||
|
||||
def _get_conn_v2(self, host='localhost'):
|
||||
try:
|
||||
return self._get_wmi_obj(self._wmi_namespace % host,
|
||||
compatibility_mode=True)
|
||||
except exceptions.x_wmi as ex:
|
||||
LOG.exception('Get version 2 connection error')
|
||||
if ex.com_error.hresult == -2147217394:
|
||||
msg = (_('Live migration is not supported on target host "%s"')
|
||||
% host)
|
||||
elif ex.com_error.hresult == -2147023174:
|
||||
msg = (_('Target live migration host "%s" is unreachable')
|
||||
% host)
|
||||
else:
|
||||
msg = _('Live migration failed: %r') % ex
|
||||
raise exceptions.HyperVException(msg)
|
||||
|
||||
def check_live_migration_config(self):
|
||||
migration_svc = (
|
||||
self._compat_conn.Msvm_VirtualSystemMigrationService()[0])
|
||||
vsmssd = (
|
||||
self._compat_conn.Msvm_VirtualSystemMigrationServiceSettingData())
|
||||
vsmssd = vsmssd[0]
|
||||
if not vsmssd.EnableVirtualSystemMigration:
|
||||
raise exceptions.HyperVException(
|
||||
_('Live migration is not enabled on this host'))
|
||||
if not migration_svc.MigrationServiceListenerIPAddressList:
|
||||
raise exceptions.HyperVException(
|
||||
_('Live migration networks are not configured on this host'))
|
||||
|
||||
def _get_vm(self, conn_v2, vm_name):
|
||||
vms = conn_v2.Msvm_ComputerSystem(ElementName=vm_name)
|
||||
n = len(vms)
|
||||
if not n:
|
||||
raise exceptions.HyperVVMNotFoundException(vm_name=vm_name)
|
||||
elif n > 1:
|
||||
raise exceptions.HyperVException(_('Duplicate VM name found: %s')
|
||||
% vm_name)
|
||||
return vms[0]
|
||||
|
||||
def _create_planned_vm(self, conn_v2_local, conn_v2_remote,
|
||||
vm, ip_addr_list, dest_host):
|
||||
# Staged
|
||||
vsmsd = conn_v2_remote.Msvm_VirtualSystemMigrationSettingData(
|
||||
MigrationType=self._MIGRATION_TYPE_STAGED)[0]
|
||||
vsmsd.DestinationIPAddressList = ip_addr_list
|
||||
migration_setting_data = vsmsd.GetText_(1)
|
||||
|
||||
LOG.debug("Creating planned VM for VM: %s", vm.ElementName)
|
||||
migr_svc = conn_v2_remote.Msvm_VirtualSystemMigrationService()[0]
|
||||
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
|
||||
ComputerSystem=vm.path_(),
|
||||
DestinationHost=dest_host,
|
||||
MigrationSettingData=migration_setting_data)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
return conn_v2_local.Msvm_PlannedComputerSystem(Name=vm.Name)[0]
|
||||
|
||||
def _get_disk_data(self, vm_name, vmutils_remote, disk_path_mapping):
|
||||
disk_paths = {}
|
||||
phys_disk_resources = vmutils_remote.get_vm_disks(vm_name)[1]
|
||||
|
||||
for disk in phys_disk_resources:
|
||||
rasd_rel_path = disk.path().RelPath
|
||||
# We set this when volumes are attached.
|
||||
serial = disk.ElementName
|
||||
disk_paths[rasd_rel_path] = disk_path_mapping[serial]
|
||||
return disk_paths
|
||||
|
||||
def _update_planned_vm_disk_resources(self, conn_v2_local,
|
||||
planned_vm, vm_name,
|
||||
disk_paths_remote):
|
||||
updated_resource_setting_data = []
|
||||
sasds = _wqlutils.get_element_associated_class(
|
||||
self._compat_conn, self._CIM_RES_ALLOC_SETTING_DATA_CLASS,
|
||||
element_uuid=planned_vm.Name)
|
||||
for sasd in sasds:
|
||||
if (sasd.ResourceType == 17 and sasd.ResourceSubType ==
|
||||
"Microsoft:Hyper-V:Physical Disk Drive" and
|
||||
sasd.HostResource):
|
||||
# Replace the local disk target with the correct remote one
|
||||
old_disk_path = sasd.HostResource[0]
|
||||
new_disk_path = disk_paths_remote.pop(sasd.path().RelPath)
|
||||
|
||||
LOG.debug("Replacing host resource "
|
||||
"%(old_disk_path)s with "
|
||||
"%(new_disk_path)s on planned VM %(vm_name)s",
|
||||
{'old_disk_path': old_disk_path,
|
||||
'new_disk_path': new_disk_path,
|
||||
'vm_name': vm_name})
|
||||
sasd.HostResource = [new_disk_path]
|
||||
updated_resource_setting_data.append(sasd.GetText_(1))
|
||||
|
||||
LOG.debug("Updating remote planned VM disk paths for VM: %s",
|
||||
vm_name)
|
||||
vsmsvc = conn_v2_local.Msvm_VirtualSystemManagementService()[0]
|
||||
(res_settings, job_path, ret_val) = vsmsvc.ModifyResourceSettings(
|
||||
ResourceSettings=updated_resource_setting_data)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_vhd_setting_data(self, vm):
|
||||
new_resource_setting_data = []
|
||||
sasds = _wqlutils.get_element_associated_class(
|
||||
self._compat_conn, self._STORAGE_ALLOC_SETTING_DATA_CLASS,
|
||||
element_uuid=vm.Name)
|
||||
for sasd in sasds:
|
||||
if (sasd.ResourceType == 31 and sasd.ResourceSubType ==
|
||||
"Microsoft:Hyper-V:Virtual Hard Disk"):
|
||||
new_resource_setting_data.append(sasd.GetText_(1))
|
||||
return new_resource_setting_data
|
||||
|
||||
def _live_migrate_vm(self, conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
|
||||
new_resource_setting_data, dest_host, migration_type):
|
||||
# VirtualSystemAndStorage
|
||||
vsmsd = conn_v2_local.Msvm_VirtualSystemMigrationSettingData(
|
||||
MigrationType=migration_type)[0]
|
||||
vsmsd.DestinationIPAddressList = rmt_ip_addr_list
|
||||
if planned_vm:
|
||||
vsmsd.DestinationPlannedVirtualSystemId = planned_vm.Name
|
||||
migration_setting_data = vsmsd.GetText_(1)
|
||||
|
||||
migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
|
||||
|
||||
LOG.debug("Starting live migration for VM: %s", vm.ElementName)
|
||||
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
|
||||
ComputerSystem=vm.path_(),
|
||||
DestinationHost=dest_host,
|
||||
MigrationSettingData=migration_setting_data,
|
||||
NewResourceSettingData=new_resource_setting_data)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_ip_address_list(self, conn_v2, hostname):
|
||||
LOG.debug("Getting live migration networks for host: %s",
|
||||
hostname)
|
||||
migr_svc_rmt = conn_v2.Msvm_VirtualSystemMigrationService()[0]
|
||||
return migr_svc_rmt.MigrationServiceListenerIPAddressList
|
||||
|
||||
def live_migrate_vm(self, vm_name, dest_host, migrate_disks=True):
|
||||
self.check_live_migration_config()
|
||||
|
||||
conn_v2_remote = self._get_conn_v2(dest_host)
|
||||
|
||||
vm = self._get_vm(self._compat_conn, vm_name)
|
||||
|
||||
rmt_ip_addr_list = self._get_ip_address_list(conn_v2_remote,
|
||||
dest_host)
|
||||
|
||||
planned_vm = self._get_planned_vm(vm_name, conn_v2_remote)
|
||||
|
||||
if migrate_disks:
|
||||
new_resource_setting_data = self._get_vhd_setting_data(vm)
|
||||
migration_type = self._MIGRATION_TYPE_VIRTUAL_SYSTEM_AND_STORAGE
|
||||
else:
|
||||
new_resource_setting_data = None
|
||||
migration_type = self._MIGRATION_TYPE_VIRTUAL_SYSTEM
|
||||
|
||||
self._live_migrate_vm(self._compat_conn, vm, planned_vm,
|
||||
rmt_ip_addr_list, new_resource_setting_data,
|
||||
dest_host, migration_type)
|
||||
|
||||
def create_planned_vm(self, vm_name, src_host, disk_path_mapping):
|
||||
# This is run on the destination host.
|
||||
dest_host = platform.node()
|
||||
vmutils_remote = vmutils.VMUtils(src_host)
|
||||
|
||||
conn_v2_remote = self._get_conn_v2(src_host)
|
||||
vm = self._get_vm(conn_v2_remote, vm_name)
|
||||
|
||||
# Make sure there are no planned VMs already.
|
||||
self.destroy_existing_planned_vm(vm_name)
|
||||
|
||||
ip_addr_list = self._get_ip_address_list(self._compat_conn,
|
||||
dest_host)
|
||||
|
||||
disk_paths = self._get_disk_data(vm_name, vmutils_remote,
|
||||
disk_path_mapping)
|
||||
|
||||
planned_vm = self._create_planned_vm(self._compat_conn,
|
||||
conn_v2_remote,
|
||||
vm, ip_addr_list,
|
||||
dest_host)
|
||||
self._update_planned_vm_disk_resources(self._compat_conn, planned_vm,
|
||||
vm_name, disk_paths)
|
@ -1,100 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils.compute import vmutils
|
||||
from os_win.utils import jobutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MigrationUtils(baseutils.BaseUtilsVirt):
|
||||
|
||||
def __init__(self):
|
||||
super(MigrationUtils, self).__init__()
|
||||
self._vmutils = vmutils.VMUtils()
|
||||
self._jobutils = jobutils.JobUtils()
|
||||
|
||||
def _get_export_setting_data(self, vm_name):
|
||||
vm = self._vmutils._lookup_vm(vm_name)
|
||||
export_sd = self._compat_conn.Msvm_VirtualSystemExportSettingData(
|
||||
InstanceID=vm.InstanceID)
|
||||
return export_sd[0]
|
||||
|
||||
def export_vm(self, vm_name, export_path,
|
||||
copy_snapshots_config=constants.EXPORT_CONFIG_SNAPSHOTS_ALL,
|
||||
copy_vm_storage=False, create_export_subdir=False):
|
||||
vm = self._vmutils._lookup_vm(vm_name)
|
||||
export_setting_data = self._get_export_setting_data(vm_name)
|
||||
|
||||
export_setting_data.CopySnapshotConfiguration = copy_snapshots_config
|
||||
export_setting_data.CopyVmStorage = copy_vm_storage
|
||||
export_setting_data.CreateVmExportSubdirectory = create_export_subdir
|
||||
|
||||
(job_path, ret_val) = self._vs_man_svc.ExportSystemDefinition(
|
||||
ComputerSystem=vm.path_(),
|
||||
ExportDirectory=export_path,
|
||||
ExportSettingData=export_setting_data.GetText_(1))
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def import_vm_definition(self, export_config_file_path,
|
||||
snapshot_folder_path,
|
||||
new_uuid=False):
|
||||
(ref, job_path, ret_val) = self._vs_man_svc.ImportSystemDefinition(
|
||||
new_uuid, snapshot_folder_path, export_config_file_path)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def realize_vm(self, vm_name):
|
||||
planned_vm = self._get_planned_vm(vm_name, fail_if_not_found=True)
|
||||
|
||||
if planned_vm:
|
||||
(job_path, ret_val) = (
|
||||
self._vs_man_svc.ValidatePlannedSystem(planned_vm.path_()))
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
(job_path, ref, ret_val) = (
|
||||
self._vs_man_svc.RealizePlannedSystem(planned_vm.path_()))
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_planned_vm(self, vm_name, conn_v2=None, fail_if_not_found=False):
|
||||
if not conn_v2:
|
||||
conn_v2 = self._conn
|
||||
planned_vm = conn_v2.Msvm_PlannedComputerSystem(ElementName=vm_name)
|
||||
if planned_vm:
|
||||
return planned_vm[0]
|
||||
elif fail_if_not_found:
|
||||
raise exceptions.HyperVException(
|
||||
_('Cannot find planned VM with name: %s') % vm_name)
|
||||
return None
|
||||
|
||||
def planned_vm_exists(self, vm_name):
|
||||
"""Checks if the Planned VM with the given name exists on the host."""
|
||||
return self._get_planned_vm(vm_name) is not None
|
||||
|
||||
def _destroy_planned_vm(self, planned_vm):
|
||||
LOG.debug("Destroying existing planned VM: %s",
|
||||
planned_vm.ElementName)
|
||||
(job_path,
|
||||
ret_val) = self._vs_man_svc.DestroySystem(planned_vm.path_())
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def destroy_existing_planned_vm(self, vm_name):
|
||||
planned_vm = self._get_planned_vm(vm_name, self._compat_conn)
|
||||
if planned_vm:
|
||||
self._destroy_planned_vm(planned_vm)
|
@ -1,22 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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 os_win.utils import baseutils
|
||||
|
||||
|
||||
class RDPConsoleUtils(baseutils.BaseUtilsVirt):
|
||||
def get_rdp_console_port(self):
|
||||
rdp_setting_data = self._conn.Msvm_TerminalServiceSettingData()[0]
|
||||
return rdp_setting_data.ListenerPort
|
File diff suppressed because it is too large
Load Diff
@ -1,294 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 re
|
||||
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils.compute import vmutils
|
||||
from oslo_utils import units
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VMUtils10(vmutils.VMUtils):
|
||||
|
||||
_UEFI_CERTIFICATE_AUTH = 'MicrosoftUEFICertificateAuthority'
|
||||
_SERIAL_PORT_SETTING_DATA_CLASS = "Msvm_SerialPortSettingData"
|
||||
_SECURITY_SETTING_DATA = 'Msvm_SecuritySettingData'
|
||||
_PCI_EXPRESS_SETTING_DATA = 'Msvm_PciExpressSettingData'
|
||||
_MSPS_NAMESPACE = '//%s/root/msps'
|
||||
|
||||
_remote_fx_res_map = {
|
||||
constants.REMOTEFX_MAX_RES_1024x768: 0,
|
||||
constants.REMOTEFX_MAX_RES_1280x1024: 1,
|
||||
constants.REMOTEFX_MAX_RES_1600x1200: 2,
|
||||
constants.REMOTEFX_MAX_RES_1920x1200: 3,
|
||||
constants.REMOTEFX_MAX_RES_2560x1600: 4,
|
||||
constants.REMOTEFX_MAX_RES_3840x2160: 5
|
||||
}
|
||||
|
||||
_remotefx_max_monitors_map = {
|
||||
# defines the maximum number of monitors for a given
|
||||
# resolution
|
||||
constants.REMOTEFX_MAX_RES_1024x768: 8,
|
||||
constants.REMOTEFX_MAX_RES_1280x1024: 8,
|
||||
constants.REMOTEFX_MAX_RES_1600x1200: 4,
|
||||
constants.REMOTEFX_MAX_RES_1920x1200: 4,
|
||||
constants.REMOTEFX_MAX_RES_2560x1600: 2,
|
||||
constants.REMOTEFX_MAX_RES_3840x2160: 1
|
||||
}
|
||||
|
||||
_remotefx_vram_vals = [64 * units.Mi, 128 * units.Mi, 256 * units.Mi,
|
||||
512 * units.Mi, 1024 * units.Mi]
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(VMUtils10, self).__init__(host)
|
||||
self._conn_msps_attr = None
|
||||
self._sec_svc_attr = None
|
||||
|
||||
@property
|
||||
def _conn_msps(self):
|
||||
if not self._conn_msps_attr:
|
||||
try:
|
||||
namespace = self._MSPS_NAMESPACE % self._host
|
||||
self._conn_msps_attr = self._get_wmi_conn(namespace)
|
||||
except Exception:
|
||||
raise exceptions.OSWinException(
|
||||
_("Namespace %(namespace)s not found. Make sure "
|
||||
"FabricShieldedTools feature is installed.") %
|
||||
{'namespace': namespace})
|
||||
|
||||
return self._conn_msps_attr
|
||||
|
||||
@property
|
||||
def _sec_svc(self):
|
||||
if not self._sec_svc_attr:
|
||||
self._sec_svc_attr = self._conn.Msvm_SecurityService()[0]
|
||||
return self._sec_svc_attr
|
||||
|
||||
def set_nested_virtualization(self, vm_name, state):
|
||||
"""Enables nested virtualization for the given VM.
|
||||
|
||||
:param vm_name: the name of the VM.
|
||||
:param state: boolean, if True, nested virtualization will be enabled,
|
||||
disabled otherwise.
|
||||
"""
|
||||
vmsettings = self._lookup_vm_check(vm_name)
|
||||
procsettings = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PROCESSOR_SETTING_DATA_CLASS,
|
||||
element_instance_id=vmsettings.InstanceID)[0]
|
||||
|
||||
procsettings.ExposeVirtualizationExtensions = state
|
||||
self._jobutils.modify_virt_resource(procsettings)
|
||||
|
||||
def vm_gen_supports_remotefx(self, vm_gen):
|
||||
"""RemoteFX is supported on both generation 1 and 2 virtual
|
||||
|
||||
machines for Windows 10 / Windows Server 2016.
|
||||
|
||||
:returns: True
|
||||
"""
|
||||
|
||||
return True
|
||||
|
||||
def _validate_remotefx_params(self, monitor_count, max_resolution,
|
||||
vram_bytes=None):
|
||||
super(VMUtils10, self)._validate_remotefx_params(monitor_count,
|
||||
max_resolution)
|
||||
if vram_bytes and vram_bytes not in self._remotefx_vram_vals:
|
||||
raise exceptions.HyperVRemoteFXException(
|
||||
_("Unsuported RemoteFX VRAM value: %(requested_value)s."
|
||||
"The supported VRAM values are: %(supported_values)s") %
|
||||
{'requested_value': vram_bytes,
|
||||
'supported_values': self._remotefx_vram_vals})
|
||||
|
||||
def _set_remotefx_vram(self, remotefx_disp_ctrl_res, vram_bytes):
|
||||
if vram_bytes:
|
||||
remotefx_disp_ctrl_res.VRAMSizeBytes = six.text_type(vram_bytes)
|
||||
|
||||
def _vm_has_s3_controller(self, vm_name):
|
||||
return self.get_vm_generation(vm_name) == constants.VM_GEN_1
|
||||
|
||||
def _set_secure_boot(self, vs_data, msft_ca_required):
|
||||
vs_data.SecureBootEnabled = True
|
||||
if msft_ca_required:
|
||||
uefi_data = self._conn.Msvm_VirtualSystemSettingData(
|
||||
ElementName=self._UEFI_CERTIFICATE_AUTH)[0]
|
||||
vs_data.SecureBootTemplateId = uefi_data.SecureBootTemplateId
|
||||
|
||||
def populate_fsk(self, fsk_filepath, fsk_pairs):
|
||||
"""Writes in the fsk file all the substitution strings and their
|
||||
|
||||
values which will populate the unattended file used when
|
||||
creating the pdk.
|
||||
"""
|
||||
|
||||
fabric_data_pairs = []
|
||||
for fsk_key, fsk_value in fsk_pairs.items():
|
||||
fabricdata = self._conn_msps.Msps_FabricData.new()
|
||||
fabricdata.key = fsk_key
|
||||
fabricdata.Value = fsk_value
|
||||
fabric_data_pairs.append(fabricdata)
|
||||
|
||||
fsk = self._conn_msps.Msps_FSK.new()
|
||||
fsk.FabricDataPairs = fabric_data_pairs
|
||||
msps_pfp = self._conn_msps.Msps_ProvisioningFileProcessor
|
||||
|
||||
msps_pfp.SerializeToFile(fsk_filepath, fsk)
|
||||
|
||||
def add_vtpm(self, vm_name, pdk_filepath, shielded):
|
||||
"""Adds a vtpm and enables it with encryption or shielded option."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
msps_pfp = self._conn_msps.Msps_ProvisioningFileProcessor
|
||||
provisioning_file = msps_pfp.PopulateFromFile(pdk_filepath)[0]
|
||||
# key_protector: array of bytes
|
||||
key_protector = provisioning_file.KeyProtector
|
||||
# policy_data: array of bytes
|
||||
policy_data = provisioning_file.PolicyData
|
||||
|
||||
security_profile = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._SECURITY_SETTING_DATA,
|
||||
element_uuid=vm.ConfigurationID)[0]
|
||||
|
||||
security_profile.EncryptStateAndVmMigrationTraffic = True
|
||||
security_profile.TpmEnabled = True
|
||||
security_profile.ShieldingRequested = shielded
|
||||
|
||||
sec_profile_serialized = security_profile.GetText_(1)
|
||||
(job_path, ret_val) = self._sec_svc.SetKeyProtector(
|
||||
key_protector, sec_profile_serialized)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
(job_path, ret_val) = self._sec_svc.SetSecurityPolicy(
|
||||
policy_data, sec_profile_serialized)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
(job_path, ret_val) = self._sec_svc.ModifySecuritySettings(
|
||||
sec_profile_serialized)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def provision_vm(self, vm_name, fsk_filepath, pdk_filepath):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
provisioning_service = self._conn_msps.Msps_ProvisioningService
|
||||
|
||||
(job_path, ret_val) = provisioning_service.ProvisionMachine(
|
||||
fsk_filepath, vm.ConfigurationID, pdk_filepath)
|
||||
self._jobutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def is_secure_vm(self, instance_name):
|
||||
inst_id = self.get_vm_id(instance_name)
|
||||
security_profile = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._SECURITY_SETTING_DATA,
|
||||
element_uuid=inst_id)
|
||||
if security_profile:
|
||||
return security_profile[0].EncryptStateAndVmMigrationTraffic
|
||||
return False
|
||||
|
||||
def add_pci_device(self, vm_name, vendor_id, product_id):
|
||||
"""Adds the given PCI device to the given VM.
|
||||
|
||||
:param vm_name: the name of the VM to which the PCI device will be
|
||||
attached to.
|
||||
:param vendor_id: the PCI device's vendor ID.
|
||||
:param product_id: the PCI device's product ID.
|
||||
:raises exceptions.PciDeviceNotFound: if there is no PCI device
|
||||
identifiable by the given vendor_id and product_id, or it was
|
||||
already assigned.
|
||||
"""
|
||||
vmsettings = self._lookup_vm_check(vm_name)
|
||||
pci_setting_data = self._get_new_setting_data(
|
||||
self._PCI_EXPRESS_SETTING_DATA)
|
||||
pci_device = self._get_assignable_pci_device(vendor_id, product_id)
|
||||
pci_setting_data.HostResource = [pci_device.path_()]
|
||||
|
||||
self._jobutils.add_virt_resource(pci_setting_data, vmsettings)
|
||||
|
||||
def _get_assignable_pci_device(self, vendor_id, product_id):
|
||||
pci_devices = self._conn.Msvm_PciExpress()
|
||||
|
||||
pattern = re.compile(
|
||||
"^(.*)VEN_%(vendor_id)s&DEV_%(product_id)s&(.*)$" % {
|
||||
'vendor_id': vendor_id, 'product_id': product_id})
|
||||
for dev in pci_devices:
|
||||
if pattern.match(dev.DeviceID):
|
||||
# NOTE(claudiub): if the given PCI device is already assigned,
|
||||
# the pci_devices list will contain PCI device with the same
|
||||
# LocationPath.
|
||||
pci_devices_found = [d for d in pci_devices if
|
||||
d.LocationPath == dev.LocationPath]
|
||||
|
||||
LOG.debug('PCI devices found: %s',
|
||||
[d.DeviceID for d in pci_devices_found])
|
||||
|
||||
# device is not in use by other VM
|
||||
if len(pci_devices_found) == 1:
|
||||
return pci_devices_found[0]
|
||||
|
||||
raise exceptions.PciDeviceNotFound(vendor_id=vendor_id,
|
||||
product_id=product_id)
|
||||
|
||||
def remove_pci_device(self, vm_name, vendor_id, product_id):
|
||||
"""Removes the given PCI device from the given VM.
|
||||
|
||||
:param vm_name: the name of the VM from which the PCI device will be
|
||||
attached from.
|
||||
:param vendor_id: the PCI device's vendor ID.
|
||||
:param product_id: the PCI device's product ID.
|
||||
"""
|
||||
vmsettings = self._lookup_vm_check(vm_name)
|
||||
|
||||
pattern = re.compile(
|
||||
"^(.*)VEN_%(vendor_id)s&DEV_%(product_id)s&(.*)$" % {
|
||||
'vendor_id': vendor_id, 'product_id': product_id})
|
||||
|
||||
pci_sds = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PCI_EXPRESS_SETTING_DATA,
|
||||
vmsettings.InstanceID)
|
||||
pci_sds = [sd for sd in pci_sds if pattern.match(sd.HostResource[0])]
|
||||
|
||||
if pci_sds:
|
||||
self._jobutils.remove_virt_resource(pci_sds[0])
|
||||
else:
|
||||
LOG.debug("PCI device with vendor ID %(vendor_id)s and "
|
||||
"%(product_id)s is not attached to %(vm_name)s",
|
||||
{'vendor_id': vendor_id, 'product_id': product_id,
|
||||
'vm_name': vm_name})
|
||||
|
||||
def remove_all_pci_devices(self, vm_name):
|
||||
"""Removes all the PCI devices from the given VM.
|
||||
|
||||
:param vm_name: the name of the VM from which all the PCI devices will
|
||||
be detached from.
|
||||
"""
|
||||
vmsettings = self._lookup_vm_check(vm_name)
|
||||
|
||||
pci_sds = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PCI_EXPRESS_SETTING_DATA,
|
||||
vmsettings.InstanceID)
|
||||
|
||||
if pci_sds:
|
||||
self._jobutils.remove_multiple_virt_resources(pci_sds)
|
||||
|
||||
def _set_vm_snapshot_type(self, vmsettings, snapshot_type):
|
||||
# We expect the caller to actually push the vmsettings update.
|
||||
vmsettings.UserSnapshotType = snapshot_type
|
@ -1,190 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DNSUtils(baseutils.BaseUtils):
|
||||
|
||||
_DNS_NAMESPACE = '//%s/root/MicrosoftDNS'
|
||||
|
||||
def __init__(self, host='.'):
|
||||
self._dns_manager_attr = None
|
||||
self._host = host
|
||||
|
||||
@property
|
||||
def _dns_manager(self):
|
||||
if not self._dns_manager_attr:
|
||||
try:
|
||||
namespace = self._DNS_NAMESPACE % self._host
|
||||
self._dns_manager_attr = self._get_wmi_obj(namespace)
|
||||
except Exception:
|
||||
raise exceptions.DNSException(
|
||||
_("Namespace %(namespace)s not found. Make sure "
|
||||
"DNS Server feature is installed.") %
|
||||
{'namespace': namespace})
|
||||
|
||||
return self._dns_manager_attr
|
||||
|
||||
def _get_zone(self, zone_name, ignore_missing=True):
|
||||
zones = self._dns_manager.MicrosoftDNS_Zone(Name=zone_name)
|
||||
if zones:
|
||||
return zones[0]
|
||||
if not ignore_missing:
|
||||
raise exceptions.DNSZoneNotFound(zone_name=zone_name)
|
||||
|
||||
def zone_list(self):
|
||||
"""Returns the current list of DNS Zones.
|
||||
|
||||
"""
|
||||
|
||||
zones = self._dns_manager.MicrosoftDNS_Zone()
|
||||
return [x.Name for x in zones]
|
||||
|
||||
def zone_exists(self, zone_name):
|
||||
return self._get_zone(zone_name) is not None
|
||||
|
||||
def get_zone_properties(self, zone_name):
|
||||
zone = self._get_zone(zone_name, ignore_missing=False)
|
||||
|
||||
zone_properties = {}
|
||||
zone_properties['zone_type'] = zone.ZoneType
|
||||
zone_properties['ds_integrated'] = zone.DsIntegrated
|
||||
zone_properties['data_file_name'] = zone.DataFile
|
||||
zone_properties['master_servers'] = zone.MasterServers or []
|
||||
|
||||
return zone_properties
|
||||
|
||||
def zone_create(self, zone_name, zone_type, ds_integrated,
|
||||
data_file_name=None, ip_addrs=None,
|
||||
admin_email_name=None):
|
||||
"""Creates a DNS Zone and returns the path to the associated object.
|
||||
|
||||
:param zone_name: string representing the name of the zone.
|
||||
:param zone_type: type of zone
|
||||
0 = Primary zone
|
||||
1 = Secondary zone, MUST include at least one master IP
|
||||
2 = Stub zone, MUST include at least one master IP
|
||||
3 = Zone forwarder, MUST include at least one master IP
|
||||
:param ds_integrated: Only Primary zones can be stored in AD
|
||||
True = the zone data is stored in the Active Directory
|
||||
False = the data zone is stored in files
|
||||
:param data_file_name(Optional): name of the data file associated
|
||||
with the zone.
|
||||
:param ip_addrs(Optional): IP addresses of the master DNS servers
|
||||
for this zone. Parameter type MUST be list
|
||||
:param admin_email_name(Optional): email address of the administrator
|
||||
responsible for the zone.
|
||||
"""
|
||||
LOG.debug("Creating DNS Zone '%s'" % zone_name)
|
||||
if self.zone_exists(zone_name):
|
||||
raise exceptions.DNSZoneAlreadyExists(zone_name=zone_name)
|
||||
|
||||
dns_zone_manager = self._dns_manager.MicrosoftDNS_Zone
|
||||
(zone_path,) = dns_zone_manager.CreateZone(
|
||||
ZoneName=zone_name,
|
||||
ZoneType=zone_type,
|
||||
DsIntegrated=ds_integrated,
|
||||
DataFileName=data_file_name,
|
||||
IpAddr=ip_addrs,
|
||||
AdminEmailname=admin_email_name)
|
||||
return zone_path
|
||||
|
||||
def zone_delete(self, zone_name):
|
||||
LOG.debug("Deleting DNS Zone '%s'" % zone_name)
|
||||
|
||||
zone_to_be_deleted = self._get_zone(zone_name)
|
||||
if zone_to_be_deleted:
|
||||
zone_to_be_deleted.Delete_()
|
||||
|
||||
def zone_modify(self, zone_name, allow_update=None, disable_wins=None,
|
||||
notify=None, reverse=None, secure_secondaries=None):
|
||||
"""Modifies properties of an existing zone. If any parameter is None,
|
||||
|
||||
then that parameter will be skipped and will not be taken into
|
||||
consideration.
|
||||
|
||||
:param zone_name: string representing the name of the zone.
|
||||
:param allow_update:
|
||||
0 = No updates allowed.
|
||||
1 = Zone accepts both secure and nonsecure updates.
|
||||
2 = Zone accepts secure updates only.
|
||||
:param disable_wins: Indicates whether the WINS record is replicated.
|
||||
If set to TRUE, WINS record replication is disabled.
|
||||
:param notify:
|
||||
0 = Do not notify secondaries
|
||||
1 = Notify Servers listed on the Name Servers Tab
|
||||
2 = Notify the specified servers
|
||||
:param reverse: Indicates whether the Zone is reverse (TRUE)
|
||||
or forward (FALSE).
|
||||
:param secure_secondaries:
|
||||
0 = Allowed to Any host
|
||||
1 = Only to the Servers listed on the Name Servers tab
|
||||
2 = To the following servers (destination servers IP addresses
|
||||
are specified in SecondaryServers value)
|
||||
3 = Zone transfers not allowed
|
||||
"""
|
||||
|
||||
zone = self._get_zone(zone_name, ignore_missing=False)
|
||||
|
||||
if allow_update is not None:
|
||||
zone.AllowUpdate = allow_update
|
||||
if disable_wins is not None:
|
||||
zone.DisableWINSRecordReplication = disable_wins
|
||||
if notify is not None:
|
||||
zone.Notify = notify
|
||||
if reverse is not None:
|
||||
zone.Reverse = reverse
|
||||
if secure_secondaries is not None:
|
||||
zone.SecureSecondaries = secure_secondaries
|
||||
|
||||
zone.put()
|
||||
|
||||
def zone_update(self, zone_name):
|
||||
LOG.debug("Updating DNS Zone '%s'" % zone_name)
|
||||
|
||||
zone = self._get_zone(zone_name, ignore_missing=False)
|
||||
if (zone.DsIntegrated and
|
||||
zone.ZoneType == constants.DNS_ZONE_TYPE_PRIMARY):
|
||||
zone.UpdateFromDS()
|
||||
elif zone.ZoneType in [constants.DNS_ZONE_TYPE_SECONDARY,
|
||||
constants.DNS_ZONE_TYPE_STUB]:
|
||||
zone.ForceRefresh()
|
||||
elif zone.ZoneType in [constants.DNS_ZONE_TYPE_PRIMARY,
|
||||
constants.DNS_ZONE_TYPE_FORWARD]:
|
||||
zone.ReloadZone()
|
||||
|
||||
def get_zone_serial(self, zone_name):
|
||||
# Performing a manual check to make sure the zone exists before
|
||||
# trying to retrieve the MicrosoftDNS_SOAType object. Otherwise,
|
||||
# the query for MicrosoftDNS_SOAType will fail with "Generic Failure"
|
||||
if not self.zone_exists(zone_name):
|
||||
# Return None if zone was not found
|
||||
return None
|
||||
|
||||
zone_soatype = self._dns_manager.MicrosoftDNS_SOAType(
|
||||
ContainerName=zone_name)
|
||||
if not zone_soatype:
|
||||
return None
|
||||
# Serial number of the SOA record
|
||||
SOA = zone_soatype[0].SerialNumber
|
||||
return int(SOA)
|
@ -1,302 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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 socket
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
|
||||
kernel32 = w_lib.get_shared_lib_handle(w_lib.KERNEL32)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostUtils(baseutils.BaseUtilsVirt):
|
||||
|
||||
_windows_version = None
|
||||
|
||||
_MSVM_PROCESSOR = 'Msvm_Processor'
|
||||
_MSVM_MEMORY = 'Msvm_Memory'
|
||||
_MSVM_NUMA_NODE = 'Msvm_NumaNode'
|
||||
|
||||
_CENTRAL_PROCESSOR = 'Central Processor'
|
||||
|
||||
_HOST_FORCED_REBOOT = 6
|
||||
_HOST_FORCED_SHUTDOWN = 12
|
||||
_DEFAULT_VM_GENERATION = constants.IMAGE_PROP_VM_GEN_1
|
||||
|
||||
FEATURE_RDS_VIRTUALIZATION = 322
|
||||
FEATURE_MPIO = 57
|
||||
|
||||
_wmi_cimv2_namespace = '//./root/cimv2'
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(HostUtils, self).__init__(host)
|
||||
self._conn_cimv2 = self._get_wmi_conn(self._wmi_cimv2_namespace,
|
||||
privileges=["Shutdown"])
|
||||
|
||||
def get_cpus_info(self):
|
||||
"""Returns dictionary containing information about the host's CPUs."""
|
||||
# NOTE(abalutoiu): Specifying exactly the fields that we need
|
||||
# improves the speed of the query. The LoadPercentage field
|
||||
# is the load capacity of each processor averaged to the last
|
||||
# second, which is time wasted.
|
||||
cpus = self._conn_cimv2.query(
|
||||
"SELECT Architecture, Name, Manufacturer, MaxClockSpeed, "
|
||||
"NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor "
|
||||
"WHERE ProcessorType = 3")
|
||||
cpus_list = []
|
||||
for cpu in cpus:
|
||||
cpu_info = {'Architecture': cpu.Architecture,
|
||||
'Name': cpu.Name,
|
||||
'Manufacturer': cpu.Manufacturer,
|
||||
'MaxClockSpeed': cpu.MaxClockSpeed,
|
||||
'NumberOfCores': cpu.NumberOfCores,
|
||||
'NumberOfLogicalProcessors':
|
||||
cpu.NumberOfLogicalProcessors}
|
||||
cpus_list.append(cpu_info)
|
||||
return cpus_list
|
||||
|
||||
def is_cpu_feature_present(self, feature_key):
|
||||
"""Checks if the host's CPUs have the given feature."""
|
||||
return kernel32.IsProcessorFeaturePresent(feature_key)
|
||||
|
||||
def get_memory_info(self):
|
||||
"""Returns a tuple with total visible memory and free physical memory.
|
||||
|
||||
The returned values are expressed in KB.
|
||||
"""
|
||||
|
||||
mem_info = self._conn_cimv2.query("SELECT TotalVisibleMemorySize, "
|
||||
"FreePhysicalMemory "
|
||||
"FROM win32_operatingsystem")[0]
|
||||
return (int(mem_info.TotalVisibleMemorySize),
|
||||
int(mem_info.FreePhysicalMemory))
|
||||
|
||||
# TODO(atuvenie) This method should be removed once all the callers have
|
||||
# changed to use the get_disk_capacity method from diskutils.
|
||||
def get_volume_info(self, drive):
|
||||
"""Returns a tuple with total size and free space of the given drive.
|
||||
|
||||
Returned values are expressed in bytes.
|
||||
|
||||
:param drive: the drive letter of the logical disk whose information
|
||||
is required.
|
||||
"""
|
||||
|
||||
logical_disk = self._conn_cimv2.query("SELECT Size, FreeSpace "
|
||||
"FROM win32_logicaldisk "
|
||||
"WHERE DeviceID='%s'"
|
||||
% drive)[0]
|
||||
return (int(logical_disk.Size), int(logical_disk.FreeSpace))
|
||||
|
||||
def check_min_windows_version(self, major, minor, build=0):
|
||||
"""Compares the host's kernel version with the given version.
|
||||
|
||||
:returns: True if the host's kernel version is higher or equal to
|
||||
the given version.
|
||||
"""
|
||||
version_str = self.get_windows_version()
|
||||
return list(map(int, version_str.split('.'))) >= [major, minor, build]
|
||||
|
||||
def get_windows_version(self):
|
||||
"""Returns a string representing the host's kernel version."""
|
||||
if not HostUtils._windows_version:
|
||||
Win32_OperatingSystem = self._conn_cimv2.Win32_OperatingSystem()[0]
|
||||
HostUtils._windows_version = Win32_OperatingSystem.Version
|
||||
return HostUtils._windows_version
|
||||
|
||||
def get_local_ips(self):
|
||||
"""Returns the list of locally assigned IPs."""
|
||||
hostname = socket.gethostname()
|
||||
return _utils.get_ips(hostname)
|
||||
|
||||
def get_host_tick_count64(self):
|
||||
"""Returns host uptime in milliseconds."""
|
||||
return kernel32.GetTickCount64()
|
||||
|
||||
def host_power_action(self, action):
|
||||
win32_os = self._conn_cimv2.Win32_OperatingSystem()[0]
|
||||
|
||||
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
|
||||
win32_os.Win32Shutdown(self._HOST_FORCED_SHUTDOWN)
|
||||
elif action == constants.HOST_POWER_ACTION_REBOOT:
|
||||
win32_os.Win32Shutdown(self._HOST_FORCED_REBOOT)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
_("Host %(action)s is not supported by the Hyper-V driver") %
|
||||
{"action": action})
|
||||
|
||||
def get_supported_vm_types(self):
|
||||
"""Get the supported Hyper-V VM generations.
|
||||
|
||||
Hyper-V Generation 2 VMs are supported in Windows 8.1,
|
||||
Windows Server / Hyper-V Server 2012 R2 or newer.
|
||||
|
||||
:returns: array of supported VM generations (ex. ['hyperv-gen1'])
|
||||
"""
|
||||
|
||||
if self.check_min_windows_version(6, 3):
|
||||
return [constants.IMAGE_PROP_VM_GEN_1,
|
||||
constants.IMAGE_PROP_VM_GEN_2]
|
||||
else:
|
||||
return [constants.IMAGE_PROP_VM_GEN_1]
|
||||
|
||||
def get_default_vm_generation(self):
|
||||
return self._DEFAULT_VM_GENERATION
|
||||
|
||||
def check_server_feature(self, feature_id):
|
||||
"""Checks if the given feature exists on the host."""
|
||||
return len(self._conn_cimv2.Win32_ServerFeature(ID=feature_id)) > 0
|
||||
|
||||
def get_numa_nodes(self):
|
||||
"""Returns the host's list of NUMA nodes.
|
||||
|
||||
:returns: list of dictionaries containing information about each
|
||||
host NUMA node. Each host has at least one NUMA node.
|
||||
"""
|
||||
numa_nodes = self._conn.Msvm_NumaNode()
|
||||
nodes_info = []
|
||||
system_memory = self._conn.Msvm_Memory(['NumberOfBlocks'])
|
||||
processors = self._conn.Msvm_Processor(['DeviceID'])
|
||||
|
||||
for node in numa_nodes:
|
||||
# Due to a bug in vmms, getting Msvm_Processor for the numa
|
||||
# node associators resulted in a vmms crash.
|
||||
# As an alternative to using associators we have to manually get
|
||||
# the related Msvm_Processor classes.
|
||||
# Msvm_HostedDependency is the association class between
|
||||
# Msvm_NumaNode and Msvm_Processor. We need to use this class to
|
||||
# relate the two because using associators on Msvm_Processor
|
||||
# will also result in a crash.
|
||||
numa_assoc = self._conn.Msvm_HostedDependency(
|
||||
Antecedent=node.path_())
|
||||
numa_node_assoc = [item.Dependent for item in numa_assoc]
|
||||
|
||||
memory_info = self._get_numa_memory_info(numa_node_assoc,
|
||||
system_memory)
|
||||
if not memory_info:
|
||||
LOG.warning("Could not find memory information for NUMA "
|
||||
"node. Skipping node measurements.")
|
||||
continue
|
||||
|
||||
cpu_info = self._get_numa_cpu_info(numa_node_assoc, processors)
|
||||
if not cpu_info:
|
||||
LOG.warning("Could not find CPU information for NUMA "
|
||||
"node. Skipping node measurements.")
|
||||
continue
|
||||
|
||||
node_info = {
|
||||
# NodeID has the format: Microsoft:PhysicalNode\<NODE_ID>
|
||||
'id': node.NodeID.split('\\')[-1],
|
||||
|
||||
# memory block size is 1MB.
|
||||
'memory': memory_info.NumberOfBlocks,
|
||||
'memory_usage': node.CurrentlyConsumableMemoryBlocks,
|
||||
|
||||
# DeviceID has the format: Microsoft:UUID\0\<DEV_ID>
|
||||
'cpuset': set([c.DeviceID.split('\\')[-1] for c in cpu_info]),
|
||||
# cpu_usage can be set, each CPU has a "LoadPercentage"
|
||||
'cpu_usage': 0,
|
||||
}
|
||||
|
||||
nodes_info.append(node_info)
|
||||
|
||||
return nodes_info
|
||||
|
||||
def _get_numa_memory_info(self, numa_node_assoc, system_memory):
|
||||
memory_info = []
|
||||
paths = [x.path_().upper() for x in numa_node_assoc]
|
||||
for memory in system_memory:
|
||||
if memory.path_().upper() in paths:
|
||||
memory_info.append(memory)
|
||||
|
||||
if memory_info:
|
||||
return memory_info[0]
|
||||
|
||||
def _get_numa_cpu_info(self, numa_node_assoc, processors):
|
||||
cpu_info = []
|
||||
paths = [x.path_().upper() for x in numa_node_assoc]
|
||||
for proc in processors:
|
||||
if proc.path_().upper() in paths:
|
||||
cpu_info.append(proc)
|
||||
|
||||
return cpu_info
|
||||
|
||||
def get_remotefx_gpu_info(self):
|
||||
"""Returns information about the GPUs used for RemoteFX.
|
||||
|
||||
:returns: list with dictionaries containing information about each
|
||||
GPU used for RemoteFX.
|
||||
"""
|
||||
gpus = []
|
||||
all_gpus = self._conn.Msvm_Physical3dGraphicsProcessor(
|
||||
EnabledForVirtualization=True)
|
||||
for gpu in all_gpus:
|
||||
gpus.append({'name': gpu.Name,
|
||||
'driver_version': gpu.DriverVersion,
|
||||
'total_video_ram': gpu.TotalVideoMemory,
|
||||
'available_video_ram': gpu.AvailableVideoMemory,
|
||||
'directx_version': gpu.DirectXVersion})
|
||||
return gpus
|
||||
|
||||
def verify_host_remotefx_capability(self):
|
||||
"""Validates that the host supports RemoteFX.
|
||||
|
||||
:raises exceptions.HyperVRemoteFXException: if the host has no GPU
|
||||
that supports DirectX 11, or SLAT.
|
||||
"""
|
||||
synth_3d_video_pool = self._conn.Msvm_Synth3dVideoPool()[0]
|
||||
if not synth_3d_video_pool.IsGpuCapable:
|
||||
raise exceptions.HyperVRemoteFXException(
|
||||
_("To enable RemoteFX on Hyper-V at least one GPU supporting "
|
||||
"DirectX 11 is required."))
|
||||
if not synth_3d_video_pool.IsSlatCapable:
|
||||
raise exceptions.HyperVRemoteFXException(
|
||||
_("To enable RemoteFX on Hyper-V it is required that the host "
|
||||
"GPUs support SLAT."))
|
||||
|
||||
def is_host_guarded(self):
|
||||
"""Checks if the host is guarded.
|
||||
|
||||
:returns: False, only Windows / Hyper-V Server 2016 or newer can be
|
||||
guarded.
|
||||
"""
|
||||
return False
|
||||
|
||||
def supports_nested_virtualization(self):
|
||||
"""Checks if the host supports nested virtualization.
|
||||
|
||||
:returns: False, only Windows / Hyper-V Server 2016 or newer supports
|
||||
nested virtualization.
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_pci_passthrough_devices(self):
|
||||
"""Get host PCI devices path.
|
||||
|
||||
Discrete device assignment is supported only on Windows / Hyper-V
|
||||
Server 2016 or newer.
|
||||
|
||||
:returns: a list of the assignable PCI devices.
|
||||
"""
|
||||
|
||||
return []
|
@ -1,132 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 re
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import exceptions
|
||||
from os_win.utils import hostutils
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostUtils10(hostutils.HostUtils):
|
||||
|
||||
_HGS_NAMESPACE = '//%s/Root/Microsoft/Windows/Hgs'
|
||||
|
||||
_PCI_VENDOR_ID_REGEX = re.compile('VEN_(.*)&DEV')
|
||||
_PCI_PRODUCT_ID_REGEX = re.compile('DEV_(.*)&SUBSYS')
|
||||
_PCI_ADDRESS_REGEX = re.compile(r'\b\d+\b')
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(HostUtils10, self).__init__(host)
|
||||
self._conn_hgs_attr = None
|
||||
|
||||
@property
|
||||
def _conn_hgs(self):
|
||||
if not self._conn_hgs_attr:
|
||||
try:
|
||||
namespace = self._HGS_NAMESPACE % self._host
|
||||
self._conn_hgs_attr = self._get_wmi_conn(namespace)
|
||||
except Exception:
|
||||
raise exceptions.OSWinException(
|
||||
_("Namespace %(namespace)s is not supported on this "
|
||||
"Windows version.") %
|
||||
{'namespace': namespace})
|
||||
|
||||
return self._conn_hgs_attr
|
||||
|
||||
def is_host_guarded(self):
|
||||
"""Checks the host is guarded so it can run Shielded VMs"""
|
||||
|
||||
(return_code,
|
||||
host_config) = self._conn_hgs.MSFT_HgsClientConfiguration.Get()
|
||||
if return_code:
|
||||
LOG.warning('Retrieving the local Host Guardian Service '
|
||||
'Client configuration failed with code: %s',
|
||||
return_code)
|
||||
return False
|
||||
return host_config.IsHostGuarded
|
||||
|
||||
def supports_nested_virtualization(self):
|
||||
"""Checks if the host supports nested virtualization.
|
||||
|
||||
:returns: True, Windows / Hyper-V Server 2016 or newer supports nested
|
||||
virtualization.
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_pci_passthrough_devices(self):
|
||||
"""Get host's assignable PCI devices.
|
||||
|
||||
:returns: a list of the assignable PCI devices.
|
||||
"""
|
||||
# NOTE(claudiub): pci_device_objects contains all available PCI
|
||||
# devices. When a PCI device is used, another object containing the
|
||||
# same devices_instance_path is added.
|
||||
pci_device_objects = self._conn.Msvm_PciExpress()
|
||||
|
||||
pci_devices = []
|
||||
processed_pci_dev_path = []
|
||||
for pci_obj in pci_device_objects:
|
||||
pci_path = pci_obj.DeviceInstancePath
|
||||
if pci_path in processed_pci_dev_path:
|
||||
continue
|
||||
|
||||
address = self._get_pci_device_address(pci_path)
|
||||
vendor_id = self._PCI_VENDOR_ID_REGEX.findall(pci_path)
|
||||
product_id = self._PCI_PRODUCT_ID_REGEX.findall(pci_path)
|
||||
|
||||
if not (address and vendor_id and product_id):
|
||||
# vendor_id / product_id / address not found.
|
||||
# skip this PCI device.
|
||||
continue
|
||||
|
||||
pci_devices.append({
|
||||
'address': address,
|
||||
'vendor_id': vendor_id[0],
|
||||
'product_id': product_id[0],
|
||||
'dev_id': pci_obj.DeviceID,
|
||||
})
|
||||
processed_pci_dev_path.append(pci_path)
|
||||
|
||||
return pci_devices
|
||||
|
||||
def _get_pci_device_address(self, pci_device_path):
|
||||
pnp_device = self._conn_cimv2.Win32_PnPEntity(DeviceID=pci_device_path)
|
||||
(return_code, pnp_device_props) = pnp_device[0].GetDeviceProperties()
|
||||
if return_code:
|
||||
# The properties of the Plug and Play device could not be retrieved
|
||||
LOG.debug('Failed to get PnP Device Properties for the PCI '
|
||||
'device: %(pci_dev)s. (return_code=%(return_code)s',
|
||||
{'pci_dev': pci_device_path, 'return_code': return_code})
|
||||
return None
|
||||
|
||||
pnp_props = {prop.KeyName: prop.Data for prop in pnp_device_props}
|
||||
location_info = pnp_props.get('DEVPKEY_Device_LocationInfo')
|
||||
slot = pnp_props.get('DEVPKEY_Device_Address')
|
||||
|
||||
try:
|
||||
[bus, domain, funct] = self._PCI_ADDRESS_REGEX.findall(
|
||||
location_info)
|
||||
address = "%04x:%02x:%02x.%1x" % (
|
||||
int(domain), int(bus), int(slot), int(funct))
|
||||
return address
|
||||
except Exception as ex:
|
||||
LOG.debug('Failed to get PCI device address. Device path: '
|
||||
'%(device_path)s. Exception: %(ex)s',
|
||||
{'device_path': pci_device_path, 'ex': ex})
|
||||
return None
|
@ -1,220 +0,0 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# 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 ctypes
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from eventlet import patcher
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
kernel32 = w_lib.get_shared_lib_handle(w_lib.KERNEL32)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Avoid using six.moves.queue as we need a non monkey patched class
|
||||
if sys.version_info > (3, 0):
|
||||
Queue = patcher.original('queue')
|
||||
else:
|
||||
Queue = patcher.original('Queue')
|
||||
|
||||
WAIT_PIPE_DEFAULT_TIMEOUT = 5 # seconds
|
||||
WAIT_IO_COMPLETION_TIMEOUT = 2 * units.k
|
||||
WAIT_INFINITE_TIMEOUT = 0xFFFFFFFF
|
||||
|
||||
IO_QUEUE_TIMEOUT = 2
|
||||
IO_QUEUE_BURST_TIMEOUT = 0.05
|
||||
|
||||
|
||||
class IOUtils(object):
|
||||
"""Asyncronous IO helper class."""
|
||||
|
||||
def __init__(self):
|
||||
self._win32_utils = win32utils.Win32Utils()
|
||||
|
||||
def _run_and_check_output(self, *args, **kwargs):
|
||||
eventlet_blocking_mode = kwargs.get('eventlet_nonblocking_mode', False)
|
||||
kwargs.update(kernel32_lib_func=True,
|
||||
failure_exc=exceptions.Win32IOException,
|
||||
eventlet_nonblocking_mode=eventlet_blocking_mode)
|
||||
return self._win32_utils.run_and_check_output(*args, **kwargs)
|
||||
|
||||
@_utils.retry_decorator(exceptions=exceptions.Win32IOException,
|
||||
max_sleep_time=2)
|
||||
def wait_named_pipe(self, pipe_name, timeout=WAIT_PIPE_DEFAULT_TIMEOUT):
|
||||
"""Wait a given amount of time for a pipe to become available."""
|
||||
self._run_and_check_output(kernel32.WaitNamedPipeW,
|
||||
ctypes.c_wchar_p(pipe_name),
|
||||
timeout * units.k)
|
||||
|
||||
def open(self, path, desired_access=None, share_mode=None,
|
||||
creation_disposition=None, flags_and_attributes=None):
|
||||
error_ret_vals = [w_const.INVALID_HANDLE_VALUE]
|
||||
handle = self._run_and_check_output(kernel32.CreateFileW,
|
||||
ctypes.c_wchar_p(path),
|
||||
desired_access,
|
||||
share_mode,
|
||||
None,
|
||||
creation_disposition,
|
||||
flags_and_attributes,
|
||||
None,
|
||||
error_ret_vals=error_ret_vals)
|
||||
return handle
|
||||
|
||||
def close_handle(self, handle):
|
||||
self._run_and_check_output(kernel32.CloseHandle, handle)
|
||||
|
||||
def cancel_io(self, handle, overlapped_structure=None,
|
||||
ignore_invalid_handle=False):
|
||||
"""Cancels pending IO on specified handle.
|
||||
|
||||
If an overlapped structure is passed, only the IO requests that
|
||||
were issued with the specified overlapped structure are canceled.
|
||||
"""
|
||||
# Ignore errors thrown when there are no requests
|
||||
# to be canceled.
|
||||
ignored_error_codes = [w_const.ERROR_NOT_FOUND]
|
||||
if ignore_invalid_handle:
|
||||
ignored_error_codes.append(w_const.ERROR_INVALID_HANDLE)
|
||||
lp_overlapped = (ctypes.byref(overlapped_structure)
|
||||
if overlapped_structure else None)
|
||||
|
||||
self._run_and_check_output(kernel32.CancelIoEx,
|
||||
handle,
|
||||
lp_overlapped,
|
||||
ignored_error_codes=ignored_error_codes)
|
||||
|
||||
def _wait_io_completion(self, event):
|
||||
# In order to cancel this, we simply set the event.
|
||||
self._run_and_check_output(kernel32.WaitForSingleObjectEx,
|
||||
event, WAIT_INFINITE_TIMEOUT,
|
||||
True, error_ret_vals=[w_const.WAIT_FAILED])
|
||||
|
||||
def set_event(self, event):
|
||||
self._run_and_check_output(kernel32.SetEvent, event)
|
||||
|
||||
def _reset_event(self, event):
|
||||
self._run_and_check_output(kernel32.ResetEvent, event)
|
||||
|
||||
def _create_event(self, event_attributes=None, manual_reset=True,
|
||||
initial_state=False, name=None):
|
||||
return self._run_and_check_output(kernel32.CreateEventW,
|
||||
event_attributes, manual_reset,
|
||||
initial_state, name,
|
||||
error_ret_vals=[None])
|
||||
|
||||
def get_completion_routine(self, callback=None):
|
||||
def _completion_routine(error_code, num_bytes, lpOverLapped):
|
||||
"""Sets the completion event and executes callback, if passed."""
|
||||
overlapped = ctypes.cast(lpOverLapped,
|
||||
wintypes.LPOVERLAPPED).contents
|
||||
self.set_event(overlapped.hEvent)
|
||||
|
||||
if callback:
|
||||
callback(num_bytes)
|
||||
|
||||
return wintypes.LPOVERLAPPED_COMPLETION_ROUTINE(_completion_routine)
|
||||
|
||||
def get_new_overlapped_structure(self):
|
||||
"""Structure used for asynchronous IO operations."""
|
||||
# Event used for signaling IO completion
|
||||
hEvent = self._create_event()
|
||||
|
||||
overlapped_structure = wintypes.OVERLAPPED()
|
||||
overlapped_structure.hEvent = hEvent
|
||||
return overlapped_structure
|
||||
|
||||
def read(self, handle, buff, num_bytes,
|
||||
overlapped_structure, completion_routine):
|
||||
self._reset_event(overlapped_structure.hEvent)
|
||||
self._run_and_check_output(kernel32.ReadFileEx,
|
||||
handle, buff, num_bytes,
|
||||
ctypes.byref(overlapped_structure),
|
||||
completion_routine)
|
||||
self._wait_io_completion(overlapped_structure.hEvent)
|
||||
|
||||
def write(self, handle, buff, num_bytes,
|
||||
overlapped_structure, completion_routine):
|
||||
self._reset_event(overlapped_structure.hEvent)
|
||||
self._run_and_check_output(kernel32.WriteFileEx,
|
||||
handle, buff, num_bytes,
|
||||
ctypes.byref(overlapped_structure),
|
||||
completion_routine)
|
||||
self._wait_io_completion(overlapped_structure.hEvent)
|
||||
|
||||
@classmethod
|
||||
def get_buffer(cls, buff_size, data=None):
|
||||
buff = (ctypes.c_ubyte * buff_size)()
|
||||
if data:
|
||||
cls.write_buffer_data(buff, data)
|
||||
return buff
|
||||
|
||||
@staticmethod
|
||||
def get_buffer_data(buff, num_bytes):
|
||||
return bytes(bytearray(buff[:num_bytes]))
|
||||
|
||||
@staticmethod
|
||||
def write_buffer_data(buff, data):
|
||||
for i, c in enumerate(data):
|
||||
buff[i] = struct.unpack('B', six.b(c))[0]
|
||||
|
||||
|
||||
class IOQueue(Queue.Queue):
|
||||
def __init__(self, client_connected):
|
||||
Queue.Queue.__init__(self)
|
||||
self._client_connected = client_connected
|
||||
|
||||
def get(self, timeout=IO_QUEUE_TIMEOUT, continue_on_timeout=True):
|
||||
while self._client_connected.isSet():
|
||||
try:
|
||||
return Queue.Queue.get(self, timeout=timeout)
|
||||
except Queue.Empty:
|
||||
if continue_on_timeout:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
def put(self, item, timeout=IO_QUEUE_TIMEOUT):
|
||||
while self._client_connected.isSet():
|
||||
try:
|
||||
return Queue.Queue.put(self, item, timeout=timeout)
|
||||
except Queue.Full:
|
||||
continue
|
||||
|
||||
def get_burst(self, timeout=IO_QUEUE_TIMEOUT,
|
||||
burst_timeout=IO_QUEUE_BURST_TIMEOUT,
|
||||
max_size=constants.SERIAL_CONSOLE_BUFFER_SIZE):
|
||||
# Get as much data as possible from the queue
|
||||
# to avoid sending small chunks.
|
||||
data = self.get(timeout=timeout)
|
||||
|
||||
while data and len(data) <= max_size:
|
||||
chunk = self.get(timeout=burst_timeout,
|
||||
continue_on_timeout=False)
|
||||
if chunk:
|
||||
data += chunk
|
||||
else:
|
||||
break
|
||||
return data
|
@ -1,254 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 errno
|
||||
import os
|
||||
|
||||
from eventlet import patcher
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils.io import ioutils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
|
||||
threading = patcher.original('threading')
|
||||
time = patcher.original('time')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NamedPipeHandler(object):
|
||||
"""Handles asynchronous I/O operations on a specified named pipe."""
|
||||
|
||||
_MAX_LOG_ROTATE_RETRIES = 5
|
||||
|
||||
def __init__(self, pipe_name, input_queue=None, output_queue=None,
|
||||
connect_event=None, log_file=None):
|
||||
self._pipe_name = pipe_name
|
||||
self._input_queue = input_queue
|
||||
self._output_queue = output_queue
|
||||
self._log_file_path = log_file
|
||||
|
||||
self._connect_event = connect_event
|
||||
self._stopped = threading.Event()
|
||||
self._workers = []
|
||||
self._pipe_handle = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self._ioutils = ioutils.IOUtils()
|
||||
|
||||
self._setup_io_structures()
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self._open_pipe()
|
||||
|
||||
if self._log_file_path:
|
||||
self._log_file_handle = open(self._log_file_path, 'ab', 1)
|
||||
|
||||
jobs = [self._read_from_pipe]
|
||||
if self._input_queue and self._connect_event:
|
||||
jobs.append(self._write_to_pipe)
|
||||
|
||||
for job in jobs:
|
||||
worker = threading.Thread(target=job)
|
||||
worker.setDaemon(True)
|
||||
worker.start()
|
||||
self._workers.append(worker)
|
||||
except Exception as err:
|
||||
msg = (_("Named pipe handler failed to initialize. "
|
||||
"Pipe Name: %(pipe_name)s "
|
||||
"Error: %(err)s") %
|
||||
{'pipe_name': self._pipe_name,
|
||||
'err': err})
|
||||
LOG.error(msg)
|
||||
self.stop()
|
||||
raise exceptions.OSWinException(msg)
|
||||
|
||||
def stop(self):
|
||||
self._stopped.set()
|
||||
|
||||
# If any worker has been spawned already, we rely on it to have
|
||||
# cleaned up the handles before ending its execution.
|
||||
# Note that we expect the caller to synchronize the start/stop calls.
|
||||
if not self._workers:
|
||||
self._cleanup_handles()
|
||||
|
||||
for worker in self._workers:
|
||||
# It may happen that another IO request was issued right after
|
||||
# we've set the stopped event and canceled pending requests.
|
||||
# In this case, retrying will ensure that the IO workers are
|
||||
# stopped properly and that there are no more outstanding IO
|
||||
# operations.
|
||||
while (worker.is_alive() and
|
||||
worker is not threading.current_thread()):
|
||||
self._cancel_io()
|
||||
worker.join(0.5)
|
||||
|
||||
self._workers = []
|
||||
|
||||
def _cleanup_handles(self):
|
||||
self._close_pipe()
|
||||
|
||||
if self._log_file_handle:
|
||||
self._log_file_handle.close()
|
||||
self._log_file_handle = None
|
||||
|
||||
if self._r_overlapped.hEvent:
|
||||
self._ioutils.close_handle(self._r_overlapped.hEvent)
|
||||
self._r_overlapped.hEvent = None
|
||||
|
||||
if self._w_overlapped.hEvent:
|
||||
self._ioutils.close_handle(self._w_overlapped.hEvent)
|
||||
self._w_overlapped.hEvent = None
|
||||
|
||||
def _setup_io_structures(self):
|
||||
self._r_buffer = self._ioutils.get_buffer(
|
||||
constants.SERIAL_CONSOLE_BUFFER_SIZE)
|
||||
self._w_buffer = self._ioutils.get_buffer(
|
||||
constants.SERIAL_CONSOLE_BUFFER_SIZE)
|
||||
|
||||
self._r_overlapped = self._ioutils.get_new_overlapped_structure()
|
||||
self._w_overlapped = self._ioutils.get_new_overlapped_structure()
|
||||
|
||||
self._r_completion_routine = self._ioutils.get_completion_routine(
|
||||
self._read_callback)
|
||||
self._w_completion_routine = self._ioutils.get_completion_routine()
|
||||
|
||||
self._log_file_handle = None
|
||||
|
||||
def _open_pipe(self):
|
||||
"""Opens a named pipe in overlapped mode for asyncronous I/O."""
|
||||
self._ioutils.wait_named_pipe(self._pipe_name)
|
||||
|
||||
self._pipe_handle = self._ioutils.open(
|
||||
self._pipe_name,
|
||||
desired_access=(w_const.GENERIC_READ | w_const.GENERIC_WRITE),
|
||||
share_mode=(w_const.FILE_SHARE_READ | w_const.FILE_SHARE_WRITE),
|
||||
creation_disposition=w_const.OPEN_EXISTING,
|
||||
flags_and_attributes=w_const.FILE_FLAG_OVERLAPPED)
|
||||
|
||||
def _close_pipe(self):
|
||||
if self._pipe_handle:
|
||||
self._ioutils.close_handle(self._pipe_handle)
|
||||
self._pipe_handle = None
|
||||
|
||||
def _cancel_io(self):
|
||||
if self._pipe_handle:
|
||||
# We ignore invalid handle errors. Even if the pipe is closed
|
||||
# and the handle reused, by specifying the overlapped structures
|
||||
# we ensure that we don't cancel IO operations other than the
|
||||
# ones that we care about.
|
||||
self._ioutils.cancel_io(self._pipe_handle, self._r_overlapped,
|
||||
ignore_invalid_handle=True)
|
||||
self._ioutils.cancel_io(self._pipe_handle, self._w_overlapped,
|
||||
ignore_invalid_handle=True)
|
||||
|
||||
def _read_from_pipe(self):
|
||||
self._start_io_worker(self._ioutils.read,
|
||||
self._r_buffer,
|
||||
self._r_overlapped,
|
||||
self._r_completion_routine)
|
||||
|
||||
def _write_to_pipe(self):
|
||||
self._start_io_worker(self._ioutils.write,
|
||||
self._w_buffer,
|
||||
self._w_overlapped,
|
||||
self._w_completion_routine,
|
||||
self._get_data_to_write)
|
||||
|
||||
def _start_io_worker(self, func, buff, overlapped_structure,
|
||||
completion_routine, buff_update_func=None):
|
||||
try:
|
||||
while not self._stopped.isSet():
|
||||
if buff_update_func:
|
||||
num_bytes = buff_update_func()
|
||||
if not num_bytes:
|
||||
continue
|
||||
else:
|
||||
num_bytes = len(buff)
|
||||
|
||||
func(self._pipe_handle, buff, num_bytes,
|
||||
overlapped_structure, completion_routine)
|
||||
except Exception:
|
||||
self._stopped.set()
|
||||
finally:
|
||||
with self._lock:
|
||||
self._cleanup_handles()
|
||||
|
||||
def _read_callback(self, num_bytes):
|
||||
data = self._ioutils.get_buffer_data(self._r_buffer,
|
||||
num_bytes)
|
||||
if self._output_queue:
|
||||
self._output_queue.put(data)
|
||||
|
||||
if self._log_file_handle:
|
||||
self._write_to_log(data)
|
||||
|
||||
def _get_data_to_write(self):
|
||||
while not (self._stopped.isSet() or self._connect_event.isSet()):
|
||||
time.sleep(1)
|
||||
|
||||
data = self._input_queue.get()
|
||||
if data:
|
||||
self._ioutils.write_buffer_data(self._w_buffer, data)
|
||||
return len(data)
|
||||
return 0
|
||||
|
||||
def _write_to_log(self, data):
|
||||
if self._stopped.isSet():
|
||||
return
|
||||
|
||||
try:
|
||||
log_size = self._log_file_handle.tell() + len(data)
|
||||
if log_size >= constants.MAX_CONSOLE_LOG_FILE_SIZE:
|
||||
self._rotate_logs()
|
||||
self._log_file_handle.write(data)
|
||||
except Exception:
|
||||
self._stopped.set()
|
||||
|
||||
def _rotate_logs(self):
|
||||
self._log_file_handle.flush()
|
||||
self._log_file_handle.close()
|
||||
|
||||
log_archive_path = self._log_file_path + '.1'
|
||||
|
||||
if os.path.exists(log_archive_path):
|
||||
self._retry_if_file_in_use(os.remove,
|
||||
log_archive_path)
|
||||
|
||||
self._retry_if_file_in_use(os.rename,
|
||||
self._log_file_path,
|
||||
log_archive_path)
|
||||
|
||||
self._log_file_handle = open(
|
||||
self._log_file_path, 'ab', 1)
|
||||
|
||||
def _retry_if_file_in_use(self, f, *args, **kwargs):
|
||||
# The log files might be in use if the console log is requested
|
||||
# while a log rotation is attempted.
|
||||
retry_count = 0
|
||||
while True:
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except WindowsError as err:
|
||||
if (err.errno == errno.EACCES and
|
||||
retry_count < self._MAX_LOG_ROTATE_RETRIES):
|
||||
retry_count += 1
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise
|
@ -1,225 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base Utility class for operations on Hyper-V.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win import _utils
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JobUtils(baseutils.BaseUtilsVirt):
|
||||
|
||||
_CONCRETE_JOB_CLASS = "Msvm_ConcreteJob"
|
||||
|
||||
_DEFAULT_JOB_TERMINATE_TIMEOUT = 15 # seconds
|
||||
_KILL_JOB_STATE_CHANGE_REQUEST = 5
|
||||
|
||||
_completed_job_states = [constants.JOB_STATE_COMPLETED,
|
||||
constants.JOB_STATE_TERMINATED,
|
||||
constants.JOB_STATE_KILLED,
|
||||
constants.JOB_STATE_COMPLETED_WITH_WARNINGS,
|
||||
constants.JOB_STATE_EXCEPTION]
|
||||
_successful_job_states = [constants.JOB_STATE_COMPLETED,
|
||||
constants.JOB_STATE_COMPLETED_WITH_WARNINGS]
|
||||
|
||||
def check_ret_val(self, ret_val, job_path, success_values=[0]):
|
||||
"""Checks that the job represented by the given arguments succeeded.
|
||||
|
||||
Some Hyper-V operations are not atomic, and will return a reference
|
||||
to a job. In this case, this method will wait for the job's
|
||||
completion.
|
||||
|
||||
:param ret_val: integer, representing the return value of the job.
|
||||
if the value is WMI_JOB_STATUS_STARTED or WMI_JOB_STATE_RUNNING,
|
||||
a job_path cannot be None.
|
||||
:param job_path: string representing the WMI object path of a
|
||||
Hyper-V job.
|
||||
:param success_values: list of return values that can be considered
|
||||
successful. WMI_JOB_STATUS_STARTED and WMI_JOB_STATE_RUNNING
|
||||
values are ignored.
|
||||
:raises exceptions.WMIJobFailed: if the given ret_val is
|
||||
WMI_JOB_STATUS_STARTED or WMI_JOB_STATE_RUNNING and the state of
|
||||
job represented by the given job_path is not
|
||||
WMI_JOB_STATE_COMPLETED or JOB_STATE_COMPLETED_WITH_WARNINGS, or
|
||||
if the given ret_val is not in the list of given success_values.
|
||||
"""
|
||||
if ret_val in [constants.WMI_JOB_STATUS_STARTED,
|
||||
constants.WMI_JOB_STATE_RUNNING]:
|
||||
return self._wait_for_job(job_path)
|
||||
elif ret_val not in success_values:
|
||||
raise exceptions.WMIJobFailed(error_code=ret_val,
|
||||
job_state=None,
|
||||
error_summ_desc=None,
|
||||
error_desc=None)
|
||||
|
||||
def _wait_for_job(self, job_path):
|
||||
"""Poll WMI job state and wait for completion."""
|
||||
|
||||
job_wmi_path = job_path.replace('\\', '/')
|
||||
job = self._get_wmi_obj(job_wmi_path)
|
||||
|
||||
while not self._is_job_completed(job):
|
||||
time.sleep(0.1)
|
||||
job = self._get_wmi_obj(job_wmi_path)
|
||||
|
||||
job_state = job.JobState
|
||||
err_code = job.ErrorCode
|
||||
|
||||
# We'll raise an exception for killed jobs.
|
||||
job_failed = job_state not in self._successful_job_states or err_code
|
||||
if job_failed:
|
||||
err_sum_desc = getattr(job, 'ErrorSummaryDescription', None)
|
||||
err_desc = job.ErrorDescription
|
||||
|
||||
raise exceptions.WMIJobFailed(job_state=job_state,
|
||||
error_code=err_code,
|
||||
error_summ_desc=err_sum_desc,
|
||||
error_desc=err_desc)
|
||||
|
||||
if job_state == constants.JOB_STATE_COMPLETED_WITH_WARNINGS:
|
||||
LOG.warning("WMI job completed with warnings. For detailed "
|
||||
"information, please check the Windows event logs.")
|
||||
|
||||
desc = job.Description
|
||||
elap = job.ElapsedTime
|
||||
LOG.debug("WMI job succeeded: %(desc)s, Elapsed=%(elap)s",
|
||||
{'desc': desc, 'elap': elap})
|
||||
return job
|
||||
|
||||
def _get_pending_jobs_affecting_element(self, element):
|
||||
# Msvm_AffectedJobElement is in fact an association between
|
||||
# the affected element and the affecting job.
|
||||
mappings = self._conn.Msvm_AffectedJobElement(
|
||||
AffectedElement=element.path_())
|
||||
pending_jobs = []
|
||||
for mapping in mappings:
|
||||
try:
|
||||
if mapping.AffectingElement and not self._is_job_completed(
|
||||
mapping.AffectingElement):
|
||||
pending_jobs.append(mapping.AffectingElement)
|
||||
|
||||
except exceptions.x_wmi as ex:
|
||||
# NOTE(claudiub): we can ignore "Not found" type exceptions.
|
||||
if not _utils._is_not_found_exc(ex):
|
||||
raise
|
||||
|
||||
return pending_jobs
|
||||
|
||||
def _stop_jobs(self, element):
|
||||
pending_jobs = self._get_pending_jobs_affecting_element(element)
|
||||
for job in pending_jobs:
|
||||
try:
|
||||
if not job.Cancellable:
|
||||
LOG.debug("Got request to terminate "
|
||||
"non-cancelable job.")
|
||||
continue
|
||||
|
||||
job.RequestStateChange(
|
||||
self._KILL_JOB_STATE_CHANGE_REQUEST)
|
||||
except exceptions.x_wmi as ex:
|
||||
# The job may had been completed right before we've
|
||||
# attempted to kill it.
|
||||
if not _utils._is_not_found_exc(ex):
|
||||
LOG.debug("Failed to stop job. Exception: %s", ex)
|
||||
|
||||
pending_jobs = self._get_pending_jobs_affecting_element(element)
|
||||
if pending_jobs:
|
||||
LOG.debug("Attempted to terminate jobs "
|
||||
"affecting element %(element)s but "
|
||||
"%(pending_count)s jobs are still pending.",
|
||||
dict(element=element,
|
||||
pending_count=len(pending_jobs)))
|
||||
raise exceptions.JobTerminateFailed()
|
||||
|
||||
def _is_job_completed(self, job):
|
||||
return job.JobState in self._completed_job_states
|
||||
|
||||
def stop_jobs(self, element, timeout=_DEFAULT_JOB_TERMINATE_TIMEOUT):
|
||||
"""Stops the Hyper-V jobs associated with the given resource.
|
||||
|
||||
:param element: string representing the path of the Hyper-V resource
|
||||
whose jobs will be stopped.
|
||||
:param timeout: the maximum amount of time allowed to stop all the
|
||||
given resource's jobs.
|
||||
:raises exceptions.JobTerminateFailed: if there are still pending jobs
|
||||
associated with the given resource and the given timeout amount of
|
||||
time has passed.
|
||||
"""
|
||||
@_utils.retry_decorator(exceptions=exceptions.JobTerminateFailed,
|
||||
timeout=timeout, max_retry_count=None)
|
||||
def _stop_jobs_with_timeout():
|
||||
self._stop_jobs(element)
|
||||
|
||||
_stop_jobs_with_timeout()
|
||||
|
||||
@_utils.not_found_decorator()
|
||||
@_utils.retry_decorator(exceptions=exceptions.HyperVException)
|
||||
def add_virt_resource(self, virt_resource, parent):
|
||||
(job_path, new_resources,
|
||||
ret_val) = self._vs_man_svc.AddResourceSettings(
|
||||
parent.path_(), [virt_resource.GetText_(1)])
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
return new_resources
|
||||
|
||||
# modify_virt_resource can fail, especially while setting up the VM's
|
||||
# serial port connection. Retrying the operation will yield success.
|
||||
@_utils.not_found_decorator()
|
||||
@_utils.retry_decorator(exceptions=exceptions.HyperVException)
|
||||
def modify_virt_resource(self, virt_resource):
|
||||
(job_path, out_set_data,
|
||||
ret_val) = self._vs_man_svc.ModifyResourceSettings(
|
||||
ResourceSettings=[virt_resource.GetText_(1)])
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def remove_virt_resource(self, virt_resource):
|
||||
self.remove_multiple_virt_resources([virt_resource])
|
||||
|
||||
@_utils.not_found_decorator()
|
||||
@_utils.retry_decorator(exceptions=exceptions.HyperVException)
|
||||
def remove_multiple_virt_resources(self, virt_resources):
|
||||
(job, ret_val) = self._vs_man_svc.RemoveResourceSettings(
|
||||
ResourceSettings=[r.path_() for r in virt_resources])
|
||||
self.check_ret_val(ret_val, job)
|
||||
|
||||
def add_virt_feature(self, virt_feature, parent):
|
||||
self.add_multiple_virt_features([virt_feature], parent)
|
||||
|
||||
@_utils.not_found_decorator()
|
||||
@_utils.retry_decorator(exceptions=exceptions.HyperVException)
|
||||
def add_multiple_virt_features(self, virt_features, parent):
|
||||
(job_path, out_set_data,
|
||||
ret_val) = self._vs_man_svc.AddFeatureSettings(
|
||||
parent.path_(), [f.GetText_(1) for f in virt_features])
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def remove_virt_feature(self, virt_feature):
|
||||
self.remove_multiple_virt_features([virt_feature])
|
||||
|
||||
@_utils.not_found_decorator()
|
||||
def remove_multiple_virt_features(self, virt_features):
|
||||
(job_path, ret_val) = self._vs_man_svc.RemoveFeatureSettings(
|
||||
FeatureSettings=[f.path_() for f in virt_features])
|
||||
self.check_ret_val(ret_val, job_path)
|
@ -1,285 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Utility class for metrics related operations.
|
||||
Based on the "root/virtualization/v2" namespace available starting with
|
||||
Hyper-V Server / Windows Server 2012.
|
||||
"""
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import exceptions
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils import baseutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MetricsUtils(baseutils.BaseUtilsVirt):
|
||||
|
||||
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
|
||||
_DVD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual CD/DVD Disk'
|
||||
_STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData'
|
||||
_PROCESSOR_SETTING_DATA_CLASS = 'Msvm_ProcessorSettingData'
|
||||
_SYNTH_ETH_PORT_SET_DATA = 'Msvm_SyntheticEthernetPortSettingData'
|
||||
_PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData'
|
||||
_PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData'
|
||||
_BASE_METRICS_VALUE = 'Msvm_BaseMetricValue'
|
||||
|
||||
_CPU_METRICS = 'Aggregated Average CPU Utilization'
|
||||
_MEMORY_METRICS = 'Aggregated Average Memory Utilization'
|
||||
_NET_IN_METRICS = 'Filtered Incoming Network Traffic'
|
||||
_NET_OUT_METRICS = 'Filtered Outgoing Network Traffic'
|
||||
# Disk metrics are supported from Hyper-V 2012 R2
|
||||
_DISK_RD_METRICS = 'Disk Data Read'
|
||||
_DISK_WR_METRICS = 'Disk Data Written'
|
||||
_DISK_LATENCY_METRICS = 'Average Disk Latency'
|
||||
_DISK_IOPS_METRICS = 'Average Normalized Disk Throughput'
|
||||
|
||||
_METRICS_ENABLED = 2
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(MetricsUtils, self).__init__(host)
|
||||
self._metrics_svc_obj = None
|
||||
self._metrics_defs_obj = {}
|
||||
|
||||
@property
|
||||
def _metrics_svc(self):
|
||||
if not self._metrics_svc_obj:
|
||||
self._metrics_svc_obj = self._compat_conn.Msvm_MetricService()[0]
|
||||
return self._metrics_svc_obj
|
||||
|
||||
@property
|
||||
def _metrics_defs(self):
|
||||
if not self._metrics_defs_obj:
|
||||
self._cache_metrics_defs()
|
||||
return self._metrics_defs_obj
|
||||
|
||||
def _cache_metrics_defs(self):
|
||||
for metrics_def in self._conn.CIM_BaseMetricDefinition():
|
||||
self._metrics_defs_obj[metrics_def.ElementName] = metrics_def
|
||||
|
||||
def enable_vm_metrics_collection(self, vm_name):
|
||||
vm = self._get_vm(vm_name)
|
||||
disks = self._get_vm_resources(vm_name,
|
||||
self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
filtered_disks = [d for d in disks if
|
||||
d.ResourceSubType is not self._DVD_DISK_RES_SUB_TYPE]
|
||||
|
||||
# enable metrics for disk.
|
||||
for disk in filtered_disks:
|
||||
self._enable_metrics(disk)
|
||||
|
||||
metrics_names = [self._CPU_METRICS, self._MEMORY_METRICS]
|
||||
self._enable_metrics(vm, metrics_names)
|
||||
|
||||
def enable_port_metrics_collection(self, switch_port_name):
|
||||
port = self._get_switch_port(switch_port_name)
|
||||
metrics_names = [self._NET_IN_METRICS, self._NET_OUT_METRICS]
|
||||
self._enable_metrics(port, metrics_names)
|
||||
|
||||
def _enable_metrics(self, element, metrics_names=None):
|
||||
if not metrics_names:
|
||||
definition_paths = [None]
|
||||
else:
|
||||
definition_paths = []
|
||||
for metrics_name in metrics_names:
|
||||
metrics_def = self._metrics_defs.get(metrics_name)
|
||||
if not metrics_def:
|
||||
LOG.warning("Metric not found: %s", metrics_name)
|
||||
continue
|
||||
definition_paths.append(metrics_def.path_())
|
||||
|
||||
element_path = element.path_()
|
||||
for definition_path in definition_paths:
|
||||
self._metrics_svc.ControlMetrics(
|
||||
Subject=element_path,
|
||||
Definition=definition_path,
|
||||
MetricCollectionEnabled=self._METRICS_ENABLED)
|
||||
|
||||
def get_cpu_metrics(self, vm_name):
|
||||
vm = self._get_vm(vm_name)
|
||||
cpu_sd = self._get_vm_resources(vm_name,
|
||||
self._PROCESSOR_SETTING_DATA_CLASS)[0]
|
||||
cpu_metrics_def = self._metrics_defs[self._CPU_METRICS]
|
||||
cpu_metrics_aggr = self._get_metrics(vm, cpu_metrics_def)
|
||||
|
||||
cpu_used = 0
|
||||
if cpu_metrics_aggr:
|
||||
cpu_used = int(cpu_metrics_aggr[0].MetricValue)
|
||||
|
||||
return (cpu_used,
|
||||
int(cpu_sd.VirtualQuantity),
|
||||
int(vm.OnTimeInMilliseconds))
|
||||
|
||||
def get_memory_metrics(self, vm_name):
|
||||
vm = self._get_vm(vm_name)
|
||||
memory_def = self._metrics_defs[self._MEMORY_METRICS]
|
||||
metrics_memory = self._get_metrics(vm, memory_def)
|
||||
memory_usage = 0
|
||||
if metrics_memory:
|
||||
memory_usage = int(metrics_memory[0].MetricValue)
|
||||
return memory_usage
|
||||
|
||||
def get_vnic_metrics(self, vm_name):
|
||||
ports = self._get_vm_resources(vm_name, self._PORT_ALLOC_SET_DATA)
|
||||
vnics = self._get_vm_resources(vm_name, self._SYNTH_ETH_PORT_SET_DATA)
|
||||
|
||||
metrics_def_in = self._metrics_defs[self._NET_IN_METRICS]
|
||||
metrics_def_out = self._metrics_defs[self._NET_OUT_METRICS]
|
||||
|
||||
for port in ports:
|
||||
vnic = [v for v in vnics if port.Parent == v.path_()][0]
|
||||
port_acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_ALLOC_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
|
||||
metrics_value_instances = self._get_metrics_value_instances(
|
||||
port_acls, self._BASE_METRICS_VALUE)
|
||||
metrics_values = self._sum_metrics_values_by_defs(
|
||||
metrics_value_instances, [metrics_def_in, metrics_def_out])
|
||||
|
||||
yield {
|
||||
'rx_mb': metrics_values[0],
|
||||
'tx_mb': metrics_values[1],
|
||||
'element_name': vnic.ElementName,
|
||||
'address': vnic.Address
|
||||
}
|
||||
|
||||
def get_disk_metrics(self, vm_name):
|
||||
metrics_def_r = self._metrics_defs[self._DISK_RD_METRICS]
|
||||
metrics_def_w = self._metrics_defs[self._DISK_WR_METRICS]
|
||||
|
||||
disks = self._get_vm_resources(vm_name,
|
||||
self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
for disk in disks:
|
||||
metrics_values = self._get_metrics_values(
|
||||
disk, [metrics_def_r, metrics_def_w])
|
||||
|
||||
yield {
|
||||
# Values are in megabytes
|
||||
'read_mb': metrics_values[0],
|
||||
'write_mb': metrics_values[1],
|
||||
'instance_id': disk.InstanceID,
|
||||
'host_resource': disk.HostResource[0]
|
||||
}
|
||||
|
||||
def get_disk_latency_metrics(self, vm_name):
|
||||
metrics_latency_def = self._metrics_defs[self._DISK_LATENCY_METRICS]
|
||||
|
||||
disks = self._get_vm_resources(vm_name,
|
||||
self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
for disk in disks:
|
||||
metrics_values = self._get_metrics_values(
|
||||
disk, [metrics_latency_def])
|
||||
|
||||
yield {
|
||||
'disk_latency': metrics_values[0],
|
||||
'instance_id': disk.InstanceID,
|
||||
}
|
||||
|
||||
def get_disk_iops_count(self, vm_name):
|
||||
metrics_def_iops = self._metrics_defs[self._DISK_IOPS_METRICS]
|
||||
|
||||
disks = self._get_vm_resources(vm_name,
|
||||
self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
for disk in disks:
|
||||
metrics_values = self._get_metrics_values(
|
||||
disk, [metrics_def_iops])
|
||||
|
||||
yield {
|
||||
'iops_count': metrics_values[0],
|
||||
'instance_id': disk.InstanceID,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _sum_metrics_values(metrics):
|
||||
return sum([int(metric.MetricValue) for metric in metrics])
|
||||
|
||||
def _sum_metrics_values_by_defs(self, element_metrics, metrics_defs):
|
||||
metrics_values = []
|
||||
for metrics_def in metrics_defs:
|
||||
if metrics_def:
|
||||
metrics = self._filter_metrics(element_metrics, metrics_def)
|
||||
metrics_values.append(self._sum_metrics_values(metrics))
|
||||
else:
|
||||
# In case the metric is not defined on this host
|
||||
metrics_values.append(0)
|
||||
return metrics_values
|
||||
|
||||
def _get_metrics_value_instances(self, elements, result_class):
|
||||
instances = []
|
||||
for el in elements:
|
||||
# NOTE(abalutoiu): Msvm_MetricForME is the association between
|
||||
# an element and all the metric values maintained for it.
|
||||
el_metric = [
|
||||
x.Dependent for x in self._conn.Msvm_MetricForME(
|
||||
Antecedent=el.path_())]
|
||||
el_metric = [
|
||||
x for x in el_metric if x.path().Class == result_class]
|
||||
if el_metric:
|
||||
instances.append(el_metric[0])
|
||||
|
||||
return instances
|
||||
|
||||
def _get_metrics_values(self, element, metrics_defs):
|
||||
element_metrics = [
|
||||
x.Dependent for x in self._conn.Msvm_MetricForME(
|
||||
Antecedent=element.path_())]
|
||||
return self._sum_metrics_values_by_defs(element_metrics, metrics_defs)
|
||||
|
||||
def _get_metrics(self, element, metrics_def):
|
||||
metrics = [
|
||||
x.Dependent for x in self._conn.Msvm_MetricForME(
|
||||
Antecedent=element.path_())]
|
||||
return self._filter_metrics(metrics, metrics_def)
|
||||
|
||||
@staticmethod
|
||||
def _filter_metrics(all_metrics, metrics_def):
|
||||
return [v for v in all_metrics if
|
||||
v.MetricDefinitionId == metrics_def.Id]
|
||||
|
||||
def _get_vm_resources(self, vm_name, resource_class):
|
||||
setting_data = self._get_vm_setting_data(vm_name)
|
||||
return _wqlutils.get_element_associated_class(
|
||||
self._conn, resource_class,
|
||||
element_instance_id=setting_data.InstanceID)
|
||||
|
||||
def _get_vm(self, vm_name):
|
||||
vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
|
||||
return self._unique_result(vms, vm_name)
|
||||
|
||||
def _get_switch_port(self, port_name):
|
||||
ports = self._conn.Msvm_SyntheticEthernetPortSettingData(
|
||||
ElementName=port_name)
|
||||
return self._unique_result(ports, port_name)
|
||||
|
||||
def _get_vm_setting_data(self, vm_name):
|
||||
vssds = self._conn.Msvm_VirtualSystemSettingData(
|
||||
ElementName=vm_name)
|
||||
return self._unique_result(vssds, vm_name)
|
||||
|
||||
@staticmethod
|
||||
def _unique_result(objects, resource_name):
|
||||
n = len(objects)
|
||||
if n == 0:
|
||||
raise exceptions.NotFound(resource=resource_name)
|
||||
elif n > 1:
|
||||
raise exceptions.OSWinException(
|
||||
_('Duplicate resource name found: %s') % resource_name)
|
||||
else:
|
||||
return objects[0]
|
@ -1,911 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Utility class for network related operations.
|
||||
Based on the "root/virtualization/v2" namespace available starting with
|
||||
Hyper-V Server / Windows Server 2012.
|
||||
"""
|
||||
import functools
|
||||
import re
|
||||
|
||||
from eventlet import patcher
|
||||
from eventlet import tpool
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
import six
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import conf
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import _wqlutils
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils import jobutils
|
||||
|
||||
CONF = conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_PORT_PROFILE_ATTR_MAP = {
|
||||
"profile_id": "ProfileId",
|
||||
"profile_data": "ProfileData",
|
||||
"profile_name": "ProfileName",
|
||||
"net_cfg_instance_id": "NetCfgInstanceId",
|
||||
"cdn_label_id": "CdnLabelId",
|
||||
"cdn_label_string": "CdnLabelString",
|
||||
"vendor_id": "VendorId",
|
||||
"vendor_name": "VendorName",
|
||||
}
|
||||
|
||||
|
||||
class NetworkUtils(baseutils.BaseUtilsVirt):
|
||||
|
||||
EVENT_TYPE_CREATE = "__InstanceCreationEvent"
|
||||
EVENT_TYPE_DELETE = "__InstanceDeletionEvent"
|
||||
|
||||
_VNIC_SET_DATA = 'Msvm_SyntheticEthernetPortSettingData'
|
||||
_EXTERNAL_PORT = 'Msvm_ExternalEthernetPort'
|
||||
_ETHERNET_SWITCH_PORT = 'Msvm_EthernetSwitchPort'
|
||||
_PORT_ALLOC_SET_DATA = 'Msvm_EthernetPortAllocationSettingData'
|
||||
_PORT_VLAN_SET_DATA = 'Msvm_EthernetSwitchPortVlanSettingData'
|
||||
_PORT_PROFILE_SET_DATA = 'Msvm_EthernetSwitchPortProfileSettingData'
|
||||
_PORT_SECURITY_SET_DATA = 'Msvm_EthernetSwitchPortSecuritySettingData'
|
||||
_PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData'
|
||||
_PORT_BANDWIDTH_SET_DATA = 'Msvm_EthernetSwitchPortBandwidthSettingData'
|
||||
_PORT_EXT_ACL_SET_DATA = _PORT_ALLOC_ACL_SET_DATA
|
||||
_LAN_ENDPOINT = 'Msvm_LANEndpoint'
|
||||
_STATE_DISABLED = 3
|
||||
|
||||
_VIRTUAL_SYSTEM_SETTING_DATA = 'Msvm_VirtualSystemSettingData'
|
||||
_VM_SUMMARY_ENABLED_STATE = 100
|
||||
_HYPERV_VM_STATE_ENABLED = 2
|
||||
|
||||
_ACL_DIR_IN = 1
|
||||
_ACL_DIR_OUT = 2
|
||||
|
||||
_ACL_TYPE_IPV4 = 2
|
||||
_ACL_TYPE_IPV6 = 3
|
||||
|
||||
_ACL_ACTION_ALLOW = 1
|
||||
_ACL_ACTION_DENY = 2
|
||||
_ACL_ACTION_METER = 3
|
||||
|
||||
_ACL_APPLICABILITY_LOCAL = 1
|
||||
_ACL_APPLICABILITY_REMOTE = 2
|
||||
|
||||
_ACL_DEFAULT = 'ANY'
|
||||
_IPV4_ANY = '0.0.0.0/0'
|
||||
_IPV6_ANY = '::/0'
|
||||
_TCP_PROTOCOL = 'tcp'
|
||||
_UDP_PROTOCOL = 'udp'
|
||||
_ICMP_PROTOCOL = '1'
|
||||
_ICMPV6_PROTOCOL = '58'
|
||||
_MAX_WEIGHT = 65500
|
||||
|
||||
# 2 directions x 2 address types = 4 ACLs
|
||||
_REJECT_ACLS_COUNT = 4
|
||||
|
||||
_VNIC_LISTENER_TIMEOUT_MS = 2000
|
||||
|
||||
_switches = {}
|
||||
_switch_ports = {}
|
||||
_vlan_sds = {}
|
||||
_profile_sds = {}
|
||||
_vsid_sds = {}
|
||||
_sg_acl_sds = {}
|
||||
_bandwidth_sds = {}
|
||||
|
||||
def __init__(self):
|
||||
super(NetworkUtils, self).__init__()
|
||||
self._jobutils = jobutils.JobUtils()
|
||||
self._enable_cache = CONF.os_win.cache_temporary_wmi_objects
|
||||
|
||||
def init_caches(self):
|
||||
if not self._enable_cache:
|
||||
LOG.info('WMI caching is disabled.')
|
||||
return
|
||||
|
||||
for vswitch in self._conn.Msvm_VirtualEthernetSwitch():
|
||||
self._switches[vswitch.ElementName] = vswitch
|
||||
|
||||
# map between switch port ID and switch port WMI object.
|
||||
for port in self._conn.Msvm_EthernetPortAllocationSettingData():
|
||||
self._switch_ports[port.ElementName] = port
|
||||
|
||||
# VLAN and VSID setting data's InstanceID will contain the switch
|
||||
# port's InstanceID.
|
||||
switch_port_id_regex = re.compile(
|
||||
"Microsoft:[0-9A-F-]*\\\\[0-9A-F-]*\\\\[0-9A-F-]",
|
||||
flags=re.IGNORECASE)
|
||||
|
||||
# map between switch port's InstanceID and their Port Profile settings
|
||||
# data WMI objects.
|
||||
for profile in self._conn.Msvm_EthernetSwitchPortProfileSettingData():
|
||||
match = switch_port_id_regex.match(profile.InstanceID)
|
||||
if match:
|
||||
self._profile_sds[match.group()] = profile
|
||||
|
||||
# map between switch port's InstanceID and their VLAN setting data WMI
|
||||
# objects.
|
||||
for vlan_sd in self._conn.Msvm_EthernetSwitchPortVlanSettingData():
|
||||
match = switch_port_id_regex.match(vlan_sd.InstanceID)
|
||||
if match:
|
||||
self._vlan_sds[match.group()] = vlan_sd
|
||||
|
||||
# map between switch port's InstanceID and their VSID setting data WMI
|
||||
# objects.
|
||||
for vsid_sd in self._conn.Msvm_EthernetSwitchPortSecuritySettingData():
|
||||
match = switch_port_id_regex.match(vsid_sd.InstanceID)
|
||||
if match:
|
||||
self._vsid_sds[match.group()] = vsid_sd
|
||||
|
||||
# map between switch port's InstanceID and their bandwidth setting
|
||||
# data WMI objects.
|
||||
bandwidths = self._conn.Msvm_EthernetSwitchPortBandwidthSettingData()
|
||||
for bandwidth_sd in bandwidths:
|
||||
match = switch_port_id_regex.match(bandwidth_sd.InstanceID)
|
||||
if match:
|
||||
self._bandwidth_sds[match.group()] = bandwidth_sd
|
||||
|
||||
def update_cache(self):
|
||||
if not self._enable_cache:
|
||||
return
|
||||
|
||||
# map between switch port ID and switch port WMI object.
|
||||
self._switch_ports.clear()
|
||||
for port in self._conn.Msvm_EthernetPortAllocationSettingData():
|
||||
self._switch_ports[port.ElementName] = port
|
||||
|
||||
def clear_port_sg_acls_cache(self, switch_port_name):
|
||||
self._sg_acl_sds.pop(switch_port_name, None)
|
||||
|
||||
def get_vswitch_id(self, vswitch_name):
|
||||
vswitch = self._get_vswitch(vswitch_name)
|
||||
return vswitch.Name
|
||||
|
||||
def get_vswitch_external_network_name(self, vswitch_name):
|
||||
ext_port = self._get_vswitch_external_port(vswitch_name)
|
||||
if ext_port:
|
||||
return ext_port.ElementName
|
||||
|
||||
def _get_vswitch(self, vswitch_name):
|
||||
if vswitch_name in self._switches:
|
||||
return self._switches[vswitch_name]
|
||||
|
||||
vswitch = self._conn.Msvm_VirtualEthernetSwitch(
|
||||
ElementName=vswitch_name)
|
||||
if not len(vswitch):
|
||||
raise exceptions.HyperVException(_('VSwitch not found: %s') %
|
||||
vswitch_name)
|
||||
if self._enable_cache:
|
||||
self._switches[vswitch_name] = vswitch[0]
|
||||
return vswitch[0]
|
||||
|
||||
def _get_vswitch_external_port(self, vswitch_name):
|
||||
vswitch = self._get_vswitch(vswitch_name)
|
||||
ext_ports = self._conn.Msvm_ExternalEthernetPort()
|
||||
for ext_port in ext_ports:
|
||||
lan_endpoint_assoc_list = (
|
||||
self._conn.Msvm_EthernetDeviceSAPImplementation(
|
||||
Antecedent=ext_port.path_()))
|
||||
if lan_endpoint_assoc_list:
|
||||
lan_endpoint_assoc_list = self._conn.Msvm_ActiveConnection(
|
||||
Dependent=lan_endpoint_assoc_list[0].Dependent.path_())
|
||||
if lan_endpoint_assoc_list:
|
||||
lan_endpoint = lan_endpoint_assoc_list[0].Antecedent
|
||||
if lan_endpoint.SystemName == vswitch.Name:
|
||||
return ext_port
|
||||
|
||||
def vswitch_port_needed(self):
|
||||
return False
|
||||
|
||||
def get_switch_ports(self, vswitch_name):
|
||||
vswitch = self._get_vswitch(vswitch_name)
|
||||
vswitch_ports = self._conn.Msvm_EthernetSwitchPort(
|
||||
SystemName=vswitch.Name)
|
||||
return set(p.Name for p in vswitch_ports)
|
||||
|
||||
def get_port_by_id(self, port_id, vswitch_name):
|
||||
vswitch = self._get_vswitch(vswitch_name)
|
||||
switch_ports = self._conn.Msvm_EthernetSwitchPort(
|
||||
SystemName=vswitch.Name)
|
||||
for switch_port in switch_ports:
|
||||
if (switch_port.ElementName == port_id):
|
||||
return switch_port
|
||||
|
||||
def vnic_port_exists(self, port_id):
|
||||
try:
|
||||
self._get_vnic_settings(port_id)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_vnic_ids(self):
|
||||
return set(
|
||||
p.ElementName
|
||||
for p in self._conn.Msvm_SyntheticEthernetPortSettingData()
|
||||
if p.ElementName is not None)
|
||||
|
||||
def get_vnic_mac_address(self, switch_port_name):
|
||||
vnic = self._get_vnic_settings(switch_port_name)
|
||||
return vnic.Address
|
||||
|
||||
def _get_vnic_settings(self, vnic_name):
|
||||
vnic_settings = self._conn.Msvm_SyntheticEthernetPortSettingData(
|
||||
ElementName=vnic_name)
|
||||
if not vnic_settings:
|
||||
raise exceptions.HyperVvNicNotFound(vnic_name=vnic_name)
|
||||
return vnic_settings[0]
|
||||
|
||||
def get_vnic_event_listener(self, event_type):
|
||||
query = self._get_event_wql_query(cls=self._VNIC_SET_DATA,
|
||||
event_type=event_type,
|
||||
timeframe=2)
|
||||
listener = self._conn.Msvm_SyntheticEthernetPortSettingData.watch_for(
|
||||
query)
|
||||
|
||||
def _poll_events(callback):
|
||||
if patcher.is_monkey_patched('thread'):
|
||||
listen = functools.partial(tpool.execute, listener,
|
||||
self._VNIC_LISTENER_TIMEOUT_MS)
|
||||
else:
|
||||
listen = functools.partial(listener,
|
||||
self._VNIC_LISTENER_TIMEOUT_MS)
|
||||
|
||||
while True:
|
||||
# Retrieve one by one all the events that occurred in
|
||||
# the checked interval.
|
||||
try:
|
||||
event = listen()
|
||||
callback(event.ElementName)
|
||||
except exceptions.x_wmi_timed_out:
|
||||
# no new event published.
|
||||
pass
|
||||
|
||||
return _poll_events
|
||||
|
||||
def _get_event_wql_query(self, cls, event_type, timeframe=2, **where):
|
||||
"""Return a WQL query used for polling WMI events.
|
||||
|
||||
:param cls: the Hyper-V class polled for events.
|
||||
:param event_type: the type of event expected.
|
||||
:param timeframe: check for events that occurred in
|
||||
the specified timeframe.
|
||||
:param where: key-value arguments which are to be included in the
|
||||
query. For example: like=dict(foo="bar").
|
||||
"""
|
||||
like = where.pop('like', {})
|
||||
like_str = " AND ".join("TargetInstance.%s LIKE '%s%%'" % (k, v)
|
||||
for k, v in like.items())
|
||||
like_str = "AND " + like_str if like_str else ""
|
||||
|
||||
query = ("SELECT * FROM %(event_type)s WITHIN %(timeframe)s "
|
||||
"WHERE TargetInstance ISA '%(class)s' %(like)s" % {
|
||||
'class': cls,
|
||||
'event_type': event_type,
|
||||
'like': like_str,
|
||||
'timeframe': timeframe})
|
||||
return query
|
||||
|
||||
def connect_vnic_to_vswitch(self, vswitch_name, switch_port_name):
|
||||
port, found = self._get_switch_port_allocation(
|
||||
switch_port_name, create=True, expected=False)
|
||||
if found and port.HostResource and port.HostResource[0]:
|
||||
# vswitch port already exists and is connected to vswitch.
|
||||
return
|
||||
|
||||
vswitch = self._get_vswitch(vswitch_name)
|
||||
vnic = self._get_vnic_settings(switch_port_name)
|
||||
|
||||
port.HostResource = [vswitch.path_()]
|
||||
port.Parent = vnic.path_()
|
||||
if not found:
|
||||
vm = self._get_vm_from_res_setting_data(vnic)
|
||||
self._jobutils.add_virt_resource(port, vm)
|
||||
else:
|
||||
self._jobutils.modify_virt_resource(port)
|
||||
|
||||
def _get_vm_from_res_setting_data(self, res_setting_data):
|
||||
vmsettings_instance_id = res_setting_data.InstanceID.split('\\')[0]
|
||||
sd = self._conn.Msvm_VirtualSystemSettingData(
|
||||
InstanceID=vmsettings_instance_id)
|
||||
vm = self._conn.Msvm_ComputerSystem(Name=sd[0].ConfigurationID)
|
||||
return vm[0]
|
||||
|
||||
def remove_switch_port(self, switch_port_name, vnic_deleted=False):
|
||||
"""Removes the switch port."""
|
||||
sw_port, found = self._get_switch_port_allocation(switch_port_name,
|
||||
expected=False)
|
||||
if not sw_port:
|
||||
# Port not found. It happens when the VM was already deleted.
|
||||
return
|
||||
|
||||
if not vnic_deleted:
|
||||
try:
|
||||
self._jobutils.remove_virt_resource(sw_port)
|
||||
except exceptions.x_wmi:
|
||||
# port may have already been destroyed by Hyper-V
|
||||
pass
|
||||
|
||||
self._switch_ports.pop(switch_port_name, None)
|
||||
self._profile_sds.pop(sw_port.InstanceID, None)
|
||||
self._vlan_sds.pop(sw_port.InstanceID, None)
|
||||
self._vsid_sds.pop(sw_port.InstanceID, None)
|
||||
self._bandwidth_sds.pop(sw_port.InstanceID, None)
|
||||
|
||||
def set_vswitch_port_profile_id(self, switch_port_name, profile_id,
|
||||
profile_data, profile_name, vendor_name,
|
||||
**kwargs):
|
||||
"""Sets up the port profile id.
|
||||
|
||||
:param switch_port_name: The ElementName of the vSwitch port.
|
||||
:param profile_id: The profile id to be set for the given switch port.
|
||||
:param profile_data: Additional data for the Port Profile.
|
||||
:param profile_name: The name of the Port Profile.
|
||||
:param net_cfg_instance_id: Unique device identifier of the
|
||||
sub-interface.
|
||||
:param cdn_label_id: The CDN Label Id.
|
||||
:param cdn_label_string: The CDN label string.
|
||||
:param vendor_id: The id of the Vendor defining the profile.
|
||||
:param vendor_name: The name of the Vendor defining the profile.
|
||||
"""
|
||||
port_alloc = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
port_profile = self._get_profile_setting_data_from_port_alloc(
|
||||
port_alloc)
|
||||
|
||||
new_port_profile = self._prepare_profile_sd(
|
||||
profile_id=profile_id, profile_data=profile_data,
|
||||
profile_name=profile_name, vendor_name=vendor_name, **kwargs)
|
||||
|
||||
if port_profile:
|
||||
# Removing the feature because it cannot be modified
|
||||
# due to a wmi exception.
|
||||
self._jobutils.remove_virt_feature(port_profile)
|
||||
|
||||
# remove from cache.
|
||||
self._profile_sds.pop(port_alloc.InstanceID, None)
|
||||
|
||||
try:
|
||||
self._jobutils.add_virt_feature(new_port_profile, port_alloc)
|
||||
except Exception as ex:
|
||||
raise exceptions.HyperVException(
|
||||
'Unable to set port profile settings %(port_profile)s '
|
||||
'for port %(port)s. Error: %(error)s' %
|
||||
dict(port_profile=new_port_profile, port=port_alloc, error=ex))
|
||||
|
||||
def set_vswitch_port_vlan_id(self, vlan_id=None, switch_port_name=None,
|
||||
**kwargs):
|
||||
"""Sets up operation mode, VLAN ID and VLAN trunk for the given port.
|
||||
|
||||
:param vlan_id: the VLAN ID to be set for the given switch port.
|
||||
:param switch_port_name: the ElementName of the vSwitch port.
|
||||
:param operation_mode: the VLAN operation mode. The acceptable values
|
||||
are:
|
||||
os_win.constants.VLAN_MODE_ACCESS, os_win.constants.VLAN_TRUNK_MODE
|
||||
If not given, VLAN_MODE_ACCESS is used by default.
|
||||
:param trunk_vlans: an array of VLAN IDs to be set in trunk mode.
|
||||
:raises AttributeError: if an unsupported operation_mode is given, or
|
||||
the given operation mode is VLAN_MODE_ACCESS and the given
|
||||
trunk_vlans is not None.
|
||||
"""
|
||||
|
||||
operation_mode = kwargs.get('operation_mode',
|
||||
constants.VLAN_MODE_ACCESS)
|
||||
trunk_vlans = kwargs.get('trunk_vlans')
|
||||
|
||||
if operation_mode not in [constants.VLAN_MODE_ACCESS,
|
||||
constants.VLAN_MODE_TRUNK]:
|
||||
msg = _('Unsupported VLAN operation mode: %s')
|
||||
raise AttributeError(msg % operation_mode)
|
||||
|
||||
if (operation_mode == constants.VLAN_MODE_ACCESS and
|
||||
trunk_vlans is not None):
|
||||
raise AttributeError(_('The given operation mode is ACCESS, '
|
||||
'cannot set given trunk_vlans.'))
|
||||
|
||||
port_alloc = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
vlan_settings = self._get_vlan_setting_data_from_port_alloc(port_alloc)
|
||||
|
||||
if operation_mode == constants.VLAN_MODE_ACCESS:
|
||||
new_vlan_settings = self._prepare_vlan_sd_access_mode(
|
||||
vlan_settings, vlan_id)
|
||||
else:
|
||||
new_vlan_settings = self._prepare_vlan_sd_trunk_mode(
|
||||
vlan_settings, vlan_id, trunk_vlans)
|
||||
|
||||
if not new_vlan_settings:
|
||||
# if no object was returned, it means that the VLAN Setting Data
|
||||
# was already added with the desired attributes.
|
||||
return
|
||||
|
||||
if vlan_settings:
|
||||
# Removing the feature because it cannot be modified
|
||||
# due to a wmi exception.
|
||||
self._jobutils.remove_virt_feature(vlan_settings)
|
||||
|
||||
# remove from cache.
|
||||
self._vlan_sds.pop(port_alloc.InstanceID, None)
|
||||
|
||||
self._jobutils.add_virt_feature(new_vlan_settings, port_alloc)
|
||||
|
||||
# TODO(claudiub): This will help solve the missing VLAN issue, but it
|
||||
# comes with a performance cost. The root cause of the problem must
|
||||
# be solved.
|
||||
vlan_settings = self._get_vlan_setting_data_from_port_alloc(port_alloc)
|
||||
if not vlan_settings:
|
||||
raise exceptions.HyperVException(
|
||||
_('Port VLAN not found: %s') % switch_port_name)
|
||||
|
||||
def _prepare_profile_sd(self, **kwargs):
|
||||
profile_id_settings = self._create_default_setting_data(
|
||||
self._PORT_PROFILE_SET_DATA)
|
||||
|
||||
for argument_name, attr_name in _PORT_PROFILE_ATTR_MAP.items():
|
||||
attribute = kwargs.pop(argument_name, None)
|
||||
if attribute is None:
|
||||
continue
|
||||
setattr(profile_id_settings, attr_name, attribute)
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unrecognized attributes %r" % kwargs)
|
||||
|
||||
return profile_id_settings
|
||||
|
||||
def _prepare_vlan_sd_access_mode(self, vlan_settings, vlan_id):
|
||||
if vlan_settings:
|
||||
# the given vlan_id might be None.
|
||||
vlan_id = vlan_id or vlan_settings.AccessVlanId
|
||||
if (vlan_settings.OperationMode == constants.VLAN_MODE_ACCESS and
|
||||
vlan_settings.AccessVlanId == vlan_id):
|
||||
# VLAN already set to correct value, no need to change it.
|
||||
return None
|
||||
|
||||
vlan_settings = self._create_default_setting_data(
|
||||
self._PORT_VLAN_SET_DATA)
|
||||
vlan_settings.AccessVlanId = vlan_id
|
||||
vlan_settings.OperationMode = constants.VLAN_MODE_ACCESS
|
||||
|
||||
return vlan_settings
|
||||
|
||||
def _prepare_vlan_sd_trunk_mode(self, vlan_settings, vlan_id, trunk_vlans):
|
||||
if vlan_settings:
|
||||
# the given vlan_id might be None.
|
||||
vlan_id = vlan_id or vlan_settings.NativeVlanId
|
||||
trunk_vlans = trunk_vlans or vlan_settings.TrunkVlanIdArray or []
|
||||
trunk_vlans = sorted(trunk_vlans)
|
||||
if (vlan_settings.OperationMode == constants.VLAN_MODE_TRUNK and
|
||||
vlan_settings.NativeVlanId == vlan_id and
|
||||
sorted(vlan_settings.TrunkVlanIdArray) == trunk_vlans):
|
||||
# VLAN already set to correct value, no need to change it.
|
||||
return None
|
||||
|
||||
vlan_settings = self._create_default_setting_data(
|
||||
self._PORT_VLAN_SET_DATA)
|
||||
vlan_settings.NativeVlanId = vlan_id
|
||||
vlan_settings.TrunkVlanIdArray = trunk_vlans
|
||||
vlan_settings.OperationMode = constants.VLAN_MODE_TRUNK
|
||||
|
||||
return vlan_settings
|
||||
|
||||
def set_vswitch_port_vsid(self, vsid, switch_port_name):
|
||||
self._set_switch_port_security_settings(switch_port_name,
|
||||
VirtualSubnetId=vsid)
|
||||
|
||||
def set_vswitch_port_mac_spoofing(self, switch_port_name, state):
|
||||
"""Sets the given port's MAC spoofing to the given state.
|
||||
|
||||
:param switch_port_name: the name of the port which will have MAC
|
||||
spoofing set to the given state.
|
||||
:param state: boolean, if MAC spoofing should be turned on or off.
|
||||
"""
|
||||
self._set_switch_port_security_settings(switch_port_name,
|
||||
AllowMacSpoofing=state)
|
||||
|
||||
def _set_switch_port_security_settings(self, switch_port_name, **kwargs):
|
||||
port_alloc = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
sec_settings = self._get_security_setting_data_from_port_alloc(
|
||||
port_alloc)
|
||||
|
||||
if sec_settings:
|
||||
if all(getattr(sec_settings, k) == v for k, v in kwargs.items()):
|
||||
# All desired properties already properly set. Nothing to do.
|
||||
return
|
||||
|
||||
# Removing the feature because it cannot be modified
|
||||
# due to a wmi exception.
|
||||
self._jobutils.remove_virt_feature(sec_settings)
|
||||
else:
|
||||
sec_settings = self._create_default_setting_data(
|
||||
self._PORT_SECURITY_SET_DATA)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(sec_settings, k, v)
|
||||
|
||||
self._jobutils.add_virt_feature(sec_settings, port_alloc)
|
||||
|
||||
# TODO(claudiub): This will help solve the missing VSID issue, but it
|
||||
# comes with a performance cost. The root cause of the problem must
|
||||
# be solved.
|
||||
sec_settings = self._get_security_setting_data_from_port_alloc(
|
||||
port_alloc)
|
||||
if not sec_settings:
|
||||
raise exceptions.HyperVException(
|
||||
_('Port Security Settings not found: %s') % switch_port_name)
|
||||
|
||||
def _get_profile_setting_data_from_port_alloc(self, port_alloc):
|
||||
return self._get_setting_data_from_port_alloc(
|
||||
port_alloc, self._profile_sds, self._PORT_PROFILE_SET_DATA)
|
||||
|
||||
def _get_vlan_setting_data_from_port_alloc(self, port_alloc):
|
||||
return self._get_setting_data_from_port_alloc(
|
||||
port_alloc, self._vlan_sds, self._PORT_VLAN_SET_DATA)
|
||||
|
||||
def _get_security_setting_data_from_port_alloc(self, port_alloc):
|
||||
return self._get_setting_data_from_port_alloc(
|
||||
port_alloc, self._vsid_sds, self._PORT_SECURITY_SET_DATA)
|
||||
|
||||
def _get_bandwidth_setting_data_from_port_alloc(self, port_alloc):
|
||||
return self._get_setting_data_from_port_alloc(
|
||||
port_alloc, self._bandwidth_sds, self._PORT_BANDWIDTH_SET_DATA)
|
||||
|
||||
def _get_setting_data_from_port_alloc(self, port_alloc, cache, data_class):
|
||||
if port_alloc.InstanceID in cache:
|
||||
return cache[port_alloc.InstanceID]
|
||||
|
||||
setting_data = self._get_first_item(
|
||||
_wqlutils.get_element_associated_class(
|
||||
self._conn, data_class,
|
||||
element_instance_id=port_alloc.InstanceID))
|
||||
if setting_data and self._enable_cache:
|
||||
cache[port_alloc.InstanceID] = setting_data
|
||||
return setting_data
|
||||
|
||||
def _get_switch_port_allocation(self, switch_port_name, create=False,
|
||||
expected=True):
|
||||
if switch_port_name in self._switch_ports:
|
||||
return self._switch_ports[switch_port_name], True
|
||||
|
||||
switch_port, found = self._get_setting_data(
|
||||
self._PORT_ALLOC_SET_DATA,
|
||||
switch_port_name, create)
|
||||
|
||||
if found:
|
||||
# newly created setting data cannot be cached, they do not
|
||||
# represent real objects yet.
|
||||
# if it was found, it means that it was not created.
|
||||
if self._enable_cache:
|
||||
self._switch_ports[switch_port_name] = switch_port
|
||||
elif expected:
|
||||
raise exceptions.HyperVPortNotFoundException(
|
||||
port_name=switch_port_name)
|
||||
return switch_port, found
|
||||
|
||||
def _get_setting_data(self, class_name, element_name, create=True):
|
||||
element_name = element_name.replace("'", '"')
|
||||
q = self._compat_conn.query("SELECT * FROM %(class_name)s WHERE "
|
||||
"ElementName = '%(element_name)s'" %
|
||||
{"class_name": class_name,
|
||||
"element_name": element_name})
|
||||
data = self._get_first_item(q)
|
||||
found = data is not None
|
||||
if not data and create:
|
||||
data = self._get_default_setting_data(class_name)
|
||||
data.ElementName = element_name
|
||||
return data, found
|
||||
|
||||
def _get_default_setting_data(self, class_name):
|
||||
return self._compat_conn.query("SELECT * FROM %s WHERE InstanceID "
|
||||
"LIKE '%%\\Default'" % class_name)[0]
|
||||
|
||||
def _create_default_setting_data(self, class_name):
|
||||
return getattr(self._compat_conn, class_name).new()
|
||||
|
||||
def _get_first_item(self, obj):
|
||||
if obj:
|
||||
return obj[0]
|
||||
|
||||
def add_metrics_collection_acls(self, switch_port_name):
|
||||
port = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
|
||||
# Add the ACLs only if they don't already exist
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_ALLOC_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
for acl_type in [self._ACL_TYPE_IPV4, self._ACL_TYPE_IPV6]:
|
||||
for acl_dir in [self._ACL_DIR_IN, self._ACL_DIR_OUT]:
|
||||
_acls = self._filter_acls(
|
||||
acls, self._ACL_ACTION_METER, acl_dir, acl_type)
|
||||
|
||||
if not _acls:
|
||||
acl = self._create_acl(
|
||||
acl_dir, acl_type, self._ACL_ACTION_METER)
|
||||
self._jobutils.add_virt_feature(acl, port)
|
||||
|
||||
def is_metrics_collection_allowed(self, switch_port_name):
|
||||
port = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
|
||||
if not self._is_port_vm_started(port):
|
||||
return False
|
||||
|
||||
# all 4 meter ACLs must be existent first. (2 x direction)
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_ALLOC_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
acls = [a for a in acls if a.Action == self._ACL_ACTION_METER]
|
||||
if len(acls) < 2:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_port_vm_started(self, port):
|
||||
vmsettings_instance_id = port.InstanceID.split('\\')[0]
|
||||
vmsettings = self._conn.Msvm_VirtualSystemSettingData(
|
||||
InstanceID=vmsettings_instance_id)
|
||||
# See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
|
||||
(ret_val, summary_info) = self._vs_man_svc.GetSummaryInformation(
|
||||
[self._VM_SUMMARY_ENABLED_STATE],
|
||||
[v.path_() for v in vmsettings])
|
||||
if ret_val or not summary_info:
|
||||
raise exceptions.HyperVException(_('Cannot get VM summary data '
|
||||
'for: %s') % port.ElementName)
|
||||
|
||||
return summary_info[0].EnabledState is self._HYPERV_VM_STATE_ENABLED
|
||||
|
||||
def create_security_rules(self, switch_port_name, sg_rules):
|
||||
port = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
|
||||
self._bind_security_rules(port, sg_rules)
|
||||
|
||||
def remove_security_rules(self, switch_port_name, sg_rules):
|
||||
port = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_EXT_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
remove_acls = []
|
||||
for sg_rule in sg_rules:
|
||||
filtered_acls = self._filter_security_acls(sg_rule, acls)
|
||||
remove_acls.extend(filtered_acls)
|
||||
|
||||
if remove_acls:
|
||||
self._jobutils.remove_multiple_virt_features(remove_acls)
|
||||
|
||||
# remove the old ACLs from the cache.
|
||||
new_acls = [a for a in acls if a not in remove_acls]
|
||||
self._sg_acl_sds[port.ElementName] = new_acls
|
||||
|
||||
def remove_all_security_rules(self, switch_port_name):
|
||||
port = self._get_switch_port_allocation(switch_port_name)[0]
|
||||
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_EXT_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
filtered_acls = [a for a in acls if
|
||||
a.Action is not self._ACL_ACTION_METER]
|
||||
|
||||
if filtered_acls:
|
||||
self._jobutils.remove_multiple_virt_features(filtered_acls)
|
||||
|
||||
# clear the cache.
|
||||
self._sg_acl_sds[port.ElementName] = []
|
||||
|
||||
def _bind_security_rules(self, port, sg_rules):
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_EXT_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
|
||||
# Add the ACL only if it don't already exist.
|
||||
add_acls = []
|
||||
processed_sg_rules = []
|
||||
weights = self._get_new_weights(sg_rules, acls)
|
||||
index = 0
|
||||
|
||||
for sg_rule in sg_rules:
|
||||
filtered_acls = self._filter_security_acls(sg_rule, acls)
|
||||
if filtered_acls:
|
||||
# ACL already exists.
|
||||
continue
|
||||
|
||||
acl = self._create_security_acl(sg_rule, weights[index])
|
||||
add_acls.append(acl)
|
||||
index += 1
|
||||
|
||||
# append sg_rule the acls list, to make sure that the same rule
|
||||
# is not processed twice.
|
||||
processed_sg_rules.append(sg_rule)
|
||||
|
||||
if add_acls:
|
||||
self._jobutils.add_multiple_virt_features(add_acls, port)
|
||||
|
||||
# caching the Security Group Rules that have been processed and
|
||||
# added to the port. The list should only be used to check the
|
||||
# existence of rules, nothing else.
|
||||
acls.extend(processed_sg_rules)
|
||||
|
||||
def _get_port_security_acls(self, port):
|
||||
"""Returns a mutable list of Security Group Rule objects.
|
||||
|
||||
Returns the list of Security Group Rule objects from the cache,
|
||||
otherwise it fetches and caches from the port's associated class.
|
||||
"""
|
||||
|
||||
if port.ElementName in self._sg_acl_sds:
|
||||
return self._sg_acl_sds[port.ElementName]
|
||||
|
||||
acls = _wqlutils.get_element_associated_class(
|
||||
self._conn, self._PORT_EXT_ACL_SET_DATA,
|
||||
element_instance_id=port.InstanceID)
|
||||
if self._enable_cache:
|
||||
self._sg_acl_sds[port.ElementName] = acls
|
||||
|
||||
return acls
|
||||
|
||||
def _create_acl(self, direction, acl_type, action):
|
||||
acl = self._create_default_setting_data(self._PORT_ALLOC_ACL_SET_DATA)
|
||||
acl.set(Direction=direction,
|
||||
AclType=acl_type,
|
||||
Action=action,
|
||||
Applicability=self._ACL_APPLICABILITY_LOCAL)
|
||||
return acl
|
||||
|
||||
def _create_security_acl(self, sg_rule, weight):
|
||||
# Acl instance can be created new each time, the object should be
|
||||
# of type ExtendedEthernetSettingsData.
|
||||
acl = self._create_default_setting_data(self._PORT_EXT_ACL_SET_DATA)
|
||||
acl.set(**sg_rule.to_dict())
|
||||
return acl
|
||||
|
||||
def _filter_acls(self, acls, action, direction, acl_type, remote_addr=""):
|
||||
return [v for v in acls
|
||||
if v.Action == action and
|
||||
v.Direction == direction and
|
||||
v.AclType == acl_type and
|
||||
v.RemoteAddress == remote_addr]
|
||||
|
||||
def _filter_security_acls(self, sg_rule, acls):
|
||||
return [a for a in acls if sg_rule == a]
|
||||
|
||||
def _get_new_weights(self, sg_rules, existent_acls):
|
||||
"""Computes the weights needed for given sg_rules.
|
||||
|
||||
:param sg_rules: ACLs to be added. They must have the same Action.
|
||||
:existent_acls: ACLs already bound to a switch port.
|
||||
:return: list of weights which will be used to create ACLs. List will
|
||||
have the recommended order for sg_rules' Action.
|
||||
"""
|
||||
return [0] * len(sg_rules)
|
||||
|
||||
def set_port_qos_rule(self, port_id, qos_rule):
|
||||
"""Sets the QoS rule for the given port.
|
||||
|
||||
:param port_id: the port's ID to which the QoS rule will be applied to.
|
||||
:param qos_rule: a dictionary containing the following keys:
|
||||
min_kbps, max_kbps, max_burst_kbps, max_burst_size_kb.
|
||||
:raises exceptions.HyperVInvalidException: if
|
||||
- min_kbps is smaller than 10MB.
|
||||
- max_kbps is smaller than min_kbps.
|
||||
- max_burst_kbps is smaller than max_kbps.
|
||||
:raises exceptions.HyperVException: if the QoS rule cannot be set.
|
||||
"""
|
||||
|
||||
# Hyper-V stores bandwidth limits in bytes.
|
||||
min_bps = qos_rule.get("min_kbps", 0) * units.Ki
|
||||
max_bps = qos_rule.get("max_kbps", 0) * units.Ki
|
||||
max_burst_bps = qos_rule.get("max_burst_kbps", 0) * units.Ki
|
||||
max_burst_sz = qos_rule.get("max_burst_size_kb", 0) * units.Ki
|
||||
|
||||
if not (min_bps or max_bps or max_burst_bps or max_burst_sz):
|
||||
# no limits need to be set
|
||||
return
|
||||
|
||||
if min_bps and min_bps < 10 * units.Mi:
|
||||
raise exceptions.InvalidParameterValue(
|
||||
param_name="min_kbps", param_value=min_bps)
|
||||
if max_bps and max_bps < min_bps:
|
||||
raise exceptions.InvalidParameterValue(
|
||||
param_name="max_kbps", param_value=max_bps)
|
||||
if max_burst_bps and max_burst_bps < max_bps:
|
||||
raise exceptions.InvalidParameterValue(
|
||||
param_name="max_burst_kbps", param_value=max_burst_bps)
|
||||
|
||||
port_alloc = self._get_switch_port_allocation(port_id)[0]
|
||||
bandwidth = self._get_bandwidth_setting_data_from_port_alloc(
|
||||
port_alloc)
|
||||
if bandwidth:
|
||||
# Removing the feature because it cannot be modified
|
||||
# due to a wmi exception.
|
||||
self._jobutils.remove_virt_feature(bandwidth)
|
||||
|
||||
# remove from cache.
|
||||
self._bandwidth_sds.pop(port_alloc.InstanceID, None)
|
||||
|
||||
bandwidth = self._get_default_setting_data(
|
||||
self._PORT_BANDWIDTH_SET_DATA)
|
||||
bandwidth.Reservation = min_bps
|
||||
bandwidth.Limit = max_bps
|
||||
bandwidth.BurstLimit = max_burst_bps
|
||||
bandwidth.BurstSize = max_burst_sz
|
||||
|
||||
try:
|
||||
self._jobutils.add_virt_feature(bandwidth, port_alloc)
|
||||
except Exception as ex:
|
||||
if '0x80070057' in six.text_type(ex):
|
||||
raise exceptions.InvalidParameterValue(
|
||||
param_name="qos_rule", param_value=qos_rule)
|
||||
raise exceptions.HyperVException(
|
||||
'Unable to set qos rule %(qos_rule)s for port %(port)s. '
|
||||
'Error: %(error)s' %
|
||||
dict(qos_rule=qos_rule, port=port_alloc, error=ex))
|
||||
|
||||
def remove_port_qos_rule(self, port_id):
|
||||
"""Removes the QoS rule from the given port.
|
||||
|
||||
:param port_id: the port's ID from which the QoS rule will be removed.
|
||||
"""
|
||||
port_alloc = self._get_switch_port_allocation(port_id)[0]
|
||||
bandwidth = self._get_bandwidth_setting_data_from_port_alloc(
|
||||
port_alloc)
|
||||
if bandwidth:
|
||||
self._jobutils.remove_virt_feature(bandwidth)
|
||||
# remove from cache.
|
||||
self._bandwidth_sds.pop(port_alloc.InstanceID, None)
|
||||
|
||||
|
||||
class NetworkUtilsR2(NetworkUtils):
|
||||
_PORT_EXT_ACL_SET_DATA = 'Msvm_EthernetSwitchPortExtendedAclSettingData'
|
||||
_MAX_WEIGHT = 65500
|
||||
|
||||
# 2 directions x 2 address types x 4 protocols = 16 ACLs
|
||||
_REJECT_ACLS_COUNT = 16
|
||||
|
||||
def _create_security_acl(self, sg_rule, weight):
|
||||
acl = super(NetworkUtilsR2, self)._create_security_acl(sg_rule,
|
||||
weight)
|
||||
acl.Weight = weight
|
||||
sg_rule.Weight = weight
|
||||
return acl
|
||||
|
||||
def _get_new_weights(self, sg_rules, existent_acls):
|
||||
sg_rule = sg_rules[0]
|
||||
num_rules = len(sg_rules)
|
||||
existent_acls = [a for a in existent_acls
|
||||
if a.Action == sg_rule.Action]
|
||||
if not existent_acls:
|
||||
if sg_rule.Action == self._ACL_ACTION_DENY:
|
||||
return list(range(1, 1 + num_rules))
|
||||
else:
|
||||
return list(range(self._MAX_WEIGHT - 1,
|
||||
self._MAX_WEIGHT - 1 - num_rules, - 1))
|
||||
|
||||
# there are existent ACLs.
|
||||
weights = [a.Weight for a in existent_acls]
|
||||
if sg_rule.Action == self._ACL_ACTION_DENY:
|
||||
return [i for i in list(range(1, self._REJECT_ACLS_COUNT + 1))
|
||||
if i not in weights][:num_rules]
|
||||
|
||||
min_weight = min(weights)
|
||||
last_weight = min_weight - num_rules - 1
|
||||
if last_weight > self._REJECT_ACLS_COUNT:
|
||||
return list(range(min_weight - 1, last_weight, - 1))
|
||||
|
||||
# not enough weights. Must search for available weights.
|
||||
# if it is this case, num_rules is a small number.
|
||||
current_weight = self._MAX_WEIGHT - 1
|
||||
new_weights = []
|
||||
for i in list(range(num_rules)):
|
||||
while current_weight in weights:
|
||||
current_weight -= 1
|
||||
new_weights.append(current_weight)
|
||||
|
||||
return new_weights
|
@ -1,172 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions SRL
|
||||
# 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_log import log as logging
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils.network import networkutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NvgreUtils(baseutils.BaseUtils):
|
||||
_HYPERV_VIRT_ADAPTER = 'Hyper-V Virtual Ethernet Adapter'
|
||||
_IPV4_ADDRESS_FAMILY = 2
|
||||
|
||||
_TRANSLATE_NAT = 0
|
||||
_TRANSLATE_ENCAP = 1
|
||||
|
||||
_LOOKUP_RECORD_TYPE_STATIC = 0
|
||||
_LOOKUP_RECORD_TYPE_L2_ONLY = 3
|
||||
|
||||
_STDCIMV2_NAMESPACE = '//./root/StandardCimv2'
|
||||
|
||||
def __init__(self):
|
||||
super(NvgreUtils, self).__init__()
|
||||
self._utils = networkutils.NetworkUtils()
|
||||
self._net_if_indexes = {}
|
||||
self._scimv2 = self._get_wmi_conn(moniker=self._STDCIMV2_NAMESPACE)
|
||||
|
||||
def create_provider_address(self, network_name, provider_vlan_id):
|
||||
iface_index = self._get_network_iface_index(network_name)
|
||||
(provider_addr, prefix_len) = self.get_network_iface_ip(network_name)
|
||||
|
||||
if not provider_addr:
|
||||
# logging is already provided by get_network_iface_ip.
|
||||
raise exceptions.NotFound(resource=network_name)
|
||||
|
||||
provider = (
|
||||
self._scimv2.MSFT_NetVirtualizationProviderAddressSettingData(
|
||||
ProviderAddress=provider_addr))
|
||||
|
||||
if provider:
|
||||
if (provider[0].VlanID == provider_vlan_id and
|
||||
provider[0].InterfaceIndex == iface_index):
|
||||
# ProviderAddress already exists.
|
||||
return
|
||||
# ProviderAddress exists, but with different VlanID or iface index.
|
||||
provider[0].Delete_()
|
||||
|
||||
self._create_new_object(
|
||||
self._scimv2.MSFT_NetVirtualizationProviderAddressSettingData,
|
||||
ProviderAddress=provider_addr,
|
||||
VlanID=provider_vlan_id,
|
||||
InterfaceIndex=iface_index,
|
||||
PrefixLength=prefix_len)
|
||||
|
||||
def create_provider_route(self, network_name):
|
||||
iface_index = self._get_network_iface_index(network_name)
|
||||
|
||||
routes = self._scimv2.MSFT_NetVirtualizationProviderRouteSettingData(
|
||||
InterfaceIndex=iface_index, NextHop=constants.IPV4_DEFAULT)
|
||||
|
||||
if not routes:
|
||||
self._create_new_object(
|
||||
self._scimv2.MSFT_NetVirtualizationProviderRouteSettingData,
|
||||
InterfaceIndex=iface_index,
|
||||
DestinationPrefix='%s/0' % constants.IPV4_DEFAULT,
|
||||
NextHop=constants.IPV4_DEFAULT)
|
||||
|
||||
def clear_customer_routes(self, vsid):
|
||||
routes = self._scimv2.MSFT_NetVirtualizationCustomerRouteSettingData(
|
||||
VirtualSubnetID=vsid)
|
||||
|
||||
for route in routes:
|
||||
route.Delete_()
|
||||
|
||||
def create_customer_route(self, vsid, dest_prefix, next_hop, rdid_uuid):
|
||||
self._create_new_object(
|
||||
self._scimv2.MSFT_NetVirtualizationCustomerRouteSettingData,
|
||||
VirtualSubnetID=vsid,
|
||||
DestinationPrefix=dest_prefix,
|
||||
NextHop=next_hop,
|
||||
Metric=255,
|
||||
RoutingDomainID='{%s}' % rdid_uuid)
|
||||
|
||||
def create_lookup_record(self, provider_addr, customer_addr, mac, vsid):
|
||||
# check for existing entry.
|
||||
lrec = self._scimv2.MSFT_NetVirtualizationLookupRecordSettingData(
|
||||
CustomerAddress=customer_addr, VirtualSubnetID=vsid)
|
||||
if (lrec and lrec[0].VirtualSubnetID == vsid and
|
||||
lrec[0].ProviderAddress == provider_addr and
|
||||
lrec[0].MACAddress == mac):
|
||||
# lookup record already exists, nothing to do.
|
||||
return
|
||||
|
||||
# create new lookup record.
|
||||
if lrec:
|
||||
lrec[0].Delete_()
|
||||
|
||||
if constants.IPV4_DEFAULT == customer_addr:
|
||||
# customer address used for DHCP requests.
|
||||
record_type = self._LOOKUP_RECORD_TYPE_L2_ONLY
|
||||
else:
|
||||
record_type = self._LOOKUP_RECORD_TYPE_STATIC
|
||||
|
||||
self._create_new_object(
|
||||
self._scimv2.MSFT_NetVirtualizationLookupRecordSettingData,
|
||||
VirtualSubnetID=vsid,
|
||||
Rule=self._TRANSLATE_ENCAP,
|
||||
Type=record_type,
|
||||
MACAddress=mac,
|
||||
CustomerAddress=customer_addr,
|
||||
ProviderAddress=provider_addr)
|
||||
|
||||
def _create_new_object(self, object_class, **args):
|
||||
new_obj = object_class.new(**args)
|
||||
new_obj.Put_()
|
||||
return new_obj
|
||||
|
||||
def _get_network_ifaces_by_name(self, network_name):
|
||||
return [n for n in self._scimv2.MSFT_NetAdapter() if
|
||||
n.Name.find(network_name) >= 0]
|
||||
|
||||
def _get_network_iface_index(self, network_name):
|
||||
if self._net_if_indexes.get(network_name):
|
||||
return self._net_if_indexes[network_name]
|
||||
|
||||
description = (
|
||||
self._utils.get_vswitch_external_network_name(network_name))
|
||||
|
||||
# physical NIC and vswitch must have the same MAC address.
|
||||
networks = self._scimv2.MSFT_NetAdapter(
|
||||
InterfaceDescription=description)
|
||||
|
||||
if not networks:
|
||||
raise exceptions.NotFound(resource=network_name)
|
||||
|
||||
self._net_if_indexes[network_name] = networks[0].InterfaceIndex
|
||||
return networks[0].InterfaceIndex
|
||||
|
||||
def get_network_iface_ip(self, network_name):
|
||||
networks = [n for n in self._get_network_ifaces_by_name(network_name)
|
||||
if n.DriverDescription == self._HYPERV_VIRT_ADAPTER]
|
||||
|
||||
if not networks:
|
||||
LOG.error('No vswitch was found with name: %s', network_name)
|
||||
return None, None
|
||||
|
||||
ip_addr = self._scimv2.MSFT_NetIPAddress(
|
||||
InterfaceIndex=networks[0].InterfaceIndex,
|
||||
AddressFamily=self._IPV4_ADDRESS_FAMILY)
|
||||
|
||||
if not ip_addr:
|
||||
LOG.error('No IP Address could be found for network: %s',
|
||||
network_name)
|
||||
return None, None
|
||||
|
||||
return ip_addr[0].IPAddress, ip_addr[0].PrefixLength
|
@ -1,250 +0,0 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# 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 contextlib
|
||||
import ctypes
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
import six
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
from os_win.utils import _acl_utils
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import constants as w_const
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
from os_win.utils.winapi.libs import advapi32 as advapi32_def
|
||||
from os_win.utils.winapi import wintypes
|
||||
|
||||
kernel32 = w_lib.get_shared_lib_handle(w_lib.KERNEL32)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PathUtils(object):
|
||||
|
||||
def __init__(self):
|
||||
self._win32_utils = win32utils.Win32Utils()
|
||||
self._acl_utils = _acl_utils.ACLUtils()
|
||||
|
||||
def open(self, path, mode):
|
||||
"""Wrapper on __builtin__.open used to simplify unit testing."""
|
||||
from six.moves import builtins
|
||||
return builtins.open(path, mode)
|
||||
|
||||
def exists(self, path):
|
||||
return os.path.exists(path)
|
||||
|
||||
def makedirs(self, path):
|
||||
os.makedirs(path)
|
||||
|
||||
def remove(self, path):
|
||||
os.remove(path)
|
||||
|
||||
def rename(self, src, dest):
|
||||
os.rename(src, dest)
|
||||
|
||||
def copy_dir(self, src, dest):
|
||||
shutil.copytree(src, dest)
|
||||
|
||||
def copyfile(self, src, dest):
|
||||
self.copy(src, dest)
|
||||
|
||||
def copy(self, src, dest, fail_if_exists=True):
|
||||
"""Copies a file to a specified location.
|
||||
|
||||
:param fail_if_exists: if set to True, the method fails if the
|
||||
destination path exists.
|
||||
"""
|
||||
# With large files this is 2x-3x faster than shutil.copy(src, dest),
|
||||
# especially when copying to a UNC target.
|
||||
if os.path.isdir(dest):
|
||||
src_fname = os.path.basename(src)
|
||||
dest = os.path.join(dest, src_fname)
|
||||
|
||||
try:
|
||||
self._win32_utils.run_and_check_output(
|
||||
kernel32.CopyFileW,
|
||||
ctypes.c_wchar_p(src),
|
||||
ctypes.c_wchar_p(dest),
|
||||
wintypes.BOOL(fail_if_exists),
|
||||
kernel32_lib_func=True)
|
||||
except exceptions.Win32Exception as exc:
|
||||
err_msg = _('The file copy from %(src)s to %(dest)s failed.'
|
||||
'Exception: %(exc)s')
|
||||
raise IOError(err_msg % dict(src=src, dest=dest, exc=exc))
|
||||
|
||||
def copy_folder_files(self, src_dir, dest_dir):
|
||||
"""Copies the files of the given src_dir to dest_dir.
|
||||
|
||||
It will ignore any nested folders.
|
||||
|
||||
:param src_dir: Given folder from which to copy files.
|
||||
:param dest_dir: Folder to which to copy files.
|
||||
"""
|
||||
|
||||
self.check_create_dir(dest_dir)
|
||||
|
||||
for fname in os.listdir(src_dir):
|
||||
src = os.path.join(src_dir, fname)
|
||||
# ignore subdirs.
|
||||
if os.path.isfile(src):
|
||||
self.copy(src, os.path.join(dest_dir, fname))
|
||||
|
||||
def move_folder_files(self, src_dir, dest_dir):
|
||||
"""Moves the files of the given src_dir to dest_dir.
|
||||
|
||||
It will ignore any nested folders.
|
||||
|
||||
:param src_dir: Given folder from which to move files.
|
||||
:param dest_dir: Folder to which to move files.
|
||||
"""
|
||||
|
||||
for fname in os.listdir(src_dir):
|
||||
src = os.path.join(src_dir, fname)
|
||||
# ignore subdirs.
|
||||
if os.path.isfile(src):
|
||||
self.rename(src, os.path.join(dest_dir, fname))
|
||||
|
||||
@_utils.retry_decorator(exceptions=exceptions.OSWinException,
|
||||
error_codes=[w_const.ERROR_DIR_IS_NOT_EMPTY])
|
||||
def rmtree(self, path):
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except exceptions.WindowsError as ex:
|
||||
# NOTE(claudiub): convert it to an OSWinException in order to use
|
||||
# the retry_decorator.
|
||||
raise exceptions.OSWinException(six.text_type(ex),
|
||||
error_code=ex.winerror)
|
||||
|
||||
def check_create_dir(self, path):
|
||||
if not self.exists(path):
|
||||
LOG.debug('Creating directory: %s', path)
|
||||
self.makedirs(path)
|
||||
|
||||
def check_remove_dir(self, path):
|
||||
if self.exists(path):
|
||||
LOG.debug('Removing directory: %s', path)
|
||||
self.rmtree(path)
|
||||
|
||||
def is_symlink(self, path):
|
||||
if sys.version_info >= (3, 2):
|
||||
return os.path.islink(path)
|
||||
|
||||
file_attr = self._win32_utils.run_and_check_output(
|
||||
kernel32.GetFileAttributesW,
|
||||
path,
|
||||
error_ret_vals=[w_const.INVALID_FILE_ATTRIBUTES],
|
||||
kernel32_lib_func=True)
|
||||
|
||||
return bool(os.path.isdir(path) and (
|
||||
file_attr & w_const.FILE_ATTRIBUTE_REPARSE_POINT))
|
||||
|
||||
def create_sym_link(self, link, target, target_is_dir=True):
|
||||
"""If target_is_dir is True, a junction will be created.
|
||||
|
||||
NOTE: Junctions only work on same filesystem.
|
||||
"""
|
||||
|
||||
self._win32_utils.run_and_check_output(kernel32.CreateSymbolicLinkW,
|
||||
link,
|
||||
target,
|
||||
target_is_dir,
|
||||
kernel32_lib_func=True)
|
||||
|
||||
def create_temporary_file(self, suffix=None, *args, **kwargs):
|
||||
fd, tmp_file_path = tempfile.mkstemp(suffix=suffix, *args, **kwargs)
|
||||
os.close(fd)
|
||||
return tmp_file_path
|
||||
|
||||
@contextlib.contextmanager
|
||||
def temporary_file(self, suffix=None, *args, **kwargs):
|
||||
"""Creates a random, temporary, closed file, returning the file's
|
||||
|
||||
path. It's different from tempfile.NamedTemporaryFile which returns
|
||||
an open file descriptor.
|
||||
"""
|
||||
|
||||
tmp_file_path = None
|
||||
try:
|
||||
tmp_file_path = self.create_temporary_file(suffix, *args, **kwargs)
|
||||
yield tmp_file_path
|
||||
finally:
|
||||
if tmp_file_path:
|
||||
fileutils.delete_if_exists(tmp_file_path)
|
||||
|
||||
def add_acl_rule(self, path, trustee_name,
|
||||
access_rights, access_mode,
|
||||
inheritance_flags=0):
|
||||
"""Adds the requested access rule to a file or object.
|
||||
|
||||
Can be used for granting/revoking access.
|
||||
"""
|
||||
p_to_free = []
|
||||
|
||||
try:
|
||||
sec_info = self._acl_utils.get_named_security_info(
|
||||
obj_name=path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION)
|
||||
p_to_free.append(sec_info['pp_sec_desc'].contents)
|
||||
|
||||
access = advapi32_def.EXPLICIT_ACCESS()
|
||||
access.grfAccessPermissions = access_rights
|
||||
access.grfAccessMode = access_mode
|
||||
access.grfInheritance = inheritance_flags
|
||||
access.Trustee.TrusteeForm = w_const.TRUSTEE_IS_NAME
|
||||
access.Trustee.pstrName = ctypes.c_wchar_p(trustee_name)
|
||||
|
||||
pp_new_dacl = self._acl_utils.set_entries_in_acl(
|
||||
entry_count=1,
|
||||
p_explicit_entry_list=ctypes.pointer(access),
|
||||
p_old_acl=sec_info['pp_dacl'].contents)
|
||||
p_to_free.append(pp_new_dacl.contents)
|
||||
|
||||
self._acl_utils.set_named_security_info(
|
||||
obj_name=path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=w_const.DACL_SECURITY_INFORMATION,
|
||||
p_dacl=pp_new_dacl.contents)
|
||||
finally:
|
||||
for p in p_to_free:
|
||||
self._win32_utils.local_free(p)
|
||||
|
||||
def copy_acls(self, source_path, dest_path):
|
||||
p_to_free = []
|
||||
|
||||
try:
|
||||
sec_info_flags = w_const.DACL_SECURITY_INFORMATION
|
||||
sec_info = self._acl_utils.get_named_security_info(
|
||||
obj_name=source_path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=sec_info_flags)
|
||||
p_to_free.append(sec_info['pp_sec_desc'].contents)
|
||||
|
||||
self._acl_utils.set_named_security_info(
|
||||
obj_name=dest_path,
|
||||
obj_type=w_const.SE_FILE_OBJECT,
|
||||
security_info_flags=sec_info_flags,
|
||||
p_dacl=sec_info['pp_dacl'].contents)
|
||||
finally:
|
||||
for p in p_to_free:
|
||||
self._win32_utils.local_free(p)
|
@ -1,104 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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 collections
|
||||
import ctypes
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from os_win._i18n import _
|
||||
from os_win import _utils
|
||||
from os_win import exceptions
|
||||
from os_win.utils import baseutils
|
||||
from os_win.utils import win32utils
|
||||
from os_win.utils.winapi import libs as w_lib
|
||||
|
||||
kernel32 = w_lib.get_shared_lib_handle(w_lib.KERNEL32)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiskUtils(baseutils.BaseUtils):
|
||||
|
||||
_wmi_namespace = 'root/microsoft/windows/storage'
|
||||
|
||||
def __init__(self):
|
||||
self._conn_storage = self._get_wmi_conn(self._wmi_namespace)
|
||||
self._win32_utils = win32utils.Win32Utils()
|
||||
|
||||
# Physical device names look like \\.\PHYSICALDRIVE1
|
||||
self._phys_dev_name_regex = re.compile(r'\\\\.*\\[a-zA-Z]*([\d]+)')
|
||||
|
||||
def _get_disk(self, disk_number):
|
||||
disk = self._conn_storage.Msft_Disk(Number=disk_number)
|
||||
if not disk:
|
||||
err_msg = _("Could not find the disk number %s")
|
||||
raise exceptions.DiskNotFound(err_msg % disk_number)
|
||||
return disk[0]
|
||||
|
||||
def get_disk_uid_and_uid_type(self, disk_number):
|
||||
disk = self._get_disk(disk_number)
|
||||
return disk.UniqueId, disk.UniqueIdFormat
|
||||
|
||||
def refresh_disk(self, disk_number):
|
||||
disk = self._get_disk(disk_number)
|
||||
disk.Refresh()
|
||||
|
||||
def get_device_number_from_device_name(self, device_name):
|
||||
matches = self._phys_dev_name_regex.findall(device_name)
|
||||
if matches:
|
||||
return matches[0]
|
||||
|
||||
err_msg = _("Could not find device number for device: %s")
|
||||
raise exceptions.DiskNotFound(err_msg % device_name)
|
||||
|
||||
@_utils.retry_decorator(exceptions=(exceptions.x_wmi,
|
||||
exceptions.OSWinException))
|
||||
def rescan_disks(self):
|
||||
ret = self._conn_storage.Msft_StorageSetting.UpdateHostStorageCache()
|
||||
|
||||
if isinstance(ret, collections.Iterable):
|
||||
ret = ret[0]
|
||||
|
||||
if ret:
|
||||
err_msg = _("Rescanning disks failed. Error code: %s.")
|
||||
raise exceptions.OSWinException(err_msg % ret)
|
||||
|
||||
def get_disk_capacity(self, path, ignore_errors=False):
|
||||
norm_path = os.path.abspath(path)
|
||||
|
||||
total_bytes = ctypes.c_ulonglong(0)
|
||||
free_bytes = ctypes.c_ulonglong(0)
|
||||
|
||||
try:
|
||||
self._win32_utils.run_and_check_output(
|
||||
kernel32.GetDiskFreeSpaceExW,
|
||||
ctypes.c_wchar_p(norm_path),
|
||||
None,
|
||||
ctypes.pointer(total_bytes),
|
||||
ctypes.pointer(free_bytes),
|
||||
kernel32_lib_func=True)
|
||||
return total_bytes.value, free_bytes.value
|
||||
except exceptions.Win32Exception as exc:
|
||||
LOG.error("Could not get disk %(path)s capacity info. "
|
||||
"Exception: %(exc)s",
|
||||
dict(path=path,
|
||||
exc=exc))
|
||||
if ignore_errors:
|
||||
return 0, 0
|
||||
else:
|
||||
raise exc
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user