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:
Dmitriy Rabotyagov
2024-12-23 11:54:12 +01:00
parent ba060b277d
commit ba1fcd9b41
86 changed files with 10 additions and 4876 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -1,4 +0,0 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./tests/unit}
top_dir=./

View File

@@ -1,10 +0,0 @@
- project:
templates:
- openstack-python3-yoga-jobs
- publish-openstack-docs-pti
check:
jobs:
- openstack-tox-pylint
gate:
jobs:
- openstack-tox-pylint

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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.

View File

@@ -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.

View File

@@ -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]

View File

@@ -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.

View File

@@ -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>

View File

@@ -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',
]

View File

@@ -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>`_.

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()

View File

@@ -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()

View File

@@ -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().
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -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, ...
"""

View File

@@ -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()

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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'
}

View File

@@ -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")

View File

@@ -1 +0,0 @@

View File

@@ -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, ...
"""

View File

@@ -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()

View File

@@ -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

View File

@@ -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'
}

View File

@@ -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")
]

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -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, '')

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -1 +0,0 @@
__author__ = 'saad'

View File

@@ -1 +0,0 @@
__author__ = 'saad'

66
tox.ini
View File

@@ -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