Retire Freezer DR
As no interest or concerns were raised for retirement for freezer-dr [1] we proceed with the process of project retirement. [1] https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/message/3VQUSJHBOOH3MKIMHUINNV75REF3GS7Z/ Depends-On: https://review.opendev.org/c/openstack/project-config/+/938180 Change-Id: I274143ba5242043561a9dc9e6ad4f64a75bbaabc
This commit is contained in:
13
.coveragerc
13
.coveragerc
@@ -1,13 +0,0 @@
|
||||
# .coveragerc to control coverage.py
|
||||
[run]
|
||||
branch = True
|
||||
omit = tests/coverage/*
|
||||
|
||||
[path]
|
||||
source = freezer_dr
|
||||
|
||||
[html]
|
||||
directory = term
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,29 +0,0 @@
|
||||
__pycache__
|
||||
dist
|
||||
build
|
||||
.venv
|
||||
.idea
|
||||
.autogenerated
|
||||
.coverage
|
||||
cover/
|
||||
coverage.xml
|
||||
*.sw?
|
||||
.tox
|
||||
*.egg*
|
||||
*.py[co]
|
||||
.DS_Store
|
||||
*.log
|
||||
.stestr/
|
||||
subunit.log
|
||||
.eggs
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Coverage data
|
||||
.coverage.*
|
||||
|
||||
# sphinx
|
||||
doc/source/_static/*.sample
|
||||
doc/source/api/*
|
||||
doc/build/*
|
||||
|
||||
333
.pylintrc
333
.pylintrc
@@ -1,333 +0,0 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=no
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# DEPRECATED
|
||||
include-ids=no
|
||||
|
||||
# DEPRECATED
|
||||
symbols=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=W,C,R,E1120,import-error
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (RP0004).
|
||||
comment=no
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus extisting member attributes cannot be deduced by static analysis
|
||||
ignored-modules=distutils
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=REQUEST,acl_users,aq_parent
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,input,file
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for function names
|
||||
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Naming hint for constant names
|
||||
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for attribute names
|
||||
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for argument names
|
||||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Naming hint for class attribute names
|
||||
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Naming hint for inline iteration names
|
||||
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Naming hint for class names
|
||||
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Naming hint for module names
|
||||
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=__.*__
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_$|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
||||
@@ -1,4 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_path=${OS_TEST_PATH:-./tests/unit}
|
||||
top_dir=./
|
||||
|
||||
10
.zuul.yaml
10
.zuul.yaml
@@ -1,10 +0,0 @@
|
||||
- project:
|
||||
templates:
|
||||
- openstack-python3-yoga-jobs
|
||||
- publish-openstack-docs-pti
|
||||
check:
|
||||
jobs:
|
||||
- openstack-tox-pylint
|
||||
gate:
|
||||
jobs:
|
||||
- openstack-tox-pylint
|
||||
@@ -1,19 +0,0 @@
|
||||
The source repository for this project can be found at:
|
||||
|
||||
https://opendev.org/openstack/freezer-dr
|
||||
|
||||
Pull requests submitted through GitHub are not monitored.
|
||||
|
||||
To start contributing to OpenStack, follow the steps in the contribution guide
|
||||
to set up and use Gerrit:
|
||||
|
||||
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
|
||||
|
||||
Bugs should be filed on Storyboard,:
|
||||
|
||||
https://storyboard.openstack.org/#!/project/openstack/freezer-dr
|
||||
|
||||
For more specific information about contributing to this repository, see the
|
||||
freezer-dr contributor guide:
|
||||
|
||||
https://docs.openstack.org/freezer-dr/latest/contributor/contributing.html
|
||||
30
CREDITS.rst
30
CREDITS.rst
@@ -1,30 +0,0 @@
|
||||
Authors
|
||||
=======
|
||||
|
||||
- Fabrizio Fresco
|
||||
- Pierre-Arthur Mathieu
|
||||
- Saad Zaher Saad
|
||||
- Thibaut Lapierre
|
||||
|
||||
Maintainers
|
||||
===========
|
||||
|
||||
- Fabrizio Fresco
|
||||
- Pierre-Arthur Mathieu
|
||||
- Saad Zaher Saad
|
||||
- Thibaut Lapierre
|
||||
|
||||
Contributors
|
||||
============
|
||||
|
||||
- Saad Zaher Saad
|
||||
- Pierre-Arthur Mathieu
|
||||
- Stefano Canepa
|
||||
|
||||
Reviewers
|
||||
=========
|
||||
|
||||
- Fabrizio Fresco
|
||||
- Pierre-Arthur Mathieu
|
||||
- Saad Zaher Saad
|
||||
- Thibaut Lapierre
|
||||
41
HACKING.rst
41
HACKING.rst
@@ -1,41 +0,0 @@
|
||||
Freezer DR Style Commandments
|
||||
=============================
|
||||
|
||||
- Step 1: Read the OpenStack Style Commandments
|
||||
https://docs.openstack.org/hacking/latest/
|
||||
- Step 2: Read on
|
||||
|
||||
Freezer DR Specific Commandments
|
||||
--------------------------------
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
Use the common logging module, and ensure you ``getLogger``::
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
LOG.debug('Foobar')
|
||||
|
||||
|
||||
|
||||
Properly Calling Callables
|
||||
--------------------------
|
||||
|
||||
Methods, functions and classes can specify optional parameters (with default
|
||||
values) using Python's keyword arg syntax. When providing a value to such a
|
||||
callable we prefer that the call also uses keyword arg syntax. For example::
|
||||
|
||||
def f(required, optional=None):
|
||||
pass
|
||||
|
||||
# GOOD
|
||||
f(0, optional=True)
|
||||
|
||||
# BAD
|
||||
f(0, True)
|
||||
|
||||
This gives us the flexibility to re-order arguments and more importantly
|
||||
to add new required arguments. It's also more explicit and easier to read.
|
||||
201
LICENSE
201
LICENSE
@@ -1,201 +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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2013, Rackspace (http://www.rackspace.com)
|
||||
|
||||
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.
|
||||
58
README.rst
58
README.rst
@@ -1,51 +1,13 @@
|
||||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
This project is no longer maintained.
|
||||
|
||||
.. image:: https://governance.openstack.org/tc/badges/freezer-dr.svg
|
||||
:target: https://governance.openstack.org/tc/reference/tags/index.html
|
||||
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".
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
=========================
|
||||
Freezer Disaster Recovery
|
||||
=========================
|
||||
|
||||
freezer-dr, OpenStack Compute node High Available provides compute node high availability for OpenStack.
|
||||
Simply freezer-dr monitors all compute nodes running in a cloud deployment and if there is any failure
|
||||
in one of the compute nodes freezer-dr will fence this compute node then freezer-dr will try to evacuate all
|
||||
running instances on this compute node, finally freezer-dr will notify all users who have workload/instances
|
||||
running on this compute node as well as will notify the cloud administrators.
|
||||
|
||||
freezer-dr has a pluggable architecture so it can be used with:
|
||||
|
||||
1. Any monitoring system to monitor the compute nodes (currently we support only native OpenStack services status)
|
||||
2. Any fencing driver (currently supports IPMI, libvirt, ...)
|
||||
3. Any evacuation driver (currently supports evacuate api call, may be migrate ??)
|
||||
4. Any notification system (currently supports email based notifications, ...)
|
||||
|
||||
just by adding a simple plugin and adjust the configuration file to use this
|
||||
plugin or in future a combination of plugins if required
|
||||
|
||||
freezer-dr should run in the control plane, however the architecture supports different scenarios.
|
||||
For running freezer-dr under high availability mode, it should run with active passive mode.
|
||||
|
||||
|
||||
------------
|
||||
How it works
|
||||
------------
|
||||
|
||||
Starting freezer-dr:
|
||||
|
||||
1. freezer-dr Monitoring manager is going to load the required monitoring driver according to the configuration
|
||||
2. freezer-dr will query the monitoring system to check if it considers any compute nodes to be down ?
|
||||
3. if no, freezer-dr will exit displaying No failed nodes
|
||||
4. if yes, freezer-dr will call the fencing manager to fence the failed compute node
|
||||
5. Fencing manager will load the correct fencer according to the configuration
|
||||
6. once the compute node is fenced and is powered off now we will start the evacuation process
|
||||
7. freezer-dr will load the correct evacuation driver
|
||||
8. freezer-dr will evacuate all instances to another computes
|
||||
9. Once the evacuation process completed, freezer-dr will call the notification manager
|
||||
10. The notification manager will load the correct driver based on the configurations
|
||||
11. freezer-dr will start the notification process ...
|
||||
For an alternative project, please see Masakari at
|
||||
https://docs.openstack.org/masakari/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-discuss@lists.openstack.org or join #openstack-freezer on
|
||||
OFTC.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
pkg-config [platform:dpkg platform:suse]
|
||||
pkgconfig [platform:redhat]
|
||||
libvirt-dev [platform:dpkg]
|
||||
libvirt-devel [platform:rpm]
|
||||
app-emulation/libvirt [platform:gentoo]
|
||||
libvirt-python [platform:rpm]
|
||||
@@ -1,5 +0,0 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/freezer-dr.conf.sample
|
||||
wrap_width = 79
|
||||
namespace = freezer-dr
|
||||
namespace = oslo.log
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<mxfile type="device" userAgent="Mozilla/5.0 (X11; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0" version="5.2.7.1" editor="www.draw.io"><diagram>5V1fc6rIEv80qbr3ISkRQX1MzmZ3T9U9d09VtmrvPhIlyi6CFzF/9tOfHugfMANG1BkkxgeVcYChp//8uqenvbK/rF5/Sbz18ls898Or4WD+emX/dDUcWhNnTB+i5Y1bbNfKWxZJMOe2suEh+MfnxgG3boO5v5E6pnEcpsFabpzFUeTPUqnNS5L4Re72FIfyXdfeAncsGx5mXlhv/SOYp8u8dTJ0y/Zf/WCxxJ0td5r/8ujN/l4k8Tbi+10N7afslf+88nCt7EHte6JiEsd0GfFt9frFDwUlQaOcGj/v+LUYZOJHPJA9Jwx5XjbpG57Un9OD82GcpMt4EUdeeF+23mVP44tLDOhoma5C+mrRV7pr8vY/0X7j4PBP7vaXn6ZvPK/eNo2pqbz6f+J4zdfIxyMGsfOZuGkTb5MZ9xoxS3jJwi+moCAosaUfr3waDfVJ/NBLg2f58h6zx6LoV1KNvjDhmonI9372wi1f9GrohjSKu3nwLK4dBoso+8H9/1bM7F3oP6XlEX1b8Ke3IjLcRY8b8fE1Wm+p/ZYucS0e10+e/eTmjag9HMQJvX39/u2rYPnEn29wy0f6ofly/T/OHvPXeJNG3srPBFxwwrM323opHV/IA/629qOHlLQCT52fBl7YPH9ZC7Ffxkdya7WfIsCleAp5elkGqf+w9jJJeSHtLIvsTukiXkt96O9mScKvfAZU+5Q130upJ60Jq/FlRUe63HaK7ME8aNJfr0FaUV90JLTXPs3WSkGBSJKGYuWrKLsOVBZGU9FZ30nXCPO5IqUjhC+IMusURKkfeRE9Bh2RWW/Bf+Y5biRznG1N6hzHlk8/xzkmOA7fM54qWSznuKHEcu/SUGI5Zq8qy024nz6W41O/x8Qp5QwVpOYpGo14inCJfFB8Vkn92yTxxO3RbS06kHLceR9MvXKfcjLzK5ZTWzxjq9me0LUUQfkvWSWFAYhthSRU5jXxN8E/3mPWQUw5Pwf1du6unJ/EFNp3AAYzmkU/oQYhAQFhzlv+YRXM5xn/hN6jH94VSPJLHBIAELcGlmxkCebUmlwVKJkHKIHPJnm7HtxYrsvsxJS+tvPDYxkFsiufED89bYhXVXk8bNIsBuV6RPQgWTMtV9ZAVn1OS7k6hor8SBXW/0qSOIgyK7DbPhD5fm6DUpbx6nFLo+4AocBLA89NG+yF02AvrIEGgwFDVKHjG3myOnSI8KOoj+ggToA+ydyLujZJhY91tyFCB9Hi98zhuh5pgoADWTk4A9Y9FQrbDQQGbU6ibx3LsGP0sNym8/iFGLUHmMVhSwIKOXyFKg8OWBtqJxFft/+YhU6rYxZ2sPfqVszDWGZG11Z05OHwpXYJXfAFwQPlPtrgC7tiHxG+gGlPgC8nYhNmI63YBBNu2mE9SNq6Ri1ji5WhAdQCW2+SwKLTroiAOEt3rFP7hAAAwRhNFPWWM0ltPvbD0bEyFH0TC17dAUfZ5G/SLFQ42JDpvyaB7SUcBRJSqV+FAghx6IejfLPLhqMyf7vTDuEom9wOnU88ShU02axhu48tYjQS5nj2pFD+IF2K91x2+xBQlMH5BILWRUDRxox2yDDamWOXeZDpOkU4Tb95wCNVuO5hO5v5m83TNuvYPzMADxBMN+VHkDxCbqsyHSIVJzGdEY/QwLqJ3bCyCw1zBt1WX+v9RbgEgzDYiI9YAI4gIhRCETGBTmIRJcuVXbG00j99N0aouBN91/0CimO7EvPdTCkaww3f/SSgZxAO7PtcKYUk2MloHZJwFUWI4OrxIYnJWJmLHbq0rpQdORpaG8sO8H9wbEMZMO6jLbZRB7IfJrYBGThDbANLOnAwtEY3ECarzMmfl+dcKLLs1PMdRoacC7sez/ttm4o0IdkEAWnPZXPUu9SREcuBig+7sEOYo4a8rac4E5WSMEjVEj9c51wo0rIsZ/3amMeVXWVby9lBw30+PUGGDn6nNMZ3s3+K0ypttWtTWz7qHTP7oUXOkaPkFtZSqmxiyp+Hnmxgk90TrIeRvsVRkMYJkfNd/pDnvS3bXBR/jGU9AtGu8MfUFHsYCTe/g2atmwE5bxKanZB8HIpmpeRZttpVhDs2EyI4FESOEfYtFpalBbK9/bGsrwt0WmaWUo9cv2maOEQVuneOMZqKpryNxJ04RJ8vxfcxEANXCvkhgLEV/dGUwIq2k4hmbIW2N9rZdRXt3LDeYcp6I1Zmfn9DK4GFcEoC2za9QbvAYjQV3vvDC3IfsOPkmImSP4QE5apPxVwksQi3nUQFtnRdWnB6PDkeRT7lKRYcPNRHC674eC5vBdtpwZX+igWv9x+wBm1j8etnI/KJs1XPUt8KhaMVORzNFg4rX9NrP66yLWQMFW+AsvWo/McEHi5CsR0AD6eeT3HpwMNF2LUL4MF+4SVHQh2sWALYjVkQO4iEYkNxX4BdgyfmssI/A7Cre2JfvKyHFHkUG6j7kRsNmF6sR7L07IsDAyucRCytCTu78V8rPtLPMzusMzKisJqIlQX91hlLnz2RVFBYklR++O4lFaOR01Z6nbWiLGWPGzbTgLkkzKKk/hxFrTrQuzjMAsMGwUSqdAeYBXCpQw/FbZtIcaK+GysZocV2L80bOsYK5NzvvTaPS1e8GvSt6hfBvWKzXrm4RBlKQSWX2qNaJaJsBTGt+JyvxF6/HsCEmtGC69/FcjG2v14yoC/8TzhMDZFaU4De/QSpI67F5h4M3JDvYIq+FhL/teOw+lY/s7V+gNYkBJfpuS4AW3295lZWlKw+s23RojlYEStlqTg99LSmDYF2c6UzerX5DfHxKhthCdO4IxA//iWqlFHWlkgf5AosmTtORjjbLOGtg4KXIn+bJnF0nbdlZZFmSz+rILR8W1NdqGCTFYSSeotcsGdh1ak0WcaIRI5sy37yd5YXlkdFM0KFxHFLqojmJ3KVNExcTip5UrtlW0sO8iMAKpUYakiVP8LpoEOenGYmRqrJ+z6IH80JuMUvIoU09DabYNaSJm0ziqkkx8BRzDWuUaFKU1UDtJ0KqVF7CdAVluvQTYS1C+FYNzZHyFsZ8M5xqf15XHtWlrQm1E61bg46MTiF4kFVjYm8qu5DJxhN1bUJfX/9L8qgfgoW2yTDhHSPFZGjyIxNaXG11JTb1SMttPJPiRjBv89glC0QEfknwMJV9QahkIyyhpjKpB6BOlibtdglrSx4n8RzwCx7ea4bRaioL3Vxrq0aVJOh7ZYLpgfvy1DvoztFDrVgutvgcyxvNVUNxY72Myi0enTzwyo0eYtR4XV0otDqAO14hSYxXenoNmg0woUnsF1r+HcWlVaU7zxUpylbwIokfQN1P/QWfTOpdXigskU7n9ZpiBB/TK1D2ygUQ9wQ2zCmderh4cO1zjFg6aiN0d1okSHKKBSLzHrUyMicGkHt4t5rEbgMki+mvZxTW+7HaE7ZS2c1bYEqazg0VNdutfGt08I+lrxRzcaCdUXeEECWFJCSfHHUFBjz41pHVFvHArpRPzbSoIBC1WKz7atcKQWb1DpMmjwzG5tBlPvo8symdQfj4ko/2cpcjcbdbTaB/q3Q955kKEvjESrunmRPLP70oYC6vBBpAwVWFVUTlTTk28E3rFApogD6rg2u79DpYzMqAm+YgpaMqmKf4/41oU85j0XIRY7DnG1bVDGez45m1DqF2OvcAZopimycEc60DwWCnKj9h/QZVJBtiXcOgDbKfrlxyxo+e6HN2BS0UfLicB99Qec6/L08bINSgeCvcT3/zRS2KZJCemIytFuHZr4toH5Rq9VYgdvimT4AfFQtQ8OCpSn4aOGvBT45fhypnNkQazCGH2FuzhUma7/YUkTW5XD7+cBlw19vfU5wqW4jqWMlY+ASeyrOCS5br/iAnEiveid3WBO4VKrDT5EpeSq4nCKhSju4lNEw7qMNXCJactHgUtmvPwXY7ARc9inNzYB1aAcui38TNrE8/mHB5QTS1wW4LP4z77ODS3kha9JQINocuKxvsTjaOh+pAlqvpHazkjVC4YqigtaRK1njI4tIH2qRVc1m87r6rnGp/Sn09n5utouMpLYnqE+unqChMmBddVzgBjZZLxT/I6B/Axsdlv+Dn88HWZvlN9qiKnr8AA==</diagram></mxfile>
|
||||
@@ -1,263 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Freezer documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Feb 4 22:27:35 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinxcontrib.apidoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'openstackdocstheme']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Freezer'
|
||||
copyright = u'2016, OpenStack'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
# html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Freezerdrdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Freezerdr.tex', u'Freezer DR Documentation',
|
||||
u'OpenStack', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'freezer-dr', u'Freezer DR Documentation',
|
||||
[u'OpenStack'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Freezer DR', u'Freezer Disaster Recovery Documentation',
|
||||
u'OpenStack', 'Freezer Disaster Recovery', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
|
||||
# -- sphinxcontrib.apidoc configuration --------------------------------------
|
||||
|
||||
apidoc_module_dir = '../../freezer_dr'
|
||||
apidoc_output_dir = 'api'
|
||||
apidoc_excluded_paths = [
|
||||
'tests',
|
||||
]
|
||||
@@ -1,47 +0,0 @@
|
||||
============================
|
||||
So You Want to Contribute...
|
||||
============================
|
||||
For general information on contributing to OpenStack, please check out the
|
||||
`contributor guide <https://docs.openstack.org/contributors/>`_ to get started.
|
||||
It covers all the basics that are common to all OpenStack projects: the accounts
|
||||
you need, the basics of interacting with our Gerrit review system, how we
|
||||
communicate as a community, etc.
|
||||
Below will cover the more project specific information you need to get started
|
||||
with freezer-dr.
|
||||
|
||||
Communication
|
||||
~~~~~~~~~~~~~
|
||||
* IRC channel #openstack-freezer at OFTC
|
||||
* Mailing list (prefix subjects with ``[freezer]`` for faster responses)
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
|
||||
|
||||
Contacting the Core Team
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Please refer the `freezer-dr Core Team
|
||||
<https://review.opendev.org/admin/groups/489539270c16cfb7990bb3f1a0f62f3e45a94635,members>`_ contacts.
|
||||
|
||||
New Feature Planning
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
freezer-dr features are tracked on `Storyboard <https://storyboard.openstack.org/#!/project/openstack/freezer-dr>`_.
|
||||
|
||||
Task Tracking
|
||||
~~~~~~~~~~~~~
|
||||
We track our tasks in `Storyboard <https://storyboard.openstack.org/#!/project/openstack/freezer-dr>`_.
|
||||
If you're looking for some smaller, easier work item to pick up and get started
|
||||
on, search for the 'low-hanging-fruit' tag.
|
||||
|
||||
Reporting a Bug
|
||||
~~~~~~~~~~~~~~~
|
||||
You found an issue and want to make sure we are aware of it? You can do so on
|
||||
`Storyboard <https://storyboard.openstack.org/#!/project/openstack/freezer-dr>`_.
|
||||
|
||||
Getting Your Patch Merged
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
All changes proposed to the freezer-dr project require one +2 votes
|
||||
from freezer-dr core reviewers with approving patch by giving
|
||||
``Workflow +1`` vote.
|
||||
|
||||
Project Team Lead Duties
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
All common PTL duties are enumerated in the `PTL guide
|
||||
<https://docs.openstack.org/project-team-guide/ptl.html>`_.
|
||||
@@ -1,32 +0,0 @@
|
||||
=====================================================
|
||||
Welcome to Freezer's Disaster Recovery documentation!
|
||||
=====================================================
|
||||
|
||||
freezer-dr, OpenStack Compute node High Available provides compute node high
|
||||
availability for OpenStack. Simply freezer-dr monitors all compute nodes running
|
||||
in a cloud deployment and if there is any failure in one of the compute nodes
|
||||
freezer-dr will fence this compute node then freezer-dr will try to evacuate
|
||||
all running instances on this compute node, finally freezer-dr will notify all
|
||||
users who have workload/instances running on this compute node as well as will
|
||||
notify the cloud administrators.
|
||||
|
||||
For Contributors
|
||||
================
|
||||
|
||||
* If you are a new contributor to freezer-dr please refer: :doc:`contributor/contributing`
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
contributor/contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
api/modules
|
||||
@@ -1,270 +0,0 @@
|
||||
[DEFAULT]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# Time to wait between different operations (integer value)
|
||||
#wait = 30
|
||||
|
||||
#
|
||||
# From oslo.log
|
||||
#
|
||||
|
||||
# If set to true, the logging level will be set to DEBUG instead of the default
|
||||
# INFO level. (boolean value)
|
||||
# Note: This option can be changed without restarting.
|
||||
#debug = false
|
||||
|
||||
# The name of a logging configuration file. This file is appended to any
|
||||
# existing logging configuration files. For details about logging configuration
|
||||
# files, see the Python logging module documentation. Note that when logging
|
||||
# configuration files are used then all logging configuration is set in the
|
||||
# configuration file and other logging configuration options are ignored (for
|
||||
# example, logging_context_format_string). (string value)
|
||||
# Note: This option can be changed without restarting.
|
||||
# Deprecated group/name - [DEFAULT]/log_config
|
||||
#log_config_append = <None>
|
||||
|
||||
# Defines the format string for %%(asctime)s in log records. Default:
|
||||
# %(default)s . This option is ignored if log_config_append is set. (string
|
||||
# value)
|
||||
#log_date_format = %Y-%m-%d %H:%M:%S
|
||||
|
||||
# (Optional) Name of log file to send logging output to. If no default is set,
|
||||
# logging will go to stderr as defined by use_stderr. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logfile
|
||||
#log_file = <None>
|
||||
|
||||
# (Optional) The base directory used for relative log_file paths. This option
|
||||
# is ignored if log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logdir
|
||||
#log_dir = <None>
|
||||
|
||||
# Uses logging handler designed to watch file system. When log file is moved or
|
||||
# removed this handler will open a new log file with specified path
|
||||
# instantaneously. It makes sense only if log_file option is specified and
|
||||
# Linux platform is used. This option is ignored if log_config_append is set.
|
||||
# (boolean value)
|
||||
#watch_log_file = false
|
||||
|
||||
# Use syslog for logging. Existing syslog format is DEPRECATED and will be
|
||||
# changed later to honor RFC5424. This option is ignored if log_config_append
|
||||
# is set. (boolean value)
|
||||
#use_syslog = false
|
||||
|
||||
# Enable journald for logging. If running in a systemd environment you may wish
|
||||
# to enable journal support. Doing so will use the journal native protocol
|
||||
# which includes structured metadata in addition to log messages.This option is
|
||||
# ignored if log_config_append is set. (boolean value)
|
||||
#use_journal = false
|
||||
|
||||
# Syslog facility to receive log lines. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
#syslog_log_facility = LOG_USER
|
||||
|
||||
# Log output to standard error. This option is ignored if log_config_append is
|
||||
# set. (boolean value)
|
||||
#use_stderr = false
|
||||
|
||||
# Format string to use for log messages with context. (string value)
|
||||
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
|
||||
|
||||
# Format string to use for log messages when context is undefined. (string
|
||||
# value)
|
||||
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
|
||||
|
||||
# Additional data to append to log message when logging level for the message
|
||||
# is DEBUG. (string value)
|
||||
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
|
||||
|
||||
# Prefix each line of exception output with this format. (string value)
|
||||
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
|
||||
|
||||
# Defines the format string for %(user_identity)s that is used in
|
||||
# logging_context_format_string. (string value)
|
||||
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
||||
|
||||
# List of package logging levels in logger=LEVEL pairs. This option is ignored
|
||||
# if log_config_append is set. (list value)
|
||||
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,oslo_messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN,keystoneauth=WARN,oslo.cache=INFO,dogpile.core.dogpile=INFO
|
||||
|
||||
# Enables or disables publication of error events. (boolean value)
|
||||
#publish_errors = false
|
||||
|
||||
# The format for an instance that is passed with the log message. (string
|
||||
# value)
|
||||
#instance_format = "[instance: %(uuid)s] "
|
||||
|
||||
# The format for an instance UUID that is passed with the log message. (string
|
||||
# value)
|
||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
||||
|
||||
# Interval, number of seconds, of log rate limiting. (integer value)
|
||||
#rate_limit_interval = 0
|
||||
|
||||
# Maximum number of logged messages per rate_limit_interval. (integer value)
|
||||
#rate_limit_burst = 0
|
||||
|
||||
# Log level name used by rate limiting: CRITICAL, ERROR, INFO, WARNING, DEBUG
|
||||
# or empty string. Logs with level greater or equal to rate_limit_except_level
|
||||
# are not filtered. An empty string means that all levels are filtered. (string
|
||||
# value)
|
||||
#rate_limit_except_level = CRITICAL
|
||||
|
||||
# Enables or disables fatal status of deprecations. (boolean value)
|
||||
#fatal_deprecations = false
|
||||
|
||||
|
||||
[evacuation]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# Time in seconds to wait between retries to disable compute node or put it in
|
||||
# maintenance mode. Default 10 seconds (string value)
|
||||
#driver = freezer_dr.evacuators.drivers.default.standard.StandardEvacuator
|
||||
|
||||
# Time in seconds to wait between retries to disable compute node or put it in
|
||||
# maintenance mode. Default 10 seconds (integer value)
|
||||
#wait = 10
|
||||
|
||||
# Number of retries to put node in maintenance mode before reporting failure to
|
||||
# evacuate the node (integer value)
|
||||
#retries = 1
|
||||
|
||||
# Set this option to True in case your compute nodes are running on a shared
|
||||
# storage or False if not (boolean value)
|
||||
#shared_storage = false
|
||||
|
||||
# Dict contains kwargs to be passed to the evacuator driver. In case you have
|
||||
# additional args needs to be passed to your evacuator please, list them as
|
||||
# key0:value0, key1:value1, .... (dict value)
|
||||
#options =
|
||||
|
||||
|
||||
[fencer]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# YAML File contains the required credentials for compute nodes (string value)
|
||||
#credentials_file = <None>
|
||||
|
||||
# Number of retries to fence the each compute node. Must be at least 1 to try
|
||||
# first the soft shutdown (integer value)
|
||||
#retries = 1
|
||||
|
||||
# Time in seconds to wait between retries. Should be reasonable amount of time
|
||||
# as different servers take different times to shut off (integer value)
|
||||
#hold_period = 10
|
||||
|
||||
# Choose the best fencer driver i.e.(ipmi, libvirt, .. (string value)
|
||||
#driver = freezer_dr.fencers.drivers.ipmi.driver.IpmiDriver
|
||||
|
||||
# List of kwargs to customize the fencer operation. You fencer driver should
|
||||
# support these options. Options should be in key:value format (dict value)
|
||||
#options =
|
||||
|
||||
|
||||
[keystone_authtoken]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# OpenStack auth URI i.e. http://controller:5000 (string value)
|
||||
#auth_uri = <None>
|
||||
|
||||
# OpenStack auth URL i.e. http://controller:35357/v3 (string value)
|
||||
#auth_url = <None>
|
||||
|
||||
# OpenStack auth plugin i.e. ( password, token, ...) password is the only
|
||||
# available plugin for the time being (string value)
|
||||
#auth_plugin = <None>
|
||||
|
||||
# OpenStack username (string value)
|
||||
#username = <None>
|
||||
|
||||
# OpenStack Password (string value)
|
||||
#password = <None>
|
||||
|
||||
# OpenStack Project Name. (string value)
|
||||
#project_name = <None>
|
||||
|
||||
# OpenStack domain Name. (string value)
|
||||
#domain_name = <None>
|
||||
|
||||
# OpenStack Project Domain id, default is Default (string value)
|
||||
#project_domain_id = <None>
|
||||
|
||||
# OpenStack user Domain id, default is Default (string value)
|
||||
#user_domain_id = <None>
|
||||
|
||||
# OpenStack Project Domain name, default is Default (string value)
|
||||
#project_domain_name = <None>
|
||||
|
||||
# OpenStack user Domain name, default is Default (string value)
|
||||
#user_domain_name = <None>
|
||||
|
||||
# OpenStack Authentication arguments you can pass it here as Key:Value,
|
||||
# Key1:Value1, ... (dict value)
|
||||
#kwargs =
|
||||
|
||||
|
||||
[monitoring]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# Driver used to get a status updates of compute nodes (string value)
|
||||
#driver = freezer_dr.monitors.drivers.default.driver.StandardDriver
|
||||
|
||||
# configuration section name. This should contain your monitoring specific
|
||||
# configuration options. (string value)
|
||||
#backend_name = <None>
|
||||
|
||||
|
||||
[notifiers]
|
||||
|
||||
#
|
||||
# From freezer-dr
|
||||
#
|
||||
|
||||
# Notification driver to load it to notify users if something went wrong
|
||||
# (string value)
|
||||
#driver = freezer_dr.notifiers.drivers.default.default_email.StandardEmail
|
||||
|
||||
# Endpoint URL for the notification system. If you the driver you are using
|
||||
# doesnot require any URL just comment it or use none (string value)
|
||||
#endpoint = localhost
|
||||
|
||||
# Username to authenticate against the notification system. If the driver you
|
||||
# are using doesnot require any authentications comment or use None (string
|
||||
# value)
|
||||
#username = <None>
|
||||
|
||||
# Password to authenticate against the notification system. If the driver you
|
||||
# are using doesnot require any authentications comment or use None (string
|
||||
# value)
|
||||
#password = <None>
|
||||
|
||||
# Path to Jinja2 templates directory that contains message templates (string
|
||||
# value)
|
||||
#templates-dir = /etc/freezer/templates
|
||||
|
||||
# Key:Value Kwargs to pass it to the notification driver, if you want to pass
|
||||
# any special arguments for your driver. (dict value)
|
||||
#options =
|
||||
|
||||
# List of emails to sent them notification if something went wrong and Freezer
|
||||
# DR wasnot able to send an email to the tenant admin (list value)
|
||||
#notify-list =
|
||||
|
||||
# The sender address, it can be email address if we used default email driver,
|
||||
# or phone number if we use sms gateway for example. (string value)
|
||||
#notify-from = <None>
|
||||
@@ -1,62 +0,0 @@
|
||||
#
|
||||
# (c) Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
---
|
||||
|
||||
servers:
|
||||
|
||||
- id: deployer
|
||||
ip-addr: 192.168.10.254
|
||||
hostname: padawan-cp1-c0-m1-mgmt
|
||||
fencer-ip: 192.168.9.2
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
|
||||
- id: ccn-0001
|
||||
ip-addr: 192.168.10.3
|
||||
hostname: padawan-cp1-c1-m1-mgmt
|
||||
fencer-ip: 192.168.9.3
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
|
||||
- id: ccn-0002
|
||||
ip-addr: 192.168.10.4
|
||||
hostname: padawan-cp1-c1-m2-mgmt
|
||||
fencer-ip: 192.168.9.4
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
|
||||
- id: ccn-0003
|
||||
ip-addr: 192.168.10.5
|
||||
hostname: padawan-cp1-c1-m3-mgmt
|
||||
fencer-ip: 192.168.9.5
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
|
||||
- id: COMPUTE-0001
|
||||
ip-addr: 192.168.10.6
|
||||
hostname: padawan-ccp-comp0001-mgmt
|
||||
domain-name: padawan-vagrant_cpn-0001 # used for libvirt driver only !
|
||||
fencer-ip: 192.168.9.6
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
|
||||
- id: COMPUTE-0002
|
||||
ip-addr: 192.168.10.7
|
||||
hostname: padawan-ccp-comp0002-mgmt
|
||||
domain-name: padawan-vagrant_cpn-0002 # name of VM in kvm
|
||||
fencer-ip: 192.168.9.7
|
||||
fencer-password: password
|
||||
fencer-user: admin
|
||||
@@ -1,68 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>Dear Administrators, <br />
|
||||
A compute node failed and freezer-dr DID NOT successfully evacuate.
|
||||
Please, find the following details about the host: <br />
|
||||
Host: {{ host }} <br />
|
||||
<p>
|
||||
Tenants:
|
||||
{% for tenant in tenants %}
|
||||
{{ tenant.get('id') }} <br />
|
||||
{% endfor %}
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Instances: <br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Instance Name
|
||||
</td>
|
||||
<td>
|
||||
IP
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances %}
|
||||
<tr>
|
||||
<td> {{ instance.get('name') }} </td>
|
||||
<td>
|
||||
{% for key, value in instance.get('addresses').iteritems() %}
|
||||
<b>{{ key }}</b> :
|
||||
{{ value[0].get('addr', 'No IP') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
Host INFO:
|
||||
<table>
|
||||
{% for key, value in hypervisor.iteritems() %}
|
||||
<tr>
|
||||
<td> {{ key }} </td>
|
||||
<td> {{ value }} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
|
||||
TimeStamp: {{ evacuation_time }} <br />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Thanks for using <b>Freezer-DR !</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,46 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>Dear {{ name }}, <br />
|
||||
One of our compute nodes failed due to some technical problem. Your
|
||||
instances (listed below) running in tenant {{ tenant }} is being evacuated to another compute host.<br />
|
||||
Instances: <br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Instance Name
|
||||
</td>
|
||||
<td>
|
||||
IP
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances%}
|
||||
<tr>
|
||||
<td> {{ instance.get('name') }} </td>
|
||||
<td>
|
||||
{% for key, value in instance.get('addresses').iteritems() %}
|
||||
<b>{{ key }}</b> :
|
||||
{{ value[0].get('addr', 'No IP') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
TimeStamp: {{ evacuation_time }} <br />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Thanks for using <b>Freezer-DR !</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>Dear Administrators, <br />
|
||||
A compute node failed and freezer-dr successfully evacuated these instances running
|
||||
on this compute node to another node. Please, find the following details
|
||||
about the evacuated host: <br />
|
||||
Host: {{ host }} <br />
|
||||
<p>
|
||||
Tenants:
|
||||
{% for tenant in tenants %}
|
||||
{{ tenant.get('id') }} <br />
|
||||
{% endfor %}
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Instances: <br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Instance Name
|
||||
</td>
|
||||
<td>
|
||||
IP
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances %}
|
||||
<tr>
|
||||
<td> {{ instance.get('name') }} </td>
|
||||
<td>
|
||||
{% for key, value in instance.get('addresses').iteritems() %}
|
||||
<b>{{ key }}</b> :
|
||||
{{ value[0].get('addr', 'No IP') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
Host INFO:
|
||||
<table>
|
||||
{% for key, value in hypervisor.iteritems() %}
|
||||
<tr>
|
||||
<td> {{ key }} </td>
|
||||
<td> {{ value }} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
|
||||
TimeStamp: {{ evacuation_time }} <br />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Thanks for using <b>Freezer-DR !</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,46 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>Dear {{ name }}, <br />
|
||||
One of our compute nodes failed due to some technical problem. Your
|
||||
instances (listed below) are running in tenant {{ tenant }} Failed to Evacuate <br />
|
||||
Instances: <br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Instance Name
|
||||
</td>
|
||||
<td>
|
||||
IP
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances%}
|
||||
<tr>
|
||||
<td> {{ instance.get('name') }} </td>
|
||||
<td>
|
||||
{% for key, value in instance.get('addresses').iteritems() %}
|
||||
<b>{{ key }}</b> :
|
||||
{{ value[0].get('addr', 'No IP') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
TimeStamp: {{ evacuation_time }} <br />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Thanks for using <b>freezer-dr !</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,46 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>Dear {{ name }}, <br />
|
||||
One of our compute nodes failed due to some technical problem. Your
|
||||
instances (listed below) running in tenant {{ tenant }} Evacuated to another compute host <br />
|
||||
Instances: <br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Instance Name
|
||||
</td>
|
||||
<td>
|
||||
IP
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances%}
|
||||
<tr>
|
||||
<td> {{ instance.get('name') }} </td>
|
||||
<td>
|
||||
{% for key, value in instance.get('addresses').iteritems() %}
|
||||
<b>{{ key }}</b> :
|
||||
{{ value[0].get('addr', 'No IP') }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
TimeStamp: {{ evacuation_time }} <br />
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Thanks for using <b>Freezer-DR !</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,20 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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.
|
||||
|
||||
# Freezer-DR Versions
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo('freezer-dr').version_string()
|
||||
@@ -1,347 +0,0 @@
|
||||
"""Manage all configuration the OpenStack way."""
|
||||
|
||||
# (c) Copyright 2016 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 sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from freezer_dr import __version__ as FREEZER_DR_VERSION
|
||||
from freezer_dr.common.utils import env
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
_MONITORS = [
|
||||
cfg.StrOpt('driver',
|
||||
default='freezer_dr.monitors.drivers.default.driver.'
|
||||
'StandardDriver',
|
||||
help='Driver used to get a status updates of compute nodes'),
|
||||
cfg.StrOpt('backend_name',
|
||||
help='configuration section name. This should contain your '
|
||||
'monitoring specific configuration options.')
|
||||
]
|
||||
|
||||
_COMMON = [
|
||||
cfg.IntOpt('wait',
|
||||
default=30,
|
||||
help='Time to wait between different operations')
|
||||
]
|
||||
|
||||
_FENCER = [
|
||||
cfg.StrOpt('credentials-file',
|
||||
help='YAML File contains the required credentials for compute '
|
||||
'nodes'),
|
||||
cfg.IntOpt('retries',
|
||||
default=1,
|
||||
help='Number of retries to fence the each compute node. Must be'
|
||||
' at least 1 to try first the soft shutdown'),
|
||||
cfg.IntOpt('hold-period',
|
||||
default=10,
|
||||
help='Time in seconds to wait between retries. Should be '
|
||||
'reasonable amount of time as different servers take '
|
||||
'different times to shut off'),
|
||||
cfg.StrOpt('driver',
|
||||
default='freezer_dr.fencers.drivers.ipmi.driver.IpmiDriver',
|
||||
help='Choose the best fencer driver i.e.(ipmi, libvirt, ..'),
|
||||
cfg.DictOpt('options',
|
||||
default={},
|
||||
help='List of kwargs to customize the fencer operation. You '
|
||||
'fencer driver should support these options. Options '
|
||||
'should be in key:value format')
|
||||
]
|
||||
|
||||
_KEYSTONE_AUTH_TOKEN = [
|
||||
cfg.StrOpt('auth_uri',
|
||||
help='OpenStack auth URI i.e. http://controller:5000',
|
||||
dest='auth_uri'),
|
||||
cfg.StrOpt('auth_url',
|
||||
help='OpenStack auth URL i.e. http://controller:35357/v3',
|
||||
dest='auth_url'),
|
||||
cfg.StrOpt('auth_plugin',
|
||||
help='OpenStack auth plugin i.e. ( password, token, ...) '
|
||||
'password is the only available plugin for the time '
|
||||
'being',
|
||||
dest='auth_plugin'),
|
||||
cfg.StrOpt('username',
|
||||
help='OpenStack username',
|
||||
dest='username'),
|
||||
cfg.StrOpt('password',
|
||||
help='OpenStack Password',
|
||||
dest='password'),
|
||||
cfg.StrOpt('project_name',
|
||||
help='OpenStack Project Name.',
|
||||
dest='project_name'),
|
||||
cfg.StrOpt('domain_name',
|
||||
help='OpenStack domain Name.',
|
||||
dest='domain_name'),
|
||||
cfg.StrOpt('project_domain_id',
|
||||
help='OpenStack Project Domain id, default is Default',
|
||||
dest='project_domain_id'),
|
||||
cfg.StrOpt('user_domain_id',
|
||||
help='OpenStack user Domain id, default is Default',
|
||||
dest='user_domain_id'),
|
||||
cfg.StrOpt('project_domain_name',
|
||||
help='OpenStack Project Domain name, default is Default',
|
||||
dest='project_domain_name'),
|
||||
cfg.StrOpt('user_domain_name',
|
||||
help='OpenStack user Domain name, default is Default',
|
||||
dest='user_domain_name'),
|
||||
cfg.DictOpt('kwargs',
|
||||
help='OpenStack Authentication arguments you can pass it here'
|
||||
' as Key:Value, Key1:Value1, ... ',
|
||||
dest='kwargs',
|
||||
default={})
|
||||
]
|
||||
|
||||
_EVACUATION = [
|
||||
cfg.StrOpt('driver',
|
||||
default='freezer_dr.evacuators.drivers.default.standard.'
|
||||
'StandardEvacuator',
|
||||
help='Time in seconds to wait between retries to disable '
|
||||
'compute node or put it in maintenance mode. Default '
|
||||
'10 seconds',
|
||||
dest='driver'),
|
||||
cfg.IntOpt('wait',
|
||||
default=10,
|
||||
help='Time in seconds to wait between retries to disable '
|
||||
'compute node or put it in maintenance mode. Default '
|
||||
'10 seconds',
|
||||
dest='wait'),
|
||||
cfg.IntOpt('retries',
|
||||
default=1,
|
||||
help='Number of retries to put node in maintenance mode before'
|
||||
' reporting failure to evacuate the node',
|
||||
dest='retries'),
|
||||
cfg.BoolOpt('shared-storage',
|
||||
default=False,
|
||||
help='Set this option to True in case your compute nodes are '
|
||||
'running on a shared storage or False if not',
|
||||
dest='shared_storage'),
|
||||
cfg.DictOpt('options',
|
||||
default={},
|
||||
help='Dict contains kwargs to be passed to the evacuator '
|
||||
'driver. In case you have additional args needs to be '
|
||||
'passed to your evacuator please, list them as '
|
||||
'key0:value0, key1:value1, ...',
|
||||
dest='options')
|
||||
]
|
||||
|
||||
_NOTIFIERS = [
|
||||
cfg.StrOpt('driver',
|
||||
default='freezer_dr.notifiers.drivers.default.default_email.'
|
||||
'StandardEmail',
|
||||
dest='driver',
|
||||
help='Notification driver to load it to notify users '
|
||||
'if something went wrong. There are two supported drivers'
|
||||
': freezer_dr.notifiers.drivers.default.default_email.'
|
||||
'StandardEmail and freezer_dr.notifiers.drivers.default.'
|
||||
'slack.slack.SlackNotifier'),
|
||||
cfg.StrOpt('endpoint',
|
||||
default='localhost',
|
||||
dest='endpoint',
|
||||
help='Endpoint URL for the notification system. If you the '
|
||||
'driver you are using doesnot require any URL just comment'
|
||||
' it or use none'),
|
||||
cfg.StrOpt('username',
|
||||
default=None,
|
||||
dest='username',
|
||||
help='Username to authenticate against the notification system.'
|
||||
' If the driver you are using doesnot require any '
|
||||
'authentications comment or use None'),
|
||||
cfg.StrOpt('password',
|
||||
default=None,
|
||||
dest='password',
|
||||
help='Password to authenticate against the notification system. '
|
||||
'If the driver you are using doesnot require any '
|
||||
'authentications comment or use None'),
|
||||
cfg.StrOpt('templates-dir',
|
||||
dest='templates-dir',
|
||||
default='/etc/freezer/templates',
|
||||
help='Path to Jinja2 templates directory that contains '
|
||||
'message templates'),
|
||||
cfg.DictOpt('options',
|
||||
default={},
|
||||
dest='options',
|
||||
help='Key:Value Kwargs to pass it to the notification driver, '
|
||||
'if you want to pass any special arguments for your '
|
||||
'driver. If you want to use the SlackNotifier driver, '
|
||||
'set as: options = slack_timeout:512,'
|
||||
'slack_ca_certs:/ca.crt,slack_insecured:True'),
|
||||
cfg.ListOpt('notify-list',
|
||||
default=[],
|
||||
dest='notify-list',
|
||||
help='List of emails to sent them notification if something '
|
||||
'went wrong and Freezer DR wasnot able to send an email '
|
||||
'to the tenant admin'),
|
||||
cfg.StrOpt('notify-from',
|
||||
dest='notify-from',
|
||||
help='The sender address, it can be email address if we used '
|
||||
'default email driver, or phone number if we use sms '
|
||||
'gateway for example.')
|
||||
]
|
||||
|
||||
|
||||
def build_os_options():
|
||||
"""Build oslo options related to OpenStack environment."""
|
||||
osclient_opts = [
|
||||
cfg.StrOpt('os-username',
|
||||
default=env('OS_USERNAME'),
|
||||
help='Name used for authentication with the OpenStack '
|
||||
'Identity service. Defaults to env[OS_USERNAME].',
|
||||
dest='os_username'),
|
||||
cfg.StrOpt('os-password',
|
||||
default=env('OS_PASSWORD'),
|
||||
help='Password used for authentication with the OpenStack '
|
||||
'Identity service. Defaults to env[OS_PASSWORD].',
|
||||
dest='os_password'),
|
||||
cfg.StrOpt('os-project-name',
|
||||
default=env('OS_PROJECT_NAME'),
|
||||
help='Project name to scope to. Defaults to '
|
||||
'env[OS_PROJECT_NAME].',
|
||||
dest='os_project_name'),
|
||||
cfg.StrOpt('os-project-domain-name',
|
||||
default=env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Domain name containing project. Defaults to '
|
||||
'env[OS_PROJECT_DOMAIN_NAME].',
|
||||
dest='os_project_domain_name'),
|
||||
cfg.StrOpt('os-user-domain-name',
|
||||
default=env('OS_USER_DOMAIN_NAME'),
|
||||
help='User\'s domain name. Defaults to '
|
||||
'env[OS_USER_DOMAIN_NAME].',
|
||||
dest='os_user_domain_name'),
|
||||
cfg.StrOpt('os-auth-url',
|
||||
default=env('OS_AUTH_URL'),
|
||||
help='Specify the Identity endpoint to use for '
|
||||
'authentication. Defaults to env[OS_AUTH_URL].',
|
||||
dest='os_auth_url'),
|
||||
cfg.StrOpt('os-backup-url',
|
||||
default=env('OS_BACKUP_URL'),
|
||||
help='Specify the Freezer backup service endpoint to use. '
|
||||
'Defaults to env[OS_BACKUP_URL].',
|
||||
dest='os_backup_url'),
|
||||
cfg.StrOpt('os-region-name',
|
||||
default=env('OS_REGION_NAME'),
|
||||
help='Specify the region to use. Defaults to '
|
||||
'env[OS_REGION_NAME].',
|
||||
dest='os_region_name'),
|
||||
cfg.StrOpt('os-token',
|
||||
default=env('OS_TOKEN'),
|
||||
help='Specify an existing token to use instead of '
|
||||
'retrieving one via authentication (e.g. '
|
||||
'with username & password). Defaults to '
|
||||
'env[OS_TOKEN].',
|
||||
dest='os_token'),
|
||||
cfg.StrOpt('os-identity-api-version',
|
||||
default=env('OS_IDENTITY_API_VERSION'),
|
||||
help='Identity API version: 2.0 or 3. '
|
||||
'Defaults to env[OS_IDENTITY_API_VERSION]',
|
||||
dest='os_identity_api_version'),
|
||||
cfg.StrOpt('os-endpoint-type',
|
||||
choices=['public', 'publicURL', 'internal', 'internalURL',
|
||||
'admin', 'adminURL'],
|
||||
default=env('OS_ENDPOINT_TYPE') or 'public',
|
||||
help='Endpoint type to select. Valid endpoint types: '
|
||||
'"public" or "publicURL", "internal" or "internalURL"'
|
||||
', "admin" or "adminURL". Defaults to '
|
||||
'env[OS_ENDPOINT_TYPE] or "public"',
|
||||
dest='os_endpoint_type'),
|
||||
]
|
||||
|
||||
return osclient_opts
|
||||
|
||||
|
||||
def configure():
|
||||
"""Register configuration."""
|
||||
CONF.register_cli_opts(build_os_options())
|
||||
CONF.register_opts(_COMMON)
|
||||
monitors_grp = cfg.OptGroup('monitoring',
|
||||
title='Monitoring',
|
||||
help='Monitoring Driver/plugin to be used to '
|
||||
'monitor compute nodes')
|
||||
CONF.register_group(monitors_grp)
|
||||
CONF.register_opts(_MONITORS, group='monitoring')
|
||||
|
||||
fencers_grp = cfg.OptGroup('fencer',
|
||||
title='fencer Options',
|
||||
help='fencer Driver/plugin to be used to '
|
||||
'fence compute nodes')
|
||||
CONF.register_group(fencers_grp)
|
||||
CONF.register_opts(_FENCER, group='fencer')
|
||||
|
||||
# Evacuation Section :)
|
||||
evacuators_grp = cfg.OptGroup('evacuation',
|
||||
title='Evacuation Options',
|
||||
help='Evacuation Driver/plugin opts to be '
|
||||
'used to Evacuate compute nodes')
|
||||
CONF.register_group(evacuators_grp)
|
||||
CONF.register_opts(_EVACUATION, group='evacuation')
|
||||
|
||||
# Notification Section :)
|
||||
notifiers_grp = cfg.OptGroup('notifiers',
|
||||
title='Notification Options',
|
||||
help='Notification Driver/plugin opts to be '
|
||||
'used to Notify admins/users if failure'
|
||||
' happens')
|
||||
CONF.register_group(notifiers_grp)
|
||||
CONF.register_opts(_NOTIFIERS, group='notifiers')
|
||||
|
||||
# Keystone Auth
|
||||
keystone_grp = cfg.OptGroup('keystone_authtoken',
|
||||
title='Keystone Auth Options',
|
||||
help='OpenStack Credentials to call the nova '
|
||||
'APIs to evacuate ')
|
||||
CONF.register_group(keystone_grp)
|
||||
CONF.register_opts(_KEYSTONE_AUTH_TOKEN, group='keystone_authtoken')
|
||||
|
||||
default_conf = cfg.find_config_files('freezer', 'freezer-dr', '.conf')
|
||||
log.register_options(CONF)
|
||||
|
||||
CONF(args=sys.argv[1:],
|
||||
project='freezer',
|
||||
default_config_files=default_conf,
|
||||
version=FREEZER_DR_VERSION)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Set some oslo log defaults."""
|
||||
_DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'boto=WARN',
|
||||
'qpid=WARN', 'stevedore=WARN', 'oslo_log=INFO',
|
||||
'iso8601=WARN',
|
||||
'requests.packages.urllib3.connectionpool=WARN',
|
||||
'urllib3.connectionpool=WARN', 'websocket=WARN',
|
||||
'keystonemiddleware=WARN', 'freezer-dr=INFO']
|
||||
|
||||
_DEFAULT_LOGGING_CONTEXT_FORMAT = (
|
||||
'%(asctime)s.%(msecs)03d %(process)d '
|
||||
'%(levelname)s %(name)s [%(request_id)s '
|
||||
'%(user_identity)s] %(instance)s'
|
||||
'%(message)s')
|
||||
log.set_defaults(_DEFAULT_LOGGING_CONTEXT_FORMAT, _DEFAULT_LOG_LEVELS)
|
||||
log.setup(CONF, 'freezer-dr', version=FREEZER_DR_VERSION)
|
||||
|
||||
|
||||
def list_opts():
|
||||
_OPTS = {
|
||||
None: _COMMON,
|
||||
'monitoring': _MONITORS,
|
||||
'keystone_authtoken': _KEYSTONE_AUTH_TOKEN,
|
||||
'fencer': _FENCER,
|
||||
'evacuation': _EVACUATION,
|
||||
'notifiers': _NOTIFIERS
|
||||
}
|
||||
|
||||
return _OPTS.items()
|
||||
@@ -1,182 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Generic deamon."""
|
||||
# (c) Copyright 2016 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 atexit
|
||||
import logging as log
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from signal import SIGTERM
|
||||
|
||||
|
||||
class Daemon(object):
|
||||
|
||||
"""A generic daemon class.
|
||||
|
||||
Usage: subclass the Daemon class and override the run() method
|
||||
"""
|
||||
|
||||
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null',
|
||||
stderr='/dev/null'):
|
||||
"""Instantiantion."""
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.pidfile = pidfile
|
||||
|
||||
def daemonize(self):
|
||||
"""Do the UNIX double-fork magic.
|
||||
|
||||
See Stevens' "Advanced Programming in the UNIX Environment" for
|
||||
details (ISBN 0201563177)
|
||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||
"""
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit first parent
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
sys.stderr.write("fork #1 failed: %d (%s)\n" %
|
||||
(e.errno, e.strerror))
|
||||
log.error(e)
|
||||
sys.exit(1)
|
||||
|
||||
# decouple from parent environment
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# do second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit from second parent
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
sys.stderr.write("fork #2 failed: %d (%s)\n"
|
||||
% (e.errno, e.strerror))
|
||||
log.error(e)
|
||||
sys.exit(1)
|
||||
|
||||
# redirect standard file descriptors
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open(self.stdin, 'r')
|
||||
so = open(self.stdout, 'a+')
|
||||
se = open(self.stderr, 'a+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
# write pidfile
|
||||
atexit.register(self.delpid)
|
||||
|
||||
pid = str(os.getpid())
|
||||
f = open(self.pidfile, 'w+')
|
||||
f.write("%s\n" % pid)
|
||||
f.close()
|
||||
|
||||
def delpid(self):
|
||||
"""Delete PID file."""
|
||||
os.remove(self.pidfile)
|
||||
|
||||
def start(self):
|
||||
"""Start the daemon."""
|
||||
log.error("Test")
|
||||
# Check for a pidfile to see if the daemon already runs
|
||||
try:
|
||||
pf = open(self.pidfile, 'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if pid:
|
||||
message = "pidfile %s already exist. Daemon" \
|
||||
" already running?\n"
|
||||
sys.stderr.write(message % self.pidfile)
|
||||
sys.exit(1)
|
||||
|
||||
# Start the daemon
|
||||
self.daemonize()
|
||||
self.run()
|
||||
|
||||
# @todo needs some enhancement like check /proc/%pid/status if it's
|
||||
# really running or not ! may be it's killed by external process
|
||||
# the PID won't be updated !
|
||||
def status(self):
|
||||
"""Check daemon status."""
|
||||
try:
|
||||
pf = open(self.pidfile, 'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if pid:
|
||||
message = "pidfile %s already exist. Daemon already " \
|
||||
"running. PID: %d \n"
|
||||
sys.stdout.write(message % (self.pidfile, pid))
|
||||
sys.exit(0)
|
||||
else:
|
||||
message = "Service not running!\n"
|
||||
sys.stdout.write(message)
|
||||
sys.exit(0)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the daemon."""
|
||||
# Get the pid from the pidfile
|
||||
try:
|
||||
pf = open(self.pidfile, 'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if not pid:
|
||||
message = "pidfile %s does not exist." \
|
||||
" Daemon not running?\n"
|
||||
sys.stderr.write(message % self.pidfile)
|
||||
return # not an error in a restart
|
||||
|
||||
# Try killing the daemon process
|
||||
try:
|
||||
while 1:
|
||||
os.kill(pid, SIGTERM)
|
||||
time.sleep(0.1)
|
||||
except OSError as err:
|
||||
err = str(err)
|
||||
if err.find("No such process") > 0:
|
||||
if os.path.exists(self.pidfile):
|
||||
os.remove(self.pidfile)
|
||||
else:
|
||||
print(str(err))
|
||||
sys.exit(1)
|
||||
|
||||
def restart(self):
|
||||
"""Restart the daemon."""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
"""You should override this method when you subclass Daemon.
|
||||
|
||||
It will be called after the process has been
|
||||
daemonized by start() or restart().
|
||||
"""
|
||||
@@ -1,311 +0,0 @@
|
||||
"""OpenStack client class."""
|
||||
# (c) Copyright 2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from keystoneclient import session
|
||||
|
||||
from keystoneclient.auth.identity import v3
|
||||
|
||||
from keystoneclient import client as keystoneclient
|
||||
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
|
||||
from novaclient import client as novaclient
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class OSClient:
|
||||
"""Provide OpenStack credentials to initalize the connection."""
|
||||
|
||||
def __init__(self, authurl, authmethod='password', ** kwargs):
|
||||
"""Initialize the all class vars.
|
||||
|
||||
:param authmethod: string authmethod should be password or token but
|
||||
currently we support only password !
|
||||
:param kwargs: username, user_id, project_name, project_id,
|
||||
default_domain_id,
|
||||
"""
|
||||
self.authmethod = authmethod
|
||||
self.authurl = authurl
|
||||
self.auth_session = None
|
||||
self.endpoint_type = 'internalURL'
|
||||
self.interface = 'internal'
|
||||
self.verify = True
|
||||
self.insecure = kwargs.pop('insecure', False)
|
||||
if self.insecure:
|
||||
self.verify = not bool(self.insecure)
|
||||
|
||||
if authmethod == 'password':
|
||||
if 'endpoint_type' in kwargs:
|
||||
self.endpoint_type = kwargs.pop('endpoint_type', 'internalURL')
|
||||
if 'interface' in kwargs:
|
||||
self.interface = kwargs.pop('interface', 'internal')
|
||||
self.kwargs = kwargs
|
||||
# self.username = kwargs.get('username', None)
|
||||
# self.password = kwargs.get('password')
|
||||
# self.project_name = kwargs.get('project_name', None)
|
||||
# self.project_id = kwargs.get('project_id', None)
|
||||
# self.user_id = kwargs.get('user_id', None)
|
||||
# self.user_domain_id = kwargs.get('user_domain_id', None)
|
||||
# self.user_domain_name = kwargs.get('user_domain_name', None)
|
||||
# self.project_domain_name =
|
||||
# kwargs.get('project_domain_name', None)
|
||||
# self.endpoint_type = kwargs.get('endpoint_type', 'internalURL')
|
||||
else:
|
||||
print("The available authmethod is password for the time being")
|
||||
print("Please, provide a password credential.")
|
||||
|
||||
self.auth()
|
||||
|
||||
def auth(self):
|
||||
"""Create a session."""
|
||||
auth = v3.Password(auth_url=self.authurl, reauthenticate=True,
|
||||
**self.kwargs)
|
||||
self.auth_session = session.Session(auth=auth, verify=self.verify)
|
||||
|
||||
def get_novaclient(self):
|
||||
if not hasattr(self, 'nova'):
|
||||
self.auth()
|
||||
self.nova = novaclient.Client('2', session=self.auth_session,
|
||||
endpoint_type=self.endpoint_type,
|
||||
insecure=self.insecure)
|
||||
return self.nova
|
||||
|
||||
def get_neutronclient(self):
|
||||
if not hasattr(self, 'neutron'):
|
||||
self.auth()
|
||||
self.neutron = neutronclient.Client(
|
||||
session=self.auth_session,
|
||||
endpoint_type=self.endpoint_type,
|
||||
insecure=self.insecure
|
||||
)
|
||||
return self.neutron
|
||||
|
||||
def novacomputes(self):
|
||||
nova = self.get_novaclient()
|
||||
services = nova.services.list()
|
||||
compute_nodes = []
|
||||
compute_hosts = []
|
||||
for service in services:
|
||||
service = service.to_dict()
|
||||
if service.get('binary') == 'nova-compute':
|
||||
compute_nodes.append(service)
|
||||
compute_hosts.append(service.get('host'))
|
||||
self.compute_hosts = compute_hosts
|
||||
return compute_nodes
|
||||
|
||||
def novahypervisors(self):
|
||||
nova = self.get_novaclient()
|
||||
hypervisors = nova.hypervisors.list()
|
||||
nova_hypervisors = []
|
||||
|
||||
for hypervisor in hypervisors:
|
||||
nova_hypervisors.append(hypervisor.to_dict())
|
||||
return nova_hypervisors
|
||||
|
||||
def neutronagents(self, hosts=[]):
|
||||
if not hosts:
|
||||
hosts = self.compute_hosts
|
||||
neutron = self.get_neutronclient()
|
||||
agents = neutron.list_agents()
|
||||
neutron_agents = []
|
||||
for agent in agents.get('agents'):
|
||||
if agent.get('host') in hosts and agent.get('binary') == \
|
||||
'neutron-openvswitch-agent':
|
||||
neutron_agents.append(agent)
|
||||
|
||||
return neutron_agents
|
||||
|
||||
def evacuate(self, nodes, shared_storage=False):
|
||||
"""
|
||||
Will get the hypervisors and list all running VMs on it and then start
|
||||
Evacuating one by one ...
|
||||
:param nodes: List of nodes to be evacuated !
|
||||
:param shared_storage: Boolean, True if your compute nodes are running
|
||||
under shared storage and False otherwise
|
||||
:return: List of nodes with VMs that were running on that node
|
||||
"""
|
||||
nova = self.get_novaclient()
|
||||
evacuated_nodes = []
|
||||
for node in nodes:
|
||||
hypervisors = nova.hypervisors.search(node.get('host'), True)
|
||||
for hypervisor in hypervisors:
|
||||
if not hasattr(hypervisor, 'servers'):
|
||||
break
|
||||
for server in hypervisor.servers:
|
||||
try:
|
||||
nova.servers.evacuate(server.get('uuid'),
|
||||
on_shared_storage=shared_storage)
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
host = {'host': node.get(
|
||||
'host'), 'servers': hypervisor.servers}
|
||||
evacuated_nodes.append(host)
|
||||
return evacuated_nodes
|
||||
|
||||
def set_in_maintenance(self, nodes):
|
||||
"""Set compute nodes in maintenance mode."""
|
||||
nova = self.get_novaclient()
|
||||
for node in nodes:
|
||||
output = []
|
||||
host = nova.hosts.get(node)[0]
|
||||
values = {"maintenance_mode": "enable"}
|
||||
try:
|
||||
output.append(host.update(values))
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return output
|
||||
|
||||
def get_session(self):
|
||||
"""Get the authentication section."""
|
||||
auth_session = session.Session(auth=self.auth_session.auth,
|
||||
verify=self.verify)
|
||||
return auth_session
|
||||
|
||||
def get_node_status(self, node):
|
||||
"""
|
||||
Check the node nova-service status and if it's disabled or not.
|
||||
:param node: dict contains node info
|
||||
:return: True or False. True => node disabled, False => node is enabled
|
||||
or unknow status !
|
||||
"""
|
||||
nova = self.get_novaclient()
|
||||
try:
|
||||
node_service = nova.services.find(host=node.get('host'))
|
||||
del nova
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return False
|
||||
|
||||
if not node_service:
|
||||
return False
|
||||
node = node_service.to_dict()
|
||||
if node.get('status') == 'disabled':
|
||||
return True
|
||||
return False
|
||||
|
||||
def disable_node(self, node):
|
||||
"""Disable nova on the failing node."""
|
||||
nova = self.get_novaclient()
|
||||
try:
|
||||
node_service = nova.services.find(host=node.get('host'))
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return False
|
||||
|
||||
if not node_service:
|
||||
return False
|
||||
node = node_service.to_dict()
|
||||
del node_service
|
||||
try:
|
||||
nova.services.disable_log_reason(
|
||||
host=node.get('host'),
|
||||
binary=node.get('binary'),
|
||||
reason='Host failed and needs to be evacuated.'
|
||||
)
|
||||
del nova
|
||||
LOG.info('Compute host: %s has been disabled to be evacuated. '
|
||||
'Host details: %s' % (node.get('host'), str(node)))
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_hypervisor_instances(self, node):
|
||||
"""Get instances from an hypervisor."""
|
||||
nova = self.get_novaclient()
|
||||
hypervisors = nova.hypervisors.search(node.get('host'), True)
|
||||
if not hypervisors:
|
||||
return []
|
||||
return hypervisors[0].servers
|
||||
|
||||
def get_hypervisor(self, node):
|
||||
"""Get an instance of the hypervisor.
|
||||
|
||||
:param node: dict contains host index
|
||||
:return: Hypervisor
|
||||
"""
|
||||
nova = self.get_novaclient()
|
||||
hypervisors = nova.hypervisors.search(node.get('host'), True)
|
||||
if not hypervisors:
|
||||
return None
|
||||
return hypervisors[0]
|
||||
|
||||
def get_instances_list(self, node):
|
||||
"""Get instances running on a node for all tenants."""
|
||||
nova = self.get_novaclient()
|
||||
servers = nova.servers.list(detailed=True,
|
||||
search_opts={'host': node.get('host'),
|
||||
'all_tenants': True})
|
||||
servers_data = []
|
||||
for server in servers:
|
||||
servers_data.append(server.to_dict())
|
||||
|
||||
return servers_data
|
||||
|
||||
def get_affected_tenants(self, node):
|
||||
return self.get_instances_list(node)
|
||||
|
||||
def list_tenants(self):
|
||||
"""List tenants."""
|
||||
auth_session = session.Session(auth=self.auth_session.auth)
|
||||
keystone = keystoneclient.Client(session=auth_session,
|
||||
endpoint_type=self.endpoint_type)
|
||||
projects = keystone.projects.list()
|
||||
|
||||
projects_data = []
|
||||
for project in projects:
|
||||
projects_data.append(project.to_dict())
|
||||
|
||||
return projects_data
|
||||
|
||||
def users_on_tenant(self, tenant):
|
||||
"""List user per project."""
|
||||
auth_session = session.Session(auth=self.auth_session.auth,
|
||||
verify=self.verify)
|
||||
keystone = keystoneclient.Client(session=auth_session,
|
||||
endpoint_type=self.endpoint_type,
|
||||
interface='internal',
|
||||
insecure=self.insecure)
|
||||
users = []
|
||||
try:
|
||||
users = keystone.users.list(default_project=tenant)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
users_list = []
|
||||
for user in users:
|
||||
users_list.append(user.to_dict())
|
||||
|
||||
return users_list
|
||||
|
||||
def get_hypervisors_stats(self):
|
||||
"""Get stats for all hypervisors."""
|
||||
nova = self.get_novaclient()
|
||||
stats = nova.hypervisor_stats.statistics()
|
||||
return stats.to_dict()
|
||||
|
||||
def get_hypervisor_details(self, node):
|
||||
"""Get details about hypervisor running on the provided node."""
|
||||
nova = self.get_novaclient()
|
||||
hypervisors = nova.hypervisors.list(detailed=True)
|
||||
for hypervisor in hypervisors:
|
||||
hypervisor = hypervisor.to_dict()
|
||||
if hypervisor.get('hypervisor_hostname') == node.get('host'):
|
||||
return hypervisor
|
||||
|
||||
return None
|
||||
@@ -1,97 +0,0 @@
|
||||
"""Utility functions shared from all modules into the project."""
|
||||
# (c) Copyright 2016 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 os
|
||||
|
||||
import jinja2
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from freezer_dr.common.osclient import OSClient
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def env(*env_vars, **kwargs):
|
||||
"""Get all environment variables."""
|
||||
for variable in env_vars:
|
||||
value = os.environ.get(variable, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def get_os_client():
|
||||
"""Return the OpenStack client.
|
||||
|
||||
Loads credentials from [keystone_authtoken] section in the configuration
|
||||
file and initialize the client and return an instance of the client
|
||||
:return: Initialized instance of OS Client
|
||||
"""
|
||||
credentials = CONF.get('keystone_authtoken')
|
||||
client = OSClient(
|
||||
authurl=credentials.get('auth_url'),
|
||||
username=credentials.get('username'),
|
||||
password=credentials.get('password'),
|
||||
project_name=credentials.get('project_name'),
|
||||
user_domain_id=credentials.get('user_domain_id'),
|
||||
project_domain_id=credentials.get('project_domain_id'),
|
||||
project_domain_name=credentials.get('project_domain_name'),
|
||||
user_domain_name=credentials.get('user_domain_name'),
|
||||
**credentials.get('kwargs')
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def load_jinja_templates(template_dir, template_name, template_vars):
|
||||
"""Load and render existing Jinja2 templates.
|
||||
|
||||
The main purpose of the function is to prepare the message to be sent and
|
||||
render it for the driver to send it directly.
|
||||
|
||||
:param template_dir: Location where jinja2 templates are stored
|
||||
:param template_name: name of the template to load it
|
||||
:param template_vars: Dict to replace existing vars in the template with
|
||||
values.
|
||||
:return: String message
|
||||
"""
|
||||
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
|
||||
template_env = jinja2.Environment(loader=template_loader)
|
||||
template = template_env.get_template(template_name)
|
||||
return template.render(template_vars)
|
||||
|
||||
|
||||
def get_admin_os_client():
|
||||
"""Return admin client data.
|
||||
|
||||
Loads credentials from [keystone_authtoken] section in the configuration
|
||||
file and initialize the client with admin privileges and return
|
||||
an instance of the client
|
||||
:return: Initialized instance of OS Client
|
||||
"""
|
||||
credentials = CONF.get('keystone_authtoken')
|
||||
client = OSClient(
|
||||
authurl=credentials.get('auth_url'),
|
||||
username=credentials.get('username'),
|
||||
password=credentials.get('password'),
|
||||
domain_name=credentials.get('domain_name'),
|
||||
user_domain_id=credentials.get('user_domain_id'),
|
||||
user_domain_name=credentials.get('user_domain_name'),
|
||||
**credentials.get('kwargs')
|
||||
)
|
||||
return client
|
||||
@@ -1,72 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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 os
|
||||
import yaml
|
||||
|
||||
|
||||
class YamlParser(object):
|
||||
|
||||
_INDEX = 'servers'
|
||||
|
||||
def __init__(self, yml_file, index='servers'):
|
||||
"""
|
||||
Provide Yaml file to parse it and process data
|
||||
:param yml_file: path to yaml file
|
||||
:param index: the key in the .yml file to get all servers listed under
|
||||
this key. the default 'is servers'
|
||||
"""
|
||||
self.file = yml_file
|
||||
self._INDEX = index
|
||||
self.data = self.parse()
|
||||
|
||||
def parse(self):
|
||||
if not self.file:
|
||||
raise Exception('No file specified !')
|
||||
if not os.path.exists(self.file) or not os.path.isfile(self.file):
|
||||
raise Exception('File desnot exists')
|
||||
|
||||
stream = open(self.file, 'r')
|
||||
data = yaml.load(stream)
|
||||
return data
|
||||
|
||||
def find_server_by_ip(self, ip):
|
||||
"""
|
||||
get server information ilo username, password and ip
|
||||
:param ip: mgmt ip address of the server, this should be the same like
|
||||
the ip in the .yml file
|
||||
:return: dict contains server information
|
||||
"""
|
||||
return self.find_server('ip-addr', ip)
|
||||
|
||||
def find_server_by_hostname(self, hostname):
|
||||
"""
|
||||
get server information ilo username, password and ip
|
||||
:param hostname: hostname matches one of the ones in the .yml file
|
||||
:return: dict contains the server information
|
||||
"""
|
||||
return self.find_server(key='hostname', value=hostname)
|
||||
|
||||
def find_server(self, key, value):
|
||||
"""
|
||||
Generic function to query the .yml file to get server information by any
|
||||
key.
|
||||
:param key:
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
for server in self.data.get(self._INDEX):
|
||||
if server.get(key) == value:
|
||||
return server
|
||||
|
||||
return None
|
||||
@@ -1,49 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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 abc
|
||||
|
||||
|
||||
class EvacuatorBaseDriver(object, metaclass=abc.ABCMeta):
|
||||
"""
|
||||
Abstract class for all evacuation drivers should implement to have
|
||||
a unified interface
|
||||
"""
|
||||
|
||||
def __init__(self, nodes, evacuator_conf, fencer):
|
||||
"""
|
||||
Initialize Evacuation driver with the config args
|
||||
:param nodes: A list of nodes to be evacuated!
|
||||
:param evacuator_conf: A dict of arguments that got loaded from the
|
||||
configuration file!
|
||||
:return: None
|
||||
"""
|
||||
self.nodes = nodes
|
||||
self.evacuator_conf = evacuator_conf
|
||||
self.fencer = fencer
|
||||
|
||||
@abc.abstractmethod
|
||||
def evacuate(self, enable_fencing=True):
|
||||
"""Evacuate the infected node.
|
||||
:return: Two lists; the first one will be the succeeded nodes and the
|
||||
other is the failed nodes
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_info(self):
|
||||
"""
|
||||
Get Driver Information
|
||||
:return: Dict contains driver information
|
||||
"""
|
||||
pass
|
||||
@@ -1,48 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.evacuators.common.utils import get_nodes_details
|
||||
from freezer_dr.fencers.common.manager import FencerManager
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
from time import sleep
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class EvacuationManager(object):
|
||||
|
||||
def __init__(self, enable_fencing=True):
|
||||
self.enable_fencing = enable_fencing
|
||||
|
||||
def evacuate(self, nodes):
|
||||
fencer = FencerManager(nodes)
|
||||
evacuation_conf = CONF.get('evacuation')
|
||||
driver = importutils.import_object(
|
||||
evacuation_conf['driver'],
|
||||
nodes,
|
||||
evacuation_conf,
|
||||
fencer
|
||||
)
|
||||
|
||||
return driver.evacuate(self.enable_fencing)
|
||||
|
||||
def get_nodes_details(self, nodes):
|
||||
"""
|
||||
To be re-structured after fixing the nova bug !
|
||||
:param nodes: list of nodes
|
||||
:return: list of node with more details
|
||||
"""
|
||||
return get_nodes_details(nodes)
|
||||
@@ -1,62 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.common.utils import get_admin_os_client
|
||||
from freezer_dr.common.utils import get_os_client
|
||||
|
||||
|
||||
def get_nodes_details(nodes):
|
||||
"""
|
||||
Get the hypervisor details, instances running on it, tenants
|
||||
:param nodes: list of hypervisors
|
||||
:return: List of hypervisors with detailed information
|
||||
"""
|
||||
nodes_details = []
|
||||
client = get_os_client()
|
||||
for node in nodes:
|
||||
instances = client.get_instances_list(node)
|
||||
tenants = set([instance.get('tenant_id') for instance in instances])
|
||||
node['instances'] = instances
|
||||
node['tenants'] = tenants
|
||||
node['details'] = client.get_hypervisor_details(node)
|
||||
nodes_details.append(node)
|
||||
nodes_details = get_users_on_tenants(nodes_details)
|
||||
return nodes_details
|
||||
|
||||
|
||||
def get_users_on_tenants(nodes):
|
||||
"""
|
||||
Lists all users that have access to a certain tenant.
|
||||
REQUIRE ADMIN PRIVILEGES !
|
||||
:param nodes: list of hypervisors
|
||||
:return: List of hypervisors with detailed tenant info
|
||||
"""
|
||||
details = []
|
||||
client = get_admin_os_client()
|
||||
for node in nodes:
|
||||
if 'tenants' in node:
|
||||
tenants = []
|
||||
for tenant in node.get('tenants'):
|
||||
users = client.users_on_tenant(tenant)
|
||||
tenants.append(
|
||||
{'id': tenant,
|
||||
'users': users,
|
||||
'instances': [instance for instance in
|
||||
node.get('instances') if
|
||||
instance.get('tenant_id') == tenant]})
|
||||
node['tenants'] = tenants
|
||||
details.append(node)
|
||||
return details
|
||||
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.common.utils import get_os_client
|
||||
from freezer_dr.evacuators.common.driver import EvacuatorBaseDriver
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import time
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class StandardEvacuator(EvacuatorBaseDriver):
|
||||
|
||||
def __init__(self, nodes, evacuator_conf, fencer):
|
||||
super(StandardEvacuator, self).__init__(nodes, evacuator_conf, fencer)
|
||||
# initialize the OS client!
|
||||
self.client = get_os_client()
|
||||
self.wait = evacuator_conf.get('wait')
|
||||
self.retires = evacuator_conf.get('retries', 1)
|
||||
if self.retires <= 0:
|
||||
self.retires = 1
|
||||
|
||||
def _disable_node(self, node):
|
||||
if not self.is_node_disabled(node):
|
||||
return self.disable_node(node)
|
||||
else:
|
||||
True
|
||||
|
||||
def evacuate(self, enable_fencing=True):
|
||||
# try to disable node
|
||||
# @todo needs more error handling like if the status didn't update or
|
||||
# we are unable to disable the node ???
|
||||
failed_nodes = [] # maintain nodes that are going to fail at any state
|
||||
succeeded_nodes = []
|
||||
for node in self.nodes:
|
||||
status = False
|
||||
for i in range(0, self.retires):
|
||||
status = self._disable_node(node)
|
||||
# if True ( node disabled ) break the loop
|
||||
if status:
|
||||
break
|
||||
else:
|
||||
status = False
|
||||
node['status'] = status
|
||||
# make sure the disable request was successful
|
||||
if not self.get_node_status(node):
|
||||
# if the node failed at any step no reason to move it to
|
||||
# the next step
|
||||
failed_nodes.append(node)
|
||||
self.nodes.remove(node) #
|
||||
else:
|
||||
succeeded_nodes.append(node)
|
||||
|
||||
nodes = succeeded_nodes
|
||||
if enable_fencing:
|
||||
nodes = self.fencer.fence(nodes=nodes)
|
||||
"""
|
||||
@todo this code needs to be commented for the time being till we fix
|
||||
nova bug found in state, which always go up afer enable or disable. We
|
||||
will use get_node_details for the time being from the main script to
|
||||
get nodes details before evacuating ...
|
||||
succeeded_nodes = []
|
||||
for node in nodes:
|
||||
node['instances'] = self.driver.get_node_instances(node)
|
||||
succeeded_nodes.append(node)
|
||||
|
||||
nodes = succeeded_nodes
|
||||
"""
|
||||
# Start evacuation calls ...
|
||||
evacuated_nodes = []
|
||||
for i in range(0, self.retires):
|
||||
try:
|
||||
time.sleep(self.wait)
|
||||
nodes = self.evacuate_nodes(nodes)
|
||||
if not nodes:
|
||||
break
|
||||
evacuated_nodes = nodes
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
return evacuated_nodes, failed_nodes
|
||||
|
||||
def get_node_instances(self, node):
|
||||
return self.client.get_hypervisor_instances(node)
|
||||
|
||||
def disable_node(self, node):
|
||||
return self.client.disable_node(node)
|
||||
|
||||
def get_node_status(self, node):
|
||||
return self.client.get_node_status(node)
|
||||
|
||||
def is_node_disabled(self, node):
|
||||
return self.client.get_node_status(node)
|
||||
|
||||
def evacuate_nodes(self, nodes):
|
||||
return self.client.evacuate(
|
||||
nodes, shared_storage=self.evacuator_conf['shared_storage'])
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
To be implemented.
|
||||
Get Driver Information
|
||||
:return: Dict contains driver information
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -1,41 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.evacuators.common.driver import EvacuatorBaseDriver
|
||||
|
||||
|
||||
class DummyEvacuator(EvacuatorBaseDriver):
|
||||
"""Evacuation driver that does nothing. Useful for testing other parts
|
||||
of Freezer-DR.
|
||||
"""
|
||||
|
||||
def __init__(self, nodes, evacuator_conf, fencer):
|
||||
super(DummyEvacuator, self).__init__(nodes, evacuator_conf, fencer)
|
||||
|
||||
def disable_node(self, node):
|
||||
return True
|
||||
|
||||
def get_node_status(self, node):
|
||||
return False
|
||||
|
||||
def is_node_disabled(self, node):
|
||||
return True
|
||||
|
||||
def evacuate_nodes(self, nodes):
|
||||
return nodes
|
||||
|
||||
def get_node_instances(self, node):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_info(self):
|
||||
raise NotImplementedError
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1,53 +0,0 @@
|
||||
# (c) Copyright 2016 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.
|
||||
|
||||
"""Abstract fencer"""
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class FencerBaseDriver(object, metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract class that all fencer plugins.
|
||||
|
||||
Should be implemented to have a unified interface and as many plugins as
|
||||
needed.
|
||||
"""
|
||||
|
||||
def __init__(self, nodes, fencer_conf):
|
||||
"""Initialize the driver.
|
||||
|
||||
Any fencer driver requires the following parameters to do the api
|
||||
calls. All these parameters can be passed from the configuration
|
||||
file in /etc/freezer/dr.conf (default).
|
||||
|
||||
:param nodes: A list of failed nodes to be fenced!
|
||||
:param fencer_conf: dict contains configuration options loaded
|
||||
from the config file.
|
||||
"""
|
||||
self.nodes = nodes
|
||||
self.fencer_conf = fencer_conf
|
||||
|
||||
@abc.abstractmethod
|
||||
def fence(self):
|
||||
"""This function to be implemented by each driver. Each driver will
|
||||
implement its own fencing logic and the manager will just load it and
|
||||
call the fence function"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_info(self):
|
||||
"""Get Driver information.
|
||||
|
||||
:return: dict of name, version, author, ...
|
||||
"""
|
||||
@@ -1,46 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class FencerManager(object):
|
||||
|
||||
def __init__(self, nodes):
|
||||
self.fencer_conf = CONF.get('fencer')
|
||||
self.nodes = nodes
|
||||
|
||||
def fence(self, nodes=None):
|
||||
"""
|
||||
Try to shutdown nodes and wait for configurable amount of times
|
||||
:return: list of nodes and either they are shutdown or failed
|
||||
"""
|
||||
# update the list of nodes if required!
|
||||
if nodes:
|
||||
self.nodes = nodes
|
||||
driver_name = self.fencer_conf['driver']
|
||||
driver = importutils.import_object(
|
||||
driver_name,
|
||||
self.nodes,
|
||||
self.fencer_conf
|
||||
)
|
||||
LOG.debug('Loaded fencing driver {0} with config: '
|
||||
'{1}'.format(driver.get_info(), self.fencer_conf))
|
||||
|
||||
return driver.fence()
|
||||
@@ -1,115 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from freezer_dr.common.yaml_parser import YamlParser
|
||||
from freezer_dr.fencers.common.driver import FencerBaseDriver
|
||||
from freezer_dr.fencers.drivers.ipmi.ipmitool import IpmiInterface
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import time
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class IpmiDriver(FencerBaseDriver):
|
||||
|
||||
def __init__(self, nodes, fencer_conf):
|
||||
|
||||
super(IpmiDriver, self).__init__(nodes, fencer_conf)
|
||||
self.parser = YamlParser(self.fencer_conf['credentials_file'])
|
||||
|
||||
def prepare_node(self, node):
|
||||
"""Prepares the subprocess to call ``ipmitool`` with the node details!
|
||||
:param node: dict contains node fencing information
|
||||
"""
|
||||
self.interface = IpmiInterface(
|
||||
node.get('fencer-ip'),
|
||||
node.get('fencer-user'),
|
||||
node.get('fencer-password'),
|
||||
verbose=CONF.debug)
|
||||
|
||||
def force_shutdown(self):
|
||||
try:
|
||||
self.interface.power_down()
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
def graceful_shutdown(self):
|
||||
try:
|
||||
self.interface.power_soft()
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
def status(self):
|
||||
return self.interface.get_power_status()
|
||||
|
||||
# @todo remove this fn as it's for testing purposes only :)
|
||||
def power_on(self):
|
||||
self.interface.power_on()
|
||||
|
||||
def get_node_details(self, node):
|
||||
"""Loads the node's fencing information from ``credentials_file``
|
||||
:param node: a dict contains node ip or hostname
|
||||
:return: a dict contains node fencing information
|
||||
"""
|
||||
node_details = self.parser.find_server_by_ip(node.get('ip')) or \
|
||||
self.parser.find_server_by_hostname(node.get('host'))
|
||||
|
||||
return node_details
|
||||
|
||||
def fence(self):
|
||||
"""Implements the fencing procedure for server fencing using ipmi
|
||||
:return: a list of nodes and weather they're fenced or not!
|
||||
"""
|
||||
fenced_nodes = []
|
||||
for node in self.nodes:
|
||||
LOG.debug("fencing node {0}".format(node))
|
||||
# load node details
|
||||
node_details = self.get_node_details(node)
|
||||
# loop on the node number of n times trying to fence it gently,
|
||||
# if not force it!
|
||||
self.prepare_node(node_details)
|
||||
for retry in range(0, self.fencer_conf['retries']):
|
||||
if self.status():
|
||||
try:
|
||||
self.graceful_shutdown()
|
||||
except Exception as e:
|
||||
LOG.debug(e)
|
||||
else:
|
||||
node['status'] = True
|
||||
break
|
||||
time.sleep(self.fencer_conf['hold_period'])
|
||||
LOG.info('wait for %d seconds before retrying to gracefully '
|
||||
'shutdown' % self.fencer_conf['hold_period'])
|
||||
|
||||
try:
|
||||
self.force_shutdown()
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
if not self.status():
|
||||
node['status'] = True
|
||||
else:
|
||||
node['status'] = False
|
||||
fenced_nodes.append(node)
|
||||
|
||||
return fenced_nodes
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
'name': 'IPMI Interface driver',
|
||||
'version': 1.1,
|
||||
'author': 'Hewlett-Packard Enterprise Company, L.P'
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from distutils import spawn
|
||||
from oslo_log import log
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class IpmiInterface:
|
||||
|
||||
_IPMI = 'ipmitool'
|
||||
_RAW_CMD = '{0} -I {1} -H {2} -U {3} -P {4} '
|
||||
_SUPPORTED_INTERFACES = ['lan', 'lanplus']
|
||||
|
||||
def __init__(self, host, username, password, verbose=False,
|
||||
interface='lanplus'):
|
||||
self._IPMI = spawn.find_executable('ipmitool')
|
||||
if not self._IPMI:
|
||||
self._IPMI = spawn.find_executable('ipmitool',
|
||||
path=':'.join(sys.path))
|
||||
if interface not in self._SUPPORTED_INTERFACES:
|
||||
raise Exception("Provided Interface is not supported")
|
||||
|
||||
self._host = host
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._verbose = verbose
|
||||
self._interface = interface
|
||||
|
||||
self._update_cmd_credentials(
|
||||
host=host,
|
||||
username=username,
|
||||
password=password,
|
||||
interface=interface
|
||||
)
|
||||
LOG.debug('IPMI Interface initialized')
|
||||
|
||||
def _update_cmd_credentials(self, host, username, password, interface):
|
||||
"""
|
||||
Update credentials to work with different server
|
||||
:param host: IPMI IP address of the server
|
||||
:param username: IPMI username
|
||||
:param password: IPMI password
|
||||
:param interface: IPMI Interface lan, lanplus
|
||||
"""
|
||||
cmd = self._RAW_CMD.format(
|
||||
self._IPMI,
|
||||
interface,
|
||||
host,
|
||||
username,
|
||||
password
|
||||
)
|
||||
self._cmd = cmd
|
||||
|
||||
def get_power_status(self):
|
||||
"""
|
||||
get the machine power status
|
||||
:return: 1 if the power is on and 0 if the power is off. otherwise it
|
||||
will return -1 for unknown state
|
||||
"""
|
||||
cmd = self._cmd + ' chassis power status'
|
||||
output = self._process_request(cmd)
|
||||
if self._verbose:
|
||||
LOG.debug(output)
|
||||
if 'is on'.lower() in output.lower():
|
||||
return 1
|
||||
elif 'is off'.lower() in output.lower():
|
||||
return 0
|
||||
return -1 # power status unknown
|
||||
|
||||
def power_down(self):
|
||||
"""
|
||||
Force shutdown the machine
|
||||
"""
|
||||
cmd = self._cmd + ' chassis power down'
|
||||
output = self._process_request(cmd)
|
||||
LOG.info('IPMI interface force shutdown node: %s, output: %s' %
|
||||
(self._host, output))
|
||||
return output
|
||||
|
||||
def power_soft(self):
|
||||
"""
|
||||
Softly shutdown the machine
|
||||
"""
|
||||
cmd = self._cmd + 'chassis power soft'
|
||||
output = self._process_request(cmd)
|
||||
LOG.info('IPMI interface soft shutdown node: %s, output: %s' %
|
||||
(self._host, output))
|
||||
return output
|
||||
|
||||
def power_reset(self):
|
||||
"""
|
||||
restart the machine
|
||||
"""
|
||||
cmd = self._cmd + ' chassis power reset'
|
||||
return self._process_request(cmd)
|
||||
|
||||
def power_on(self):
|
||||
"""
|
||||
power on the machine
|
||||
"""
|
||||
cmd = self._cmd + ' chassis power on'
|
||||
return self._process_request(cmd)
|
||||
|
||||
def _process_request(self, cmd):
|
||||
if self._verbose:
|
||||
LOG.debug('Executing IPMI command:', cmd)
|
||||
|
||||
process = subprocess.Popen(cmd, shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
output, error = process.communicate()
|
||||
|
||||
if self._verbose:
|
||||
LOG.debug('IPMI Output: ', output)
|
||||
LOG.debug('IPMI Error', error)
|
||||
|
||||
if process.returncode:
|
||||
LOG.error(cmd)
|
||||
raise Exception(error)
|
||||
return output
|
||||
|
||||
def _custom_cmd(self, cmd):
|
||||
"""
|
||||
execute custom ipmitool commands
|
||||
:param cmd: string contains the command, for credentials and interface
|
||||
you should _update_cmd_credentials to update them first
|
||||
:return: output of the command you sent or raise error
|
||||
"""
|
||||
cmd = self._cmd + cmd
|
||||
output = self._process_request(cmd)
|
||||
LOG.info('Executing IPMI custom command: %s with output: %s' %
|
||||
(cmd, output))
|
||||
return output
|
||||
@@ -1,100 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.common.yaml_parser import YamlParser
|
||||
from freezer_dr.fencers.common.driver import FencerBaseDriver
|
||||
import libvirt
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import time
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class LibvirtDriver(FencerBaseDriver):
|
||||
|
||||
def __init__(self, nodes, fencer_conf):
|
||||
super(LibvirtDriver, self).__init__(nodes, fencer_conf)
|
||||
self.parser = YamlParser(self.fencer_conf['credentials_file'])
|
||||
# initiate libvirt connection
|
||||
conn_name = self.fencer_conf.get('name', None)
|
||||
self.connection = libvirt.open(name=conn_name)
|
||||
|
||||
def force_shutdown(self, node):
|
||||
target = self.connection.lookupByName(name=node.get('domain-name'))
|
||||
return target.destroy()
|
||||
|
||||
def graceful_shutdown(self, node):
|
||||
target = self.connection.lookupByName(name=node.get('domain-name'))
|
||||
return target.shutdown()
|
||||
|
||||
def status(self, node):
|
||||
target = self.connection.lookupByName(name=node.get('domain-name'))
|
||||
return target.isActive()
|
||||
|
||||
def get_node_details(self, node):
|
||||
"""Loads the node's fencing information from ``credentials_file``
|
||||
:param node: a dict contains node ip or hostname
|
||||
:return: a dict contains node fencing information
|
||||
"""
|
||||
node_details = self.parser.find_server_by_ip(node.get('ip')) or \
|
||||
self.parser.find_server_by_hostname(node.get('host'))
|
||||
|
||||
return node_details
|
||||
|
||||
def fence(self):
|
||||
"""Implements the fencing procedure for server fencing using ipmi
|
||||
:return: a list of nodes and weather they're fenced or not!
|
||||
"""
|
||||
fenced_nodes = []
|
||||
for node in self.nodes:
|
||||
LOG.debug("fencing node {0}".format(node))
|
||||
# load node details
|
||||
node_details = self.get_node_details(node)
|
||||
# loop on the node number of n times trying to fence it gently,
|
||||
# if not force it!
|
||||
for retry in range(0, self.fencer_conf['retries']):
|
||||
if self.status(node=node_details):
|
||||
try:
|
||||
self.graceful_shutdown(node=node_details)
|
||||
except Exception as e:
|
||||
LOG.debug(e)
|
||||
else:
|
||||
node['status'] = True
|
||||
break
|
||||
time.sleep(self.fencer_conf['hold_period'])
|
||||
LOG.info('wait for %d seconds before retrying to gracefully '
|
||||
'shutdown' % self.fencer_conf['hold_period'])
|
||||
|
||||
try:
|
||||
self.force_shutdown(node=node_details)
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
if not self.status(node=node_details):
|
||||
node['status'] = True
|
||||
else:
|
||||
node['status'] = False
|
||||
fenced_nodes.append(node)
|
||||
|
||||
return fenced_nodes
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
'name': 'Libvirt Interface driver',
|
||||
'version': 1.1,
|
||||
'author': 'Hewlett-Packard Enterprise Company, L.P'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from freezer_dr.common import config
|
||||
from freezer_dr.evacuators.common.manager import EvacuationManager
|
||||
from freezer_dr.monitors.common.manager import MonitorManager
|
||||
from freezer_dr.notifiers.common.manager import NotificationManager
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
config.configure()
|
||||
config.setup_logging()
|
||||
LOG.info('Starting Freezer DR ... ')
|
||||
# initialize the notification driver as it will be used in many parts
|
||||
notifier = NotificationManager()
|
||||
# load and initialize the monitoring driver
|
||||
monitor = MonitorManager(notifier=notifier.get_driver())
|
||||
# Do the monitoring procedure
|
||||
# Monitor, analyse, nodes down ?, wait, double check ? evacuate ..
|
||||
nodes = monitor.monitor()
|
||||
|
||||
if nodes:
|
||||
# @todo put node in maintenance mode :) Not working with virtual
|
||||
# deployments
|
||||
# Load Fence driver
|
||||
# Shutdown the node
|
||||
evac = EvacuationManager()
|
||||
notify_nodes = evac.get_nodes_details(nodes)
|
||||
notifier.notify(notify_nodes, 'original')
|
||||
evacuated_nodes, failed_nodes = evac.evacuate(nodes)
|
||||
LOG.debug("Successfully evacuated nodes {0}".format(evacuated_nodes))
|
||||
LOG.debug("Failed to evacuate nodes {0}".format(failed_nodes))
|
||||
evacuated_nodes = evac.get_nodes_details(evacuated_nodes)
|
||||
notifier.notify(evacuated_nodes, 'success')
|
||||
failed_nodes = evac.get_nodes_details(failed_nodes)
|
||||
notifier.notify(failed_nodes, 'error')
|
||||
else:
|
||||
print("No nodes reported to be down")
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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 abc
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class MonitorBaseDriver(object, metaclass=abc.ABCMeta):
|
||||
"""
|
||||
Abstract class that all monitoring plugins should implement to have a
|
||||
unified interface and as many plugins as we want...
|
||||
"""
|
||||
_OPTS = []
|
||||
|
||||
def __init__(self, backend_name, notifier):
|
||||
"""
|
||||
Initializing the driver. Any monitoring system requires the following
|
||||
parameters to call it's api. All these parameters can be passed from the
|
||||
configuration file in /etc/freezer/dr.conf
|
||||
:param backend_name: Name of section in the configuration file that
|
||||
contains your driver initialization details; like username, password,
|
||||
endpoint and so on. Variables in this section depends on your driver
|
||||
|
||||
:param notifier: Notifier instance which can be used to notify the
|
||||
admins in case of error or problem happened during the DR process.
|
||||
You should only call notify method and send it your message to send
|
||||
it to the admins
|
||||
"""
|
||||
CONF.register_opts(self._OPTS, group=backend_name)
|
||||
self.conf = CONF.get(backend_name)
|
||||
self.notifier = notifier
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_data(self):
|
||||
"""
|
||||
Gathering metrics data. making the actual api call to
|
||||
the monitoring system and get a list of nodes status.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_metrics(self):
|
||||
"""
|
||||
return list of metrics used to monitor compute nodes. it's Optional
|
||||
not all drivers need to implement this method.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def analyze_nodes(self, nodes):
|
||||
"""
|
||||
Process nodes from get_data and return list of down nodes
|
||||
:param nodes: dict of metrics of nodes { 'metric1': nodes,
|
||||
'metric2': nodes}
|
||||
:return: a list of down nodes
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def process_failed(self, nodes=[], wait=0):
|
||||
"""
|
||||
Double check the failed nodes again to make sure that nodes are down.
|
||||
return a list of down nodes to be passed to the evacuation tool to
|
||||
process failed hosts.
|
||||
:param nodes: a list contains pre-checked nodes to re-check them again
|
||||
:param wait: a configurable a mount of time to wait before doing this
|
||||
check to give a chance for the host to recover if there was a minor
|
||||
issue.
|
||||
:return: a list of nodes to be evacuated, the list will be passed
|
||||
directly to the evacuation tool to process them
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_alive(self):
|
||||
"""
|
||||
Plugin should provide a way to make sure that the monitoring system is
|
||||
a live or not. It's optional not all drivers need to implement it.
|
||||
:return: True or False
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
Get Driver information ..
|
||||
:return: dict of name, version, author, ...
|
||||
"""
|
||||
@@ -1,65 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitorManager(object):
|
||||
|
||||
def __init__(self, notifier):
|
||||
monitor = CONF.get('monitoring')
|
||||
backend_name = monitor['backend_name']
|
||||
self.driver = importutils.import_object(
|
||||
monitor.driver,
|
||||
backend_name=backend_name,
|
||||
notifier=notifier
|
||||
)
|
||||
driver_info = self.driver.get_info()
|
||||
LOG.info('Initializing driver %s with version %s found in %s' %
|
||||
(driver_info['name'], driver_info['version'],
|
||||
monitor.get('driver')))
|
||||
|
||||
def monitor(self):
|
||||
# Check if the monitoring system is a live
|
||||
is_alive = self.driver.is_alive()
|
||||
# if not a live will record that in logs and will try to communicate !
|
||||
if not is_alive:
|
||||
LOG.error('Monitoring system is not a live or may be driver is '
|
||||
'missing implementation for is_alive method')
|
||||
|
||||
# getting data from the monitoring system
|
||||
# may be in future we add a hock function to external data processors !
|
||||
# @todo add external data processors to analyze the monitoring systems
|
||||
# data to separate monitoring from analysis
|
||||
data = self.driver.get_data()
|
||||
|
||||
# Asking the driver to analyze the data provided and provide list
|
||||
# of failed nodes
|
||||
nodes_down = self.driver.analyze_nodes(nodes=data)
|
||||
if not nodes_down:
|
||||
LOG.info('No nodes reported down')
|
||||
return 0 # for the time being we will exit with no error !
|
||||
|
||||
LOG.info('Nodes Down are: %s will be double checked again after %s '
|
||||
'seconds' % (str(nodes_down), CONF.wait))
|
||||
nodes_to_evacuate = self.driver.process_failed(nodes=nodes_down,
|
||||
wait=CONF.wait)
|
||||
return nodes_to_evacuate
|
||||
|
||||
def get_driver_info(self):
|
||||
return self.driver.get_info()
|
||||
@@ -1,158 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from freezer_dr.common.osclient import OSClient
|
||||
from freezer_dr.monitors.common.driver import MonitorBaseDriver
|
||||
|
||||
from http.client import HTTPConnection
|
||||
from http.client import HTTPSConnection
|
||||
from http.client import socket
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from time import sleep
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class StandardDriver(MonitorBaseDriver):
|
||||
_OPTS = [
|
||||
cfg.StrOpt('username',
|
||||
help='username to be used to initialize the default '
|
||||
'monitoring driver'),
|
||||
cfg.StrOpt('password',
|
||||
help='Password to be used for initializing the default '
|
||||
'monitoring driver'),
|
||||
cfg.StrOpt('endpoint',
|
||||
help='Monitoring system API endpoint'),
|
||||
cfg.DictOpt('kwargs',
|
||||
default={},
|
||||
help='List of kwargs if you want to pass it to initialize'
|
||||
' the monitoring driver. should be provided in'
|
||||
' key:value format'),
|
||||
]
|
||||
|
||||
def __init__(self, backend_name, notifier):
|
||||
super(StandardDriver, self).__init__(backend_name=backend_name,
|
||||
notifier=notifier)
|
||||
self.endpoint = self.conf.endpoint
|
||||
client = OSClient(
|
||||
authurl=self.conf.endpoint,
|
||||
username=self.conf.username,
|
||||
password=self.conf.password,
|
||||
**self.conf.kwargs
|
||||
)
|
||||
LOG.info("OSClient:: username: %s, password: %s, endpoint: %s, kwargs:"
|
||||
" %s" % (self.conf.username, '****', self.conf.endpoint,
|
||||
self.conf.kwargs)
|
||||
)
|
||||
self.client = client
|
||||
|
||||
def get_data(self):
|
||||
hypervisors = self.client.novahypervisors()
|
||||
computes = self.client.novacomputes()
|
||||
agents = self.client.neutronagents()
|
||||
data = {'hypervisors': hypervisors,
|
||||
'computes': computes,
|
||||
'agents': agents}
|
||||
return data
|
||||
|
||||
def process_failed(self, nodes=None, wait=0):
|
||||
if not wait:
|
||||
wait = CONF.wait
|
||||
if not nodes:
|
||||
return None
|
||||
sleep(wait)
|
||||
# @todo do the api call again to get the nodes status again
|
||||
data = self.get_data()
|
||||
nodes_down = self.analyze_nodes(nodes=data)
|
||||
# Thanks Eldar :) for sets
|
||||
nodes_down_hosts = set([dnode['host'] for dnode in nodes_down])
|
||||
return [node for node in nodes if node['host'] in nodes_down_hosts]
|
||||
|
||||
def get_metrics(self):
|
||||
return ['nova-compute', 'hypervisor', 'neutron-ovs-agent']
|
||||
|
||||
def analyze_nodes(self, nodes):
|
||||
# list all down nova compute
|
||||
nova_down = self.is_nova_service_down(nodes.get('computes'))
|
||||
# list all down hypervisors
|
||||
hypervisor_down = self.is_hpyervisor_down(nodes.get('hypervisors'))
|
||||
# list all down openvswitch agents
|
||||
agents_down = self.is_neutron_agents_down(nodes.get('agents'))
|
||||
|
||||
nodes_down = []
|
||||
for server in hypervisor_down:
|
||||
ip = server.get('ip')
|
||||
host = server.get('host')
|
||||
if host in nova_down and host in agents_down:
|
||||
node = {'ip': ip, 'host': host}
|
||||
nodes_down.append(node)
|
||||
|
||||
return nodes_down
|
||||
|
||||
def is_alive(self):
|
||||
url = urlparse(self.endpoint)
|
||||
if url.scheme == 'https':
|
||||
http_connector = HTTPSConnection
|
||||
else:
|
||||
http_connector = HTTPConnection
|
||||
try:
|
||||
connection = http_connector(host=url.netloc)
|
||||
connection.request('HEAD', url=url.path)
|
||||
response = connection.getresponse()
|
||||
except socket.error:
|
||||
return False
|
||||
try:
|
||||
if getattr(response, 'status') == 200:
|
||||
return True
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
'name': 'Freezer DR Native Driver',
|
||||
'version': 1.0,
|
||||
'author': 'Hewlett-Packard Development Company, L.P'
|
||||
}
|
||||
|
||||
def is_hpyervisor_down(self, hypervisors):
|
||||
down_hosts = []
|
||||
for hypervisor in hypervisors:
|
||||
if hypervisor.get('state') == 'down':
|
||||
host = {}
|
||||
host['host'] = hypervisor.get('service').get('host')
|
||||
host['ip'] = hypervisor.get('host_ip')
|
||||
down_hosts.append(host)
|
||||
|
||||
return down_hosts
|
||||
|
||||
def is_nova_service_down(self, computes):
|
||||
down_hosts = []
|
||||
for node in computes:
|
||||
if node.get('state') == 'down' and node.get('status') == 'enabled':
|
||||
down_hosts.append(node.get('host'))
|
||||
return down_hosts
|
||||
|
||||
def is_neutron_agents_down(self, agents):
|
||||
down_hosts = []
|
||||
for agent in agents:
|
||||
if agent.get('admin_state_up') and not agent.get('alive'):
|
||||
down_hosts.append(agent.get('host'))
|
||||
|
||||
return down_hosts
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from freezer_dr.monitors.common.driver import MonitorBaseDriver
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class DummyDriver(MonitorBaseDriver):
|
||||
"""A monitoring driver that returns a configured list of nodes as failed.
|
||||
|
||||
This can be useful for testing without actually shutting down the nodes.
|
||||
The nodes that should be reported as failing, can be configured in the
|
||||
monitoring section of the freezer_dr configuration file as follows:
|
||||
kwargs = nodes_down:hostname1;hostname2
|
||||
"""
|
||||
_OPTS = [
|
||||
cfg.ListOpt('nodes_down',
|
||||
default=[],
|
||||
required=True,
|
||||
help="fake list of failed compute nodes.")
|
||||
]
|
||||
|
||||
def __init__(self, backend_name, notifier):
|
||||
super(DummyDriver, self).__init__(backend_name=backend_name,
|
||||
notifier=notifier)
|
||||
|
||||
hostnames = self.conf.get('nodes_down', [])
|
||||
self.nodes_down = [{'host': n} for n in hostnames]
|
||||
|
||||
def get_data(self):
|
||||
return self.nodes_down
|
||||
|
||||
def get_metrics(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def process_failed(self, nodes=None, wait=0):
|
||||
return nodes
|
||||
|
||||
def analyze_nodes(self, nodes):
|
||||
return nodes
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
'name': 'Freezer DR Dummy Driver',
|
||||
'version': 1.0,
|
||||
'author': 'Hewlett-Packard Enterprise Development, L.P'
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
# (c) Copyright 2016 Hewlett-Packard Development Enterprise, 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 http.client
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
from monascaclient import client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from freezer_dr.common import utils
|
||||
from freezer_dr.monitors.common import driver
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class MonascaDriver(driver.MonitorBaseDriver):
|
||||
"""Monasca monitoring driver to monitor compute nodes. It makes use of
|
||||
Monasca to monitor the compute nodes. Metric information
|
||||
needed. 'hostname' must be used in dimensions to filter the
|
||||
values in alarms. You need to define alarms for all hosts with
|
||||
the required metrics.
|
||||
"""
|
||||
|
||||
_OPTS = [
|
||||
cfg.StrOpt('keystone_url',
|
||||
help="Keystone Url for authentication",
|
||||
required=True),
|
||||
cfg.StrOpt('username',
|
||||
help="cloud user used to record monasca alerts and alarms",
|
||||
required=True),
|
||||
cfg.StrOpt('password',
|
||||
help="Cloud user's password",
|
||||
required=True),
|
||||
cfg.StrOpt('project_name',
|
||||
help='Project/Tenant name. Default is admin',
|
||||
default='admin',
|
||||
required=True),
|
||||
cfg.BoolOpt('insecure',
|
||||
help='Use insecure connection.',
|
||||
default=False),
|
||||
cfg.StrOpt('project_domain_id',
|
||||
help="Project Domain Id. Default is default",
|
||||
default='default'),
|
||||
cfg.StrOpt('user_domain_id',
|
||||
help="User Domain Id. Default is default",
|
||||
default='default'),
|
||||
cfg.StrOpt('cacert',
|
||||
help='CA certificate. Default is None',
|
||||
default=None),
|
||||
cfg.StrOpt('monasca_url',
|
||||
help='Monasca endpoint URL. This is required to create a '
|
||||
'monasca client instance '
|
||||
),
|
||||
cfg.ListOpt('metrics',
|
||||
help='Monasca Metrics that needs to be checked. Each metric'
|
||||
' should be defined in a seperate section in the'
|
||||
' configuration file.',
|
||||
default=['host_alive_status'],
|
||||
required=True
|
||||
),
|
||||
cfg.StrOpt('aggregate',
|
||||
choices=['any', 'all'],
|
||||
default='all',
|
||||
help="If more than one metric used and they reported "
|
||||
"different states i.e.(a:failed, b:success) should we "
|
||||
"evacuate the compute host if only one metric failed "
|
||||
"(any) or only if all failed we evacuate (all). "
|
||||
"Default is all")
|
||||
]
|
||||
|
||||
def __init__(self, backend_name, notifier):
|
||||
super(MonascaDriver, self).__init__(backend_name=backend_name,
|
||||
notifier=notifier)
|
||||
self.monasca_client = client.Client(
|
||||
"2_0",
|
||||
self.conf['monasca_url'],
|
||||
auth_url=self.conf['keystone_url'],
|
||||
username=self.conf['username'],
|
||||
password=self.conf['password'],
|
||||
project_name=self.conf['project_name'],
|
||||
user_doamin_id=self.conf['user_domain_id'],
|
||||
project_doamin_id=self.conf['project_domain_id'],
|
||||
insecure=self.conf.get('insecure'),
|
||||
cacert=self.conf.get('cacert', None)
|
||||
)
|
||||
# Compute nodes might be disabled or set to maintenance mode so
|
||||
# freezer-dr needs to process only enabled nodes ...
|
||||
self.nodes = [node for node in self.get_compute_nodes()
|
||||
if node['status'] == "enabled"]
|
||||
# register metric options in their groups and load their values
|
||||
self.__load_metrics()
|
||||
|
||||
def _get_raw_data(self):
|
||||
""" This function returns the raw data we got from Monasca before
|
||||
processing and normalizing. You shouldn't call this function directly.
|
||||
:return: dict contains:
|
||||
{
|
||||
hostname1: {
|
||||
metric_name1: [{metric value 1}, {metric value 2}]
|
||||
metric_name2: [{metric value 1}, {metric value 2}]
|
||||
},
|
||||
hostname2: {
|
||||
metric_name1: [{metric value 1}, {metric value 2}]
|
||||
metric_name2: [{metric value 1}, {metric value 2}]
|
||||
}
|
||||
}
|
||||
"""
|
||||
data = {}
|
||||
for node in self.nodes:
|
||||
data[node['host']] = {}
|
||||
for metric in self.conf.metrics:
|
||||
data[node['host']][metric] = self.monasca_client.alarms.list(
|
||||
**self._build_metrics(
|
||||
metric=metric,
|
||||
hostname=node['host']
|
||||
)
|
||||
)
|
||||
return data
|
||||
|
||||
def get_data(self):
|
||||
"""This function returns monitoring data from Monasca. It calls
|
||||
_get_raw_data to get raw data and then process these data returns
|
||||
a normalized dict
|
||||
:return: dict contains::
|
||||
|
||||
{
|
||||
hostname1: {
|
||||
metric_name1: ['Ok', 'ALARM', 'UNDETERMINED']
|
||||
metric_name2: ['OK', 'OK', 'OK']
|
||||
},
|
||||
hostname2: {
|
||||
metric_name1: ['Ok', 'ALARM', 'OK']
|
||||
metric_name2: ['ALARM', 'UNDETERMINED', 'OK']
|
||||
}
|
||||
}
|
||||
"""
|
||||
data = self._get_raw_data()
|
||||
data2 = {}
|
||||
for host, metric_results in data.items():
|
||||
data2[host] = {}
|
||||
for metric_name, metric_values in metric_results.iteritems():
|
||||
data2[host][metric_name] = []
|
||||
for metric_value in metric_values:
|
||||
data2[host][metric_name].append(metric_value.get('state'))
|
||||
return data2
|
||||
|
||||
def process_failed(self, nodes=None, wait=1):
|
||||
time.sleep(wait)
|
||||
data = self.get_data()
|
||||
nodes_down = self.analyze_nodes(nodes=data)
|
||||
# Thanks Eldar :) for sets
|
||||
nodes_down_hosts = set([dnode['host'] for dnode in nodes_down])
|
||||
return [node for node in nodes if node['host'] in nodes_down_hosts]
|
||||
|
||||
def get_metrics(self):
|
||||
"""Lists all metrics
|
||||
:return: List of Metrics
|
||||
"""
|
||||
return self.conf['metrics']
|
||||
|
||||
def _build_metrics(self, metric, hostname=None):
|
||||
"""Build the query to send to Monasca"""
|
||||
metric = CONF[metric]
|
||||
dimensions = {'hostname': hostname}
|
||||
dimensions.update(metric.get('dimensions', {}))
|
||||
|
||||
fields = {
|
||||
'metric_dimensions': dimensions,
|
||||
'metric_name': metric['metric_name']
|
||||
}
|
||||
return fields
|
||||
|
||||
def analyze_nodes(self, nodes):
|
||||
"""It will check if the nodes are in 'OK' state or not. If not they
|
||||
will considered down. We have three states as follow:
|
||||
1. OK
|
||||
2. ALARM
|
||||
3. UNDEFINED
|
||||
"""
|
||||
# @todo(szaher) use list comprehension instead of loops
|
||||
# list below is correct and should return the extact same value like
|
||||
# the two nested for loops
|
||||
# nodes_down = [
|
||||
# {"host": hostname} for hostname, metrics in nodes.iteritems() if
|
||||
# [True for name, values in metrics.iteritems() if 'ALARM' in values]
|
||||
# ]
|
||||
nodes_data = []
|
||||
for node, metrics in nodes.iteritems():
|
||||
node_data = {node: []}
|
||||
for metric_name, metric_data in metrics.iteritems():
|
||||
node_data[node].append(
|
||||
self.__process_metric(node, metric_name, metric_data)
|
||||
)
|
||||
nodes_data.append(node_data)
|
||||
|
||||
aggregate = self.conf.get('aggregate', 'all')
|
||||
aggregate += '({0})'
|
||||
nodes_down = []
|
||||
for node_data in nodes_data:
|
||||
node_info = {}
|
||||
for node, data in node_data.iteritems():
|
||||
if not data:
|
||||
LOG.warning('No data available for node: {0}'.format(node))
|
||||
continue
|
||||
node_info[node] = eval(aggregate.format(data))
|
||||
if node_info:
|
||||
nodes_down.append(node_info)
|
||||
|
||||
if not nodes_down:
|
||||
return []
|
||||
return [
|
||||
{'host': host.keys()[0]} for host in nodes_down
|
||||
if True in host.values()
|
||||
]
|
||||
|
||||
def __process_metric(self, node, metric_name, metric_data):
|
||||
"""Process metric values got from Monasca.
|
||||
Handles UNDETERMINED states and changes it to required state(read
|
||||
from config file).
|
||||
If no metric data found,"""
|
||||
metric_conf = CONF[metric_name]
|
||||
# process UNDETERMINED State and change it to the required state
|
||||
metric_data = [
|
||||
i if i in ['OK', 'ALARM'] else
|
||||
metric_conf.get('undetermined', 'ALARM').upper()
|
||||
for i in metric_data
|
||||
]
|
||||
if not metric_data:
|
||||
message = """
|
||||
No data found for this metric: {0} <br />
|
||||
Data returned: {1} <br />
|
||||
hostname: {2} <br />
|
||||
Cause might be: <br />
|
||||
<ul>
|
||||
<li>Metric is not defined in Monasca </li>
|
||||
<li>Alarm with this metric name is not set for this host </li>
|
||||
<li>Check your Monasca configuration and Metric configuration
|
||||
defined in freezer-dr.conf </li>
|
||||
</ul>
|
||||
You can try this command to check: <br /><code>
|
||||
$ monasca alarm-list --metric-name {3} --metric-dimensions
|
||||
hostname={2}
|
||||
</code>
|
||||
<br /> <br />
|
||||
Freezer-DR
|
||||
""".format(metric_name, str(metric_data), node,
|
||||
metric_conf['metric_name'])
|
||||
self.notifier.notify(message)
|
||||
LOG.warning("No data found for metric: {0} on host: {1}".format(
|
||||
metric_name, node
|
||||
))
|
||||
exit(1)
|
||||
# build the decision
|
||||
aggregate = metric_conf.get('aggregate')
|
||||
aggregate += "(x=='ALARM' for x in metric_data)"
|
||||
return eval(aggregate)
|
||||
|
||||
def is_alive(self):
|
||||
url = urllib.parse.urlparse(self.conf.monasca_url)
|
||||
if url.scheme == 'https':
|
||||
http_connector = http.client.HTTPSConnection
|
||||
else:
|
||||
http_connector = http.client.HTTPConnection
|
||||
try:
|
||||
connection = http_connector(host=url.netloc)
|
||||
connection.request('HEAD', url=url.path)
|
||||
response = connection.getresponse()
|
||||
except http.client.socket.error:
|
||||
return False
|
||||
try:
|
||||
if getattr(response, 'status') in [200, 401]:
|
||||
return True
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
'name': 'Monasca Driver',
|
||||
'version': 1.0,
|
||||
'author': 'Hewlett-Packard Development Enterprise, L.P'
|
||||
}
|
||||
|
||||
def get_compute_nodes(self):
|
||||
"""Get a list of available compute hosts."""
|
||||
client = utils.get_os_client()
|
||||
return client.novacomputes()
|
||||
|
||||
def __load_metrics(self):
|
||||
"""load custom sections created by user"""
|
||||
for metric in self.conf.metrics:
|
||||
CONF.register_opts(self.__metric_opts, group=metric)
|
||||
|
||||
@property
|
||||
def __metric_opts(self):
|
||||
"""List of options to be used in metric defined sections"""
|
||||
return [
|
||||
cfg.StrOpt("metric_name",
|
||||
help="Metric Name used to log monitoring information"
|
||||
" in Monasca",
|
||||
required=True),
|
||||
cfg.DictOpt("dimensions",
|
||||
default={},
|
||||
help="Dict that contains dimensions information. "
|
||||
"component:nova-compute,service:compute",
|
||||
),
|
||||
cfg.StrOpt("aggregate",
|
||||
choices=["any", "all"],
|
||||
help="How to consider the compute node is down. If you "
|
||||
"metric reports many states, like checking "
|
||||
"different services on the compute host, should we"
|
||||
" consider if one component down all are down or"
|
||||
" only if all components are down. Default is all."
|
||||
" This means if all components fail, freezer-dr"
|
||||
" will consider the host failed",
|
||||
default='all'
|
||||
),
|
||||
cfg.StrOpt("undetermined",
|
||||
choices=['OK', 'ALARM'],
|
||||
default='ALARM',
|
||||
help="How to handle UNDETERMINED states. It can be "
|
||||
"ignored, will be considered OK state or can be "
|
||||
"considered ALARM. Default is ALARM")
|
||||
|
||||
]
|
||||
@@ -1,57 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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 abc
|
||||
|
||||
|
||||
class NotifierBaseDriver(object, metaclass=abc.ABCMeta):
|
||||
""" Used to notify admins/users at any stage that an error happened or
|
||||
process completed or something went wrong !
|
||||
"""
|
||||
|
||||
def __init__(self, url, username, password, templates_dir, notify_from,
|
||||
admin_list=None, **kwargs):
|
||||
""" Initialize the notification backend.
|
||||
:param url: Notification system backend
|
||||
:param username: Username
|
||||
:param password: Password
|
||||
:param templates_dir: Path to templates directory to load message
|
||||
templates
|
||||
:param kwargs: Key:Value arguments
|
||||
"""
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.templates_dir = templates_dir
|
||||
self.admin_list = admin_list
|
||||
self.notify_from = notify_from
|
||||
self.options = kwargs
|
||||
|
||||
@abc.abstractmethod
|
||||
def notify_status(self, node, status):
|
||||
""" Custom notification method. Can be used if you want to send custom
|
||||
notification about Tenant, Instance, or go deeper if you want
|
||||
:param node: Compute Host, Tenant, Instance, ...
|
||||
:param status: Error, Success, Info
|
||||
:return: True, False
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def notify(self, message):
|
||||
""" This method will be used in different places to notify admins
|
||||
about certain problem
|
||||
:param message: String message name
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
@@ -1,54 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class NotificationManager(object):
|
||||
|
||||
def __init__(self):
|
||||
notifier_conf = CONF.get('notifiers')
|
||||
self.driver = importutils.import_object(
|
||||
notifier_conf.get('driver'),
|
||||
notifier_conf.get('endpoint'),
|
||||
notifier_conf.get('username'),
|
||||
notifier_conf.get('password'),
|
||||
notifier_conf.get('templates-dir'),
|
||||
notifier_conf.get('notify-from'),
|
||||
notifier_conf.get('notify-list'),
|
||||
**notifier_conf.get('options')
|
||||
)
|
||||
|
||||
def notify(self, nodes, status):
|
||||
"""
|
||||
Send Notification to users added on tenants that has VMs running on the
|
||||
affected host.
|
||||
:param nodes: List of hosts that are affected, contains instances
|
||||
running on those hosts, tenants, users added on those tenants.
|
||||
:param status: success or error
|
||||
:return:
|
||||
"""
|
||||
for node in nodes:
|
||||
self.driver.notify_status(node, status)
|
||||
|
||||
def get_driver(self):
|
||||
return self.driver
|
||||
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from datetime import date
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from freezer_dr.common.utils import load_jinja_templates
|
||||
from freezer_dr.notifiers.common.driver import NotifierBaseDriver
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import smtplib
|
||||
import time
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class StandardEmail(NotifierBaseDriver):
|
||||
|
||||
def __init__(self, url, username, password, templates_dir, notify_from,
|
||||
admin_list=None, **kwargs):
|
||||
super(StandardEmail, self).__init__(url, username, password,
|
||||
templates_dir, notify_from,
|
||||
admin_list, **kwargs)
|
||||
LOG.info('Initializing StandardEmail driver @ {0}'.format(url))
|
||||
server = smtplib.SMTP(url, kwargs.get('port'))
|
||||
server.ehlo()
|
||||
if kwargs.get('tls'):
|
||||
LOG.info('TLS enabled !')
|
||||
server.starttls()
|
||||
if username and password:
|
||||
server.login(username, password)
|
||||
LOG.info('Logged in !')
|
||||
self.server = server
|
||||
|
||||
def notify_status(self, node, status):
|
||||
_template = 'info.jinja'
|
||||
if status == 'original':
|
||||
_template = 'original.jinja'
|
||||
if status == 'success':
|
||||
_template = 'user_success.jinja'
|
||||
elif status == 'error':
|
||||
_template = 'error.jinja'
|
||||
|
||||
for tenant in node.get('tenants'):
|
||||
for user in tenant.get('users'):
|
||||
if 'email' in user:
|
||||
subject = '[' + status + '] Evacuation Status'
|
||||
template_vars = {
|
||||
'name': user.get('name'),
|
||||
'tenant': tenant.get('id'),
|
||||
'instances': tenant.get('instances'),
|
||||
'evacuation_time': date.fromtimestamp(time.time())
|
||||
}
|
||||
message = load_jinja_templates(self.templates_dir,
|
||||
_template, template_vars)
|
||||
self.send_email(self.notify_from, user.get('email'),
|
||||
subject, html_msg=message)
|
||||
# notify administrators
|
||||
subject = 'Host Evacuation status'
|
||||
_template = 'success.jinja'
|
||||
template_vars = {
|
||||
'host': node.get('host'),
|
||||
'tenants': node.get('tenants'),
|
||||
'instances': node.get('instances'),
|
||||
'hypervisor': node.get('details'),
|
||||
'evacuation_time': date.fromtimestamp(time.time())
|
||||
}
|
||||
message = load_jinja_templates(self.templates_dir, _template,
|
||||
template_vars)
|
||||
self.send_email(self.notify_from, self.notify_from, subject,
|
||||
message, self.admin_list or None)
|
||||
|
||||
def send_email(self, mail_from, mail_to, subject, html_msg, cc_list=None,
|
||||
plain_msg=None):
|
||||
LOG.info('Sending email ....')
|
||||
message = MIMEMultipart()
|
||||
message['Subject'] = subject
|
||||
message['to'] = mail_to
|
||||
if cc_list:
|
||||
message['cc'] = ', '.join(cc_list)
|
||||
message['from'] = mail_from or self.notify_from
|
||||
msg = MIMEText(html_msg, 'html')
|
||||
message.attach(msg)
|
||||
if plain_msg:
|
||||
plain_msg = MIMEText(plain_msg, 'plain')
|
||||
message.attach(plain_msg)
|
||||
|
||||
try:
|
||||
self.server.sendmail(mail_from, mail_to,
|
||||
message.as_string())
|
||||
LOG.info('Email sent successfully !')
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
||||
def notify(self, message):
|
||||
try:
|
||||
self.send_email(
|
||||
mail_from=self.notify_from,
|
||||
mail_to=self.notify_from,
|
||||
subject="[Freezer-DR] Problem Occurred",
|
||||
html_msg=message,
|
||||
cc_list=self.admin_list or []
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.server.quit()
|
||||
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
#
|
||||
# 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 datetime import date
|
||||
from freezer_dr.notifiers.common.driver import NotifierBaseDriver
|
||||
import json
|
||||
from oslo_log import log
|
||||
import requests
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
class SlackNotifier(NotifierBaseDriver):
|
||||
|
||||
MAX_CACHE_SIZE = 100
|
||||
RESPONSE_OK = 'ok'
|
||||
|
||||
_raw_data_url_caches = []
|
||||
|
||||
def __init__(self, url, username, password, templates_dir, notify_from,
|
||||
admin_list=None, **kwargs):
|
||||
super(SlackNotifier, self).__init__(url, username, password,
|
||||
templates_dir, notify_from,
|
||||
admin_list, **kwargs)
|
||||
LOG.info('Initializing SlackNotifier driver @ {0}'.format(url))
|
||||
|
||||
self.slack_timeout = kwargs.get('slack_timeout', '')
|
||||
self.slack_ca_certs = kwargs.get('slack_ca_certs', '')
|
||||
self.slack_insecured = kwargs.get('slack_insecured', 'True')
|
||||
self.slack_proxy = kwargs.get('slack_proxy', '')
|
||||
|
||||
def _build_slack_message(self, node, status):
|
||||
"""Builds slack message body
|
||||
"""
|
||||
body = {
|
||||
'title': 'Host Evacuation status',
|
||||
'host': node.get('host'),
|
||||
'tenants': node.get('tenants'),
|
||||
'instances': node.get('instances'),
|
||||
'hypervisor': node.get('details'),
|
||||
'evacuation_time': date.fromtimestamp(time.time()),
|
||||
'status': status
|
||||
}
|
||||
slack_request = {}
|
||||
slack_request['text'] = json.dumps(body, indent=3)
|
||||
|
||||
return slack_request
|
||||
|
||||
def _check_response(self, result):
|
||||
if 'application/json' in result.headers.get('Content-Type'):
|
||||
response = result.json()
|
||||
if response.get(self.RESPONSE_OK):
|
||||
return True
|
||||
else:
|
||||
LOG.error('Received an error message when trying to send to slack. error={}'
|
||||
.format(response.get('error')))
|
||||
return False
|
||||
elif self.RESPONSE_OK == result.text:
|
||||
return True
|
||||
else:
|
||||
LOG.error('Received an error message when trying to send to slack. error={}'
|
||||
.format(result.text))
|
||||
return False
|
||||
|
||||
def _send_message(self, request_options):
|
||||
try:
|
||||
url = request_options.get('url')
|
||||
result = requests.post(**request_options)
|
||||
if result.status_code not in range(200, 300):
|
||||
LOG.error('Received an HTTP code {} when trying to post on URL {}.'
|
||||
.format(result.status_code, url))
|
||||
return False
|
||||
|
||||
# Slack returns 200 ok even if the token is invalid. Response has valid error message
|
||||
if self._check_response(result):
|
||||
LOG.info('Notification successfully posted.')
|
||||
return True
|
||||
|
||||
LOG.error('Failed to send to slack on URL {}.'.format(url))
|
||||
return False
|
||||
except Exception as err:
|
||||
LOG.error('Error trying to send to slack on URL {}. Detail: {}'
|
||||
.format(url, err))
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def notify_status(self, node, status):
|
||||
"""Notify the Host Evacuation status via slack
|
||||
Posts on the given url
|
||||
"""
|
||||
|
||||
slack_message = self._build_slack_message(node, status)
|
||||
address = self.url
|
||||
address = address.replace('#', '%23')
|
||||
|
||||
parsed_url = urllib.parse.urlsplit(address)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
url = urllib.parse.urljoin(address, urllib.parse.urlparse(address).path)
|
||||
|
||||
verify = self.slack_ca_certs or not self.slack_insecured
|
||||
|
||||
proxy = self.slack_proxy
|
||||
proxy_dict = None
|
||||
if proxy is not None:
|
||||
proxy_dict = {'https': proxy}
|
||||
|
||||
data_format_list = ['json', 'data']
|
||||
if url in SlackNotifier._raw_data_url_caches:
|
||||
data_format_list = ['data']
|
||||
|
||||
for data_format in data_format_list:
|
||||
LOG.info('Trying to send message to {} as {}'
|
||||
.format(url, data_format))
|
||||
request_options = {
|
||||
'url': url,
|
||||
'verify': verify,
|
||||
'params': query_params,
|
||||
'proxies': proxy_dict,
|
||||
'timeout': self.slack_timeout,
|
||||
data_format: slack_message
|
||||
}
|
||||
if self._send_message(request_options):
|
||||
if (data_format == 'data' and
|
||||
url not in SlackNotifier._raw_data_url_caches and
|
||||
len(SlackNotifier._raw_data_url_caches) < self.MAX_CACHE_SIZE):
|
||||
SlackNotifier._raw_data_url_caches.append(url)
|
||||
return True
|
||||
|
||||
LOG.info('Failed to send message to {} as {}'
|
||||
.format(url, data_format))
|
||||
return False
|
||||
|
||||
def notify(self, message):
|
||||
pass
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Python 2.7 support has been dropped. Last release of freezer-dr
|
||||
to support py2.7 is OpenStack Train. The minimum version of Python now
|
||||
supported by freezer-dr is Python 3.6.
|
||||
@@ -1,11 +0,0 @@
|
||||
pbr>=2.0.0 # Apache-2.0
|
||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||
python-monascaclient>=1.1.0 # Apache-2.0
|
||||
python-neutronclient>=6.7.0 # Apache-2.0
|
||||
python-novaclient>=9.1.0 # Apache-2.0
|
||||
PyYAML>=3.12.0 # MIT
|
||||
oslo.config>=5.2.0 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
libvirt-python>=1.2.5 # LGPLv2+
|
||||
Jinja2>=2.10 # BSD License (3 clause)
|
||||
46
setup.cfg
46
setup.cfg
@@ -1,46 +0,0 @@
|
||||
[metadata]
|
||||
name = freezer-dr
|
||||
summary = OpenStack Disaster Recovery
|
||||
description-file =
|
||||
README.rst
|
||||
author = Freezer Team
|
||||
author-email = openstack-discuss@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/freezer/latest/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Development Status :: 5 - Production/Stable
|
||||
Natural Language :: English
|
||||
Operating System :: POSIX :: Linux
|
||||
Topic :: System :: Recovery Tools
|
||||
|
||||
keywords =
|
||||
openstack
|
||||
freezer
|
||||
disaster
|
||||
recovery
|
||||
evacuation
|
||||
high availability
|
||||
dr
|
||||
|
||||
[files]
|
||||
packages =
|
||||
freezer_dr
|
||||
|
||||
[entry_points]
|
||||
oslo.config.opts =
|
||||
freezer-dr = freezer_dr.common.config:list_opts
|
||||
console_scripts =
|
||||
freezer-dr = freezer_dr.main:main
|
||||
20
setup.py
20
setup.py
@@ -1,20 +0,0 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
||||
@@ -1,14 +0,0 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
# hacking pins flake8 etc
|
||||
hacking>=3.0.1,<3.1.0 # Apache-2.0
|
||||
coverage>=4.0
|
||||
mock>=2.0.0
|
||||
pylint==2.4.0 # GPLv2
|
||||
stestr>=2.0.0 # Apache-2.0
|
||||
testtools>=2.2.0
|
||||
sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.31.2 # Apache-2.0
|
||||
sphinxcontrib-apidoc>=0.2.0
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1,39 +0,0 @@
|
||||
# (c) Copyright 2014,2015 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 unittest
|
||||
from mock import patch
|
||||
|
||||
from freezer_dr.common import utils
|
||||
from freezer_dr.common.osclient import OSClient
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
|
||||
@patch('freezer_dr.common.utils.CONF')
|
||||
def test_os_credentials(self, mock_CONF):
|
||||
keystone_conf = dict({
|
||||
'auth_url': '',
|
||||
'password': '',
|
||||
'project_name': '',
|
||||
'user_domain_id': '',
|
||||
'project_domain_id': '',
|
||||
'project_domain_id': '',
|
||||
'user_domain_name': '',
|
||||
'kwargs': ''
|
||||
})
|
||||
mock_CONF = {'keystone_authtoken': keystone_conf}
|
||||
cred = utils.get_os_client()
|
||||
osclient_object = isinstance(cred, OSClient)
|
||||
self.assertEqual(osclient_object, True, '')
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
@@ -1 +0,0 @@
|
||||
__author__ = 'saad'
|
||||
66
tox.ini
66
tox.ini
@@ -1,66 +0,0 @@
|
||||
[tox]
|
||||
minversion = 2.0
|
||||
envlist =py39,py38, pep8,pylint,docs
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
basepython = python3
|
||||
usedevelop = True
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
passenv =
|
||||
http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
OS_TEST_PATH = ./tests/unit
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
stestr run {posargs}
|
||||
rm -f .coverage
|
||||
rm -rf .testrepository
|
||||
|
||||
whitelist_externals =
|
||||
find
|
||||
coverage
|
||||
rm
|
||||
python_files = test_*.py
|
||||
norecursedirs = .tox .venv
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands =
|
||||
sphinx-build -W -b html doc/source doc/build/html
|
||||
|
||||
[testenv:py39]
|
||||
basepython = python3.9
|
||||
|
||||
[testenv:py38]
|
||||
basepython = python3.8
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8 freezer_dr
|
||||
|
||||
[testenv:pylint]
|
||||
commands = pylint --rcfile .pylintrc freezer_dr
|
||||
|
||||
[testenv:genconfig]
|
||||
sitepackages = False
|
||||
envdir = {toxworkdir}/venv
|
||||
commands =
|
||||
oslo-config-generator --config-file=config-generator/freezer-dr.conf
|
||||
|
||||
[flake8]
|
||||
# it's not a bug that we aren't using all of hacking
|
||||
# H102 -> apache2 license exists
|
||||
# H103 -> license is apache
|
||||
# H201 -> no bare excepts
|
||||
# H501 -> don't use locals() for str formatting
|
||||
# H903 -> \n not \r\n
|
||||
ignore = H
|
||||
select = H102, H103, H201, H501, H903, H201, H306, H301, H233
|
||||
show-source = True
|
||||
exclude = .venv,.tox,dist,doc,test,*egg,tests
|
||||
Reference in New Issue
Block a user