Remove stack manager class.

StackManager class is obsolete and it has been replaced
from tobiko.openstack.heat._stack.HeatStackFixture class.

Change-Id: Id2efa0372a4eae942769b7913173b99488bc7359
This commit is contained in:
Federico Ressi 2019-04-12 09:42:34 +02:00
parent 2e4030d8ad
commit c261ef1ae5
8 changed files with 12 additions and 337 deletions

View File

@ -20,7 +20,6 @@ import argparse
from oslo_log import log
from tobiko.common.managers import stack
from tobiko.common.managers import ansible
from tobiko import config
@ -38,11 +37,8 @@ class TobikoCMD(object):
self.args = (self.parser).parse_args()
curr_dir = os.path.dirname(__file__)
self.templates_dir = os.path.join(curr_dir,
"../tests/scenario/templates")
self.playbooks_dir = os.path.join(curr_dir,
"../tests/scenario/playbooks")
self.stackManager = stack.StackManager(self.templates_dir)
self.ansibleManager = ansible.AnsibleManager(self.playbooks_dir)
def get_parser(self):
@ -61,7 +57,3 @@ class TobikoCMD(object):
for handler in root_logger.handlers:
if isinstance(handler, logging.StreamHandler):
handler.setLevel(level)
self.stackManager = stack.StackManager(
templates_dir=self.templates_dir)
self.ansibleManager = ansible.AnsibleManager(
playbooks_dir=self.playbooks_dir)

View File

@ -13,14 +13,12 @@
# under the License.
from __future__ import absolute_import
import os
import sys
from oslo_log import log
import tobiko
from tobiko.cmd import base
from tobiko.common import constants
LOG = log.getLogger(__name__)
@ -29,37 +27,12 @@ class CreateUtil(base.TobikoCMD):
def get_parser(self):
parser = super(CreateUtil, self).get_parser()
parser.add_argument(
'--stack', '-s',
help="The name of the stack to create.\n"
"This is based on the template name in templates dir")
parser.add_argument(
'--playbook', '-p',
help="The name of the playbook to execute.\n"
"This is based on the playbook name in playbooks dir")
parser.add_argument(
'--all', '-a', action='store_true', dest='all',
help="Create all the stacks defined in Tobiko.")
parser.add_argument(
'--wait', '-w', action='store_true', dest='wait',
help="Wait for stack to reach CREATE_COMPLETE status before "
"exiting.")
return parser
def create_stacks(self, stack_name=None, all_stacks=False, wait=False):
"""Creates a stack based on given arguments."""
if all_stacks or stack_name is None:
templates = self.stackManager.get_templates_names()
else:
templates = [stack_name + constants.TEMPLATE_SUFFIX]
for template in templates:
stack_name = os.path.splitext(template)[0]
self.stackManager.create_stack(
stack_name=stack_name,
template_name=template,
parameters=constants.DEFAULT_PARAMS,
wait=wait)
def run_playbook(self, playbook):
"""Executes given playbook."""
self.ansibleManager.run_playbook(playbook, mode='create')
@ -75,10 +48,6 @@ def main():
create_cmd.set_stream_handler_logging_level()
if create_cmd.args.playbook:
create_cmd.run_playbook(create_cmd.args.playbook)
else:
create_cmd.create_stacks(stack_name=create_cmd.args.stack,
all_stacks=create_cmd.args.all,
wait=create_cmd.args.wait)
if __name__ == '__main__':

View File

@ -25,31 +25,11 @@ class DeleteUtil(base.TobikoCMD):
def get_parser(self):
parser = super(DeleteUtil, self).get_parser()
parser.add_argument(
'--stack', '-s',
help="The name of the stack to remove.")
parser.add_argument(
'--all', '-a', action='store_true', dest='all',
help="Remove all the stacks created by Tobiko.")
parser.add_argument(
'--wait', '-w', action='store_true', dest='wait',
help="Wait for stack to be deleted before exiting.")
parser.add_argument(
'--playbook', '-p',
help="The name of the playbook to execute in delete mode.")
return parser
def delete_stack(self, stack_name=None, all_stacks=False, wait=False):
"""Deletes a stack based on given arguments."""
if all_stacks or stack_name is None:
stacks = self.stackManager.get_stacks_match_templates()
for stack in stacks:
self.stackManager.delete_stack(stack, wait=wait)
LOG.info("Deleted stack: %s", stack)
else:
self.stackManager.delete_stack(stack_name, wait=wait)
LOG.info("Deleted stack: %s", stack_name)
def run_playbook(self, playbook):
"""Executes given playbook."""
self.ansibleManager.run_playbook(playbook, mode='delete')
@ -59,12 +39,7 @@ def main():
"""Delete CLI main entry."""
delete_cmd = DeleteUtil()
delete_cmd.set_stream_handler_logging_level()
if delete_cmd.args.playbook:
delete_cmd.run_playbook(delete_cmd.args.playbook)
else:
delete_cmd.delete_stack(stack_name=delete_cmd.args.stack,
all_stacks=delete_cmd.args.all,
wait=delete_cmd.args.wait)
delete_cmd.run_playbook(delete_cmd.args.playbook)
if __name__ == '__main__':

View File

@ -31,30 +31,12 @@ class ListUtil(base.TobikoCMD):
def get_parser(self):
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument('--stacks', '-s',
help="List stacks (created by Tobiko)",
const='list_stacks',
action='store_const', dest='action')
parser.add_argument('--templates', '-t',
help="List templates provided by Tobiko",
const='list_templates',
action='store_const', dest='action')
parser.add_argument('--playbooks', '-p',
help="List playbooks provided by Tobiko",
const='list_playbooks',
action='store_const', dest='action')
return parser
def list_stacks(self):
"""Lists stacks created by Tobiko."""
for stack in self.stackManager.get_stacks_match_templates():
sys.stdout.write(stack + '\n')
def list_templates(self):
"""Lists templates included in Tobiko."""
for template in self.stackManager.get_templates_names():
sys.stdout.write(template + '\n')
def list_playbooks(self):
"""Lists playbooks included in Tobiko."""
for playbook in self.ansibleManager.get_playbooks_names():
@ -68,7 +50,7 @@ def main():
action_func = getattr(list_cmd, list_cmd.args.action)
action_func()
else:
list_cmd.list_templates()
list_cmd.list_playbooks()
if __name__ == '__main__':

View File

@ -1,35 +0,0 @@
# Copyright 2018 Red Hat
#
# 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 absolute_import
from tobiko.openstack import neutron
class NetworkManager(object):
"""Manages Neutron Resources."""
_client = None
@property
def client(self):
if not self._client:
self._client = neutron.get_neutron_client()
return self._client
def create_sg_rules(self, rules, sg_id):
"""Creates security group rules."""
for rule in rules:
rule['security_group_id'] = sg_id
body = {'security_group_rule': rule}
self.client.create_security_group_rule(body)

View File

@ -1,207 +0,0 @@
# Copyright 2018 Red Hat
#
# 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 absolute_import
import os
import time
from heatclient.common import template_utils
from heatclient import exc
from oslo_log import log
import yaml
import tobiko
from tobiko.common import constants
from tobiko.openstack import heat
LOG = log.getLogger(__name__)
# Status
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
CREATE_COMPLETE = 'CREATE_COMPLETE'
CREATE_FAILED = 'CREATE_FAILED'
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
DELETE_COMPLETE = 'DELETE_COMPLETE'
DELETE_FAILED = 'DELETE_FAILED'
class StackManager(object):
"""Manages Heat stacks."""
def __init__(self, templates_dir, wait_interval=5):
self.templates_dir = templates_dir
self.wait_interval = wait_interval
_client = None
@property
def client(self):
if not self._client:
self._client = heat.get_heat_client()
return self._client
def load_template(self, template_path):
"""Loads template from a given file."""
_, template = template_utils.get_template_contents(template_path)
return yaml.safe_dump(template)
def create_stack(self, stack_name, template_name, parameters, wait=True):
"""Creates stack based on passed parameters."""
stack = self.wait_for_stack_status(
stack_name=stack_name, expected_status={DELETE_COMPLETE,
CREATE_COMPLETE,
CREATE_FAILED})
if stack and stack.stack_status == CREATE_COMPLETE:
LOG.debug('Stack %r already exists.', stack_name)
return stack
if stack and stack.stack_status.endswith('_FAILED'):
self.delete_stack(stack_name, wait=True)
template = self.load_template(os.path.join(self.templates_dir,
template_name))
try:
self.client.stacks.create(stack_name=stack_name,
template=template,
parameters=parameters)
except exc.HTTPConflict:
LOG.debug('Stack %r already exists.', stack_name)
else:
LOG.debug('Crating stack %r...', stack_name)
if wait:
return self.wait_for_stack_status(stack_name=stack_name)
else:
return self.get_stack(stack_name=stack_name)
def delete_stack(self, stack_name, wait=False):
"""Deletes stack."""
self.client.stacks.delete(stack_name)
if wait:
self.wait_for_stack_status(stack_name,
expected_status={DELETE_COMPLETE})
def get_stack(self, stack_name, resolve_outputs=False):
"""Returns stack ID."""
try:
return self.client.stacks.get(stack_name,
resolve_outputs=resolve_outputs)
except exc.HTTPNotFound:
return None
def wait_for_resource_status(self, stack_id, resource_name,
status=CREATE_COMPLETE):
"""Waits for resource to reach the given status."""
res = self.client.resources.get(stack_id, resource_name)
while (res.resource_status != status):
time.sleep(self.wait_interval)
res = self.client.resources.get(stack_id, resource_name)
def wait_for_stack_status(self, stack_name=None, stack=None,
expected_status=None, check=True):
"""Waits for the stack to reach the given status."""
expected_status = expected_status or {CREATE_COMPLETE}
stack_name = stack_name or stack.stack_name
stack = stack or self.get_stack(stack_name=stack_name)
while (stack and stack.stack_status.endswith('_IN_PROGRESS') and
stack.stack_status not in expected_status):
LOG.debug("Waiting for %r stack status (observed=%r, expected=%r)",
stack_name, stack.stack_status, expected_status)
time.sleep(self.wait_interval)
stack = self.get_stack(stack_name=stack_name)
if check:
if stack is None:
if DELETE_COMPLETE not in expected_status:
raise StackNotFound(name=stack_name)
else:
check_stack_status(stack, expected_status)
return stack
def get_output(self, stack, key):
"""Returns a specific value from stack outputs by using a given key."""
check_stack_status(stack, {CREATE_COMPLETE})
if not hasattr(stack, 'outputs'):
stack = self.get_stack(stack_name=stack.stack_name,
resolve_outputs=True)
outputs = {output['output_key']: output['output_value']
for output in stack.outputs}
try:
return outputs[key]
except KeyError:
raise InvalidOutputKey(name=stack.stack_name,
key=key)
def get_templates_names(self, strip_suffix=False):
"""Returns a list of all the files in templates dir."""
templates = []
for (_, _, files) in os.walk(self.templates_dir):
templates.extend(files)
if strip_suffix:
templates = [
f[:-len(constants.TEMPLATE_SUFFIX)] for f in templates]
return templates
def get_stacks_match_templates(self):
"""Returns a list of existing stack names in the cloud project
which match the templates defined in the project source code."""
matched_stacks = []
code_stacks = self.get_templates_names(strip_suffix=True)
cloud_stacks = self.client.stacks.list()
for stack in cloud_stacks:
if stack.stack_name in code_stacks:
matched_stacks.append(stack.stack_name)
return matched_stacks
def check_stack_status(stack, expected):
observed = stack.stack_status
if observed not in expected:
if observed == CREATE_FAILED:
error_class = StackCreationFailed
elif observed == DELETE_FAILED:
error_class = StackDeletionFailed
else:
error_class = InvalidStackStatus
raise error_class(name=stack.stack_name,
observed=observed,
expected=expected,
reason=stack.stack_status_reason)
class InvalidOutputKey(tobiko.TobikoException):
msg = ("Output key %(key)r not found in stack %(name).")
class StackNotFound(tobiko.TobikoException):
msg = ("Stack %(name)r not found")
class InvalidStackStatus(tobiko.TobikoException):
msg = ("Stack %(name)r status %(observed)r not in %(expected)r "
"(reason=%(status_reason)r)")
class StackCreationFailed(InvalidStackStatus):
pass
class StackDeletionFailed(InvalidStackStatus):
pass

View File

@ -25,9 +25,7 @@ class TobikoCMDTest(test_base.OpenstackTest):
def test_init(self, argv=None):
self.patch_argv(argv=argv)
cmd = self.command_class()
self.assertIsNotNone(cmd.stackManager)
return cmd
return self.command_class()
def patch_argv(self, argv=None):
return self.patch('sys.argv', [self.command_name] + (argv or []))

View File

@ -25,7 +25,6 @@ import tobiko
from tobiko.openstack import heat
from tobiko.openstack import keystone
from tobiko.tests.openstack import base
from tobiko.common.managers import stack as _stack
class MyStack(heat.HeatStackFixture):
@ -309,11 +308,11 @@ class HeatStackFixtureTest(base.OpenstackTest):
client.stacks.delete.assert_called_once_with(stack.stack_name)
def test_get_outputs(self):
stack = mock.MagicMock(stack_status=_stack.CREATE_COMPLETE,
outputs=[{'output_key': 'key1',
'output_value': 'value1'},
{'output_key': 'key2',
'output_value': 'value2'}])
stack = mock_stack(status='CREATE_COMPLETE',
outputs=[{'output_key': 'key1',
'output_value': 'value1'},
{'output_key': 'key2',
'output_value': 'value2'}])
client = mock.MagicMock(specs=heatclient.Client)
client.stacks.get.return_value = stack
stack_fixture = MyStack(client=client)
@ -326,5 +325,7 @@ class HeatStackFixtureTest(base.OpenstackTest):
self.assertEqual('value2', outputs.key2)
def mock_stack(status, stack_id='<stack-id>'):
return mock.MagicMock(stack_status=status, id=stack_id)
def mock_stack(status, stack_id='<stack-id>', outputs=None):
return mock.MagicMock(stack_status=status,
id=stack_id,
outputs=outputs or [])