0139f12c2e
If a load balancer loses more than one amphora at the same time the failover process will fail and leave the load balancer in provisioning status ERROR. This patch resolves this by failing over one amphora at a time marking any amphora that are also failed in status ERROR. The health manager will then failover the other failed amphora in subsequent checks. This patch will update multiple healthy amphora in parallel and will timeout failed amphroa using the new "active_connection_max_retries" configuration setting used for "fail-fast" connections. The patch also updates the amphora failover flow documentation to show the full flow and not just the spares failover flow. It updates the amphora driver "get_diagnostics" method to pass instead of error. It also adds a AmphoraComputeConnectivityWait task to explicitly wait for a compute instance to come up and be reachable. This allows a longer timeout and clarifies this may fail due to compute (nova) failures. Previously the first plug vip task would do this wait. Change-Id: Ief97ddda8261b5bbc54c6824f90ae9c7a2d81701 Story: 2001481 Task: 6202
151 lines
6.6 KiB
Python
Executable File
151 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2016 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.
|
|
#
|
|
|
|
import argparse
|
|
import importlib
|
|
import os
|
|
|
|
import graphviz
|
|
from taskflow import engines
|
|
|
|
from octavia.common import constants
|
|
from octavia.tests.common import data_model_helpers as dmh
|
|
|
|
|
|
def main():
|
|
arg_parser = argparse.ArgumentParser(
|
|
description='Generate graphviz representations of the '
|
|
'Octavia TaskFlow flows.')
|
|
arg_parser.add_argument('-f', '--flow-list', required=True,
|
|
help='Path to flow list file')
|
|
arg_parser.add_argument('-o', '--output-directory', required=True,
|
|
help='Path to flow list file')
|
|
args = arg_parser.parse_args()
|
|
generate(args.flow_list, args.output_directory)
|
|
|
|
|
|
def generate(flow_list, output_directory):
|
|
# Create the diagrams
|
|
base_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
os.path.pardir)
|
|
diagram_list = []
|
|
with open(os.path.join(base_path, flow_list), 'r') as flowlist:
|
|
for row in flowlist:
|
|
if row.startswith('#'):
|
|
continue
|
|
current_tuple = tuple(row.strip().split(' '))
|
|
current_class = getattr(importlib.import_module(current_tuple[0]),
|
|
current_tuple[1])
|
|
current_instance = current_class()
|
|
get_flow_method = getattr(current_instance, current_tuple[2])
|
|
if (current_tuple[1] == 'AmphoraFlows' and
|
|
current_tuple[2] == 'get_failover_flow'):
|
|
amp1 = dmh.generate_amphora()
|
|
amp2 = dmh.generate_amphora()
|
|
lb = dmh.generate_load_balancer(amphorae=[amp1, amp2])
|
|
current_engine = engines.load(
|
|
get_flow_method(role=constants.ROLE_STANDALONE,
|
|
load_balancer=lb))
|
|
elif (current_tuple[1] == 'LoadBalancerFlows' and
|
|
current_tuple[2] == 'get_create_load_balancer_flow'):
|
|
current_engine = engines.load(
|
|
get_flow_method(
|
|
constants.TOPOLOGY_ACTIVE_STANDBY))
|
|
elif (current_tuple[1] == 'LoadBalancerFlows' and
|
|
current_tuple[2] == 'get_delete_load_balancer_flow'):
|
|
lb = dmh.generate_load_balancer()
|
|
delete_flow, store = get_flow_method(lb)
|
|
current_engine = engines.load(delete_flow)
|
|
elif (current_tuple[1] == 'LoadBalancerFlows' and
|
|
current_tuple[2] == 'get_cascade_delete_load_balancer_flow'):
|
|
lb = dmh.generate_load_balancer()
|
|
delete_flow, store = get_flow_method(lb)
|
|
current_engine = engines.load(delete_flow)
|
|
elif (current_tuple[1] == 'MemberFlows' and
|
|
current_tuple[2] == 'get_batch_update_members_flow'):
|
|
current_engine = engines.load(
|
|
get_flow_method([], [], []))
|
|
else:
|
|
current_engine = engines.load(get_flow_method())
|
|
current_engine.compile()
|
|
# We need to render svg and not dot here so we can scale
|
|
# the image in the restructured text page
|
|
src = graphviz.Source(current_engine.compilation.
|
|
execution_graph.export_to_dot())
|
|
src.format = 'svg'
|
|
src.render(filename=current_tuple[1] + '-' + current_tuple[2],
|
|
directory=os.path.join(base_path, output_directory),
|
|
cleanup=True)
|
|
diagram_list.append((current_tuple[1], current_tuple[2]))
|
|
|
|
# Create the class docs
|
|
diagram_list = sorted(diagram_list, key=getDiagKey)
|
|
class_tracker = None
|
|
current_doc_file = None
|
|
for doc_tuple in diagram_list:
|
|
# If we are still working on the same class, append
|
|
if doc_tuple[0] == class_tracker:
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write(doc_tuple[1] + '\n')
|
|
current_doc_file.write('-' * len(doc_tuple[1]) + '\n')
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write('.. image:: ' + doc_tuple[0] +
|
|
'-' + doc_tuple[1] + '.svg\n')
|
|
current_doc_file.write(' :width: 660px\n')
|
|
current_doc_file.write(' :target: ../../../_images/' +
|
|
doc_tuple[0] +
|
|
'-' + doc_tuple[1] + '.svg\n')
|
|
|
|
# First or new class, create the file
|
|
else:
|
|
if current_doc_file is not None:
|
|
current_doc_file.close()
|
|
current_doc_file = open(os.path.join(
|
|
base_path, output_directory, doc_tuple[0] + '.rst'), 'w+')
|
|
class_tracker = doc_tuple[0]
|
|
|
|
file_title = constants.FLOW_DOC_TITLES.get(doc_tuple[0],
|
|
'Unknown Flows')
|
|
|
|
current_doc_file.write('=' * len(file_title) + '\n')
|
|
current_doc_file.write(file_title + '\n')
|
|
current_doc_file.write('=' * len(file_title) + '\n')
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write('.. contents::\n')
|
|
current_doc_file.write(' :depth: 2\n')
|
|
current_doc_file.write(' :backlinks: top\n')
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write('Click on any flow to view full size.\n')
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write(doc_tuple[1] + '\n')
|
|
current_doc_file.write('-' * len(doc_tuple[1]) + '\n')
|
|
current_doc_file.write('\n')
|
|
current_doc_file.write('.. image:: ' + doc_tuple[0] +
|
|
'-' + doc_tuple[1] + '.svg\n')
|
|
current_doc_file.write(' :width: 660px\n')
|
|
current_doc_file.write(' :target: ../../../_images/' +
|
|
doc_tuple[0] +
|
|
'-' + doc_tuple[1] + '.svg\n')
|
|
|
|
current_doc_file.close()
|
|
|
|
|
|
def getDiagKey(item):
|
|
return item[0] + '-' + item[1]
|
|
|
|
if __name__ == "__main__":
|
|
main()
|