X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=snaps%2Fopenstack%2Fcreate_stack.py;h=43ac307f1b8e984bcc2cca8955fa5767b35e3b67;hb=c5fd2310c8e9f63cf607de7fa9c21f60ab877224;hp=41cc7252c51addcf97d699ae47b7b385896f83b4;hpb=dc24c3d90a7a3068256381d815c689b3f243707f;p=snaps.git diff --git a/snaps/openstack/create_stack.py b/snaps/openstack/create_stack.py index 41cc725..43ac307 100644 --- a/snaps/openstack/create_stack.py +++ b/snaps/openstack/create_stack.py @@ -17,67 +17,115 @@ import logging import time from heatclient.exc import HTTPNotFound -from snaps.openstack.utils import heat_utils + +import snaps +from snaps.config.stack import StackConfig +from snaps.openstack.create_flavor import OpenStackFlavor +from snaps.openstack.create_instance import OpenStackVmInstance +from snaps.openstack.create_keypairs import OpenStackKeypair +from snaps.openstack.create_security_group import OpenStackSecurityGroup +from snaps.openstack.create_router import OpenStackRouter +from snaps.openstack.create_volume import OpenStackVolume +from snaps.openstack.create_volume_type import OpenStackVolumeType +from snaps.openstack.openstack_creator import OpenStackCloudObject +from snaps.openstack.utils import ( + nova_utils, settings_utils, glance_utils, cinder_utils) + +from snaps.openstack.create_network import OpenStackNetwork +from snaps.openstack.utils import heat_utils, neutron_utils __author__ = 'spisarski' logger = logging.getLogger('create_stack') -STACK_COMPLETE_TIMEOUT = 1200 -POLL_INTERVAL = 3 -STATUS_CREATE_FAILED = 'CREATE_FAILED' -STATUS_CREATE_COMPLETE = 'CREATE_COMPLETE' -STATUS_DELETE_COMPLETE = 'DELETE_COMPLETE' - -class OpenStackHeatStack: +class OpenStackHeatStack(OpenStackCloudObject, object): """ - Class responsible for creating an heat stack in OpenStack + Class responsible for managing a heat stack in OpenStack """ - def __init__(self, os_creds, stack_settings): + def __init__(self, os_creds, stack_settings, image_settings=None, + keypair_settings=None): """ Constructor :param os_creds: The OpenStack connection credentials :param stack_settings: The stack settings + :param image_settings: A list of ImageConfig objects that were used + for spawning this stack + :param keypair_settings: A list of KeypairConfig objects that were + used for spawning this stack :return: """ - self.__os_creds = os_creds + super(self.__class__, self).__init__(os_creds) + self.stack_settings = stack_settings + + if image_settings: + self.image_settings = image_settings + else: + self.image_settings = None + + if image_settings: + self.keypair_settings = keypair_settings + else: + self.keypair_settings = None + self.__stack = None self.__heat_cli = None - def create(self, cleanup=False): + self.__neutron = None + self.__nova = None + self.__glance = None + self.__cinder = None + + def initialize(self): """ - Creates the heat stack in OpenStack if it does not already exist and - returns the domain Stack object - :param cleanup: Denotes whether or not this is being called for cleanup - :return: The OpenStack Stack object + Loads the existing heat stack + :return: The Stack domain object or None """ - self.__heat_cli = heat_utils.heat_client(self.__os_creds) + super(self.__class__, self).initialize() + + self.__neutron = neutron_utils.neutron_client( + self._os_creds, self._os_session) + self.__nova = nova_utils.nova_client(self._os_creds, self._os_session) + self.__glance = glance_utils.glance_client( + self._os_creds, self._os_session) + self.__cinder = cinder_utils.cinder_client( + self._os_creds, self._os_session) + + self.__heat_cli = heat_utils.heat_client( + self._os_creds, self._os_session) self.__stack = heat_utils.get_stack( self.__heat_cli, stack_settings=self.stack_settings) if self.__stack: logger.info('Found stack with name - ' + self.stack_settings.name) return self.__stack - elif not cleanup: + + def create(self): + """ + Creates the heat stack in OpenStack if it does not already exist and + returns the domain Stack object + :return: The Stack domain object or None + """ + self.initialize() + + if self.__stack: + logger.info('Found stack with name - %s', self.stack_settings.name) + return self.__stack + else: self.__stack = heat_utils.create_stack(self.__heat_cli, self.stack_settings) logger.info( - 'Created stack with name - ' + self.stack_settings.name) + 'Created stack with name - %s', self.stack_settings.name) if self.__stack and self.stack_complete(block=True): - logger.info( - 'Stack is now active with name - ' + - self.stack_settings.name) + logger.info('Stack is now active with name - %s', + self.stack_settings.name) return self.__stack else: - raise StackCreationError( - 'Stack was not created or activated in the alloted amount ' - 'of time') - else: - logger.info('Did not create stack due to cleanup mode') - - return self.__stack + status = heat_utils.get_stack_status_reason(self.__heat_cli, + self.__stack.id) + logger.error('ERROR: STACK CREATION FAILED: %s', status) + raise StackCreationError('Failure while creating stack') def clean(self): """ @@ -86,11 +134,46 @@ class OpenStackHeatStack: """ if self.__stack: try: + logger.info('Deleting stack - %s', self.__stack.name) + heat_utils.delete_stack(self.__heat_cli, self.__stack) + + try: + self.stack_deleted(block=True) + except StackError as e: + # Stack deletion seems to fail quite a bit + logger.warn('Stack did not delete properly - %s', e) + + # Delete VMs first + for vm_inst_creator in self.get_vm_inst_creators(): + try: + vm_inst_creator.clean() + if not vm_inst_creator.vm_deleted(block=True): + logger.warn('Unable to deleted VM - %s', + vm_inst_creator.get_vm_inst().name) + except: + logger.warn('Unexpected error deleting VM - %s ', + vm_inst_creator.get_vm_inst().name) + + logger.info('Attempting to delete again stack - %s', + self.__stack.name) + + # Delete Stack again heat_utils.delete_stack(self.__heat_cli, self.__stack) + deleted = self.stack_deleted(block=True) + if not deleted: + raise StackError( + 'Stack could not be deleted ' + self.__stack.name) except HTTPNotFound: pass - self.__stack = None + self.__stack = None + + self.__neutron.httpclient.session.session.close() + self.__nova.client.session.session.close() + self.__glance.http_client.session.session.close() + self.__cinder.client.session.session.close() + + super(self.__class__, self).clean() def get_stack(self): """ @@ -98,7 +181,8 @@ class OpenStackHeatStack: called :return: the object """ - return self.__stack + if self.__stack: + return heat_utils.get_stack_by_id(self.__heat_cli, self.__stack.id) def get_outputs(self): """ @@ -106,7 +190,7 @@ class OpenStackHeatStack: object :return: """ - return heat_utils.get_stack_outputs(self.__heat_cli, self.__stack.id) + return heat_utils.get_outputs(self.__heat_cli, self.__stack) def get_status(self): """ @@ -114,10 +198,12 @@ class OpenStackHeatStack: object :return: """ - return heat_utils.get_stack_status(self.__heat_cli, self.__stack.id) + stack = self.get_stack() + if stack: + return stack.status def stack_complete(self, block=False, timeout=None, - poll_interval=POLL_INTERVAL): + poll_interval=snaps.config.stack.POLL_INTERVAL): """ Returns true when the stack status returns the value of expected_status_code @@ -129,11 +215,220 @@ class OpenStackHeatStack: """ if not timeout: timeout = self.stack_settings.stack_create_timeout - return self._stack_status_check(STATUS_CREATE_COMPLETE, block, timeout, - poll_interval) + return self._stack_status_check( + snaps.config.stack.STATUS_CREATE_COMPLETE, block, timeout, + poll_interval, snaps.config.stack.STATUS_CREATE_FAILED) + + def stack_deleted(self, block=False, + timeout=snaps.config.stack.STACK_DELETE_TIMEOUT, + poll_interval=snaps.config.stack.POLL_INTERVAL): + """ + Returns true when the stack status returns the value of + expected_status_code + :param block: When true, thread will block until active or timeout + value in seconds has been exceeded (False) + :param timeout: The timeout value + :param poll_interval: The polling interval in seconds + :return: T/F + """ + return self._stack_status_check( + snaps.config.stack.STATUS_DELETE_COMPLETE, block, timeout, + poll_interval, snaps.config.stack.STATUS_DELETE_FAILED) + + def get_network_creators(self): + """ + Returns a list of network creator objects as configured by the heat + template + :return: list() of OpenStackNetwork objects + """ + + out = list() + stack_networks = heat_utils.get_stack_networks( + self.__heat_cli, self.__neutron, self.__stack) + + for stack_network in stack_networks: + net_settings = settings_utils.create_network_config( + self.__neutron, stack_network) + net_creator = OpenStackNetwork(self._os_creds, net_settings) + out.append(net_creator) + net_creator.initialize() + + return out + + def get_security_group_creators(self): + """ + Returns a list of security group creator objects as configured by the + heat template + :return: list() of OpenStackNetwork objects + """ + + out = list() + stack_security_groups = heat_utils.get_stack_security_groups( + self.__heat_cli, self.__neutron, self.__stack) + + for stack_security_group in stack_security_groups: + settings = settings_utils.create_security_group_config( + self.__neutron, stack_security_group) + creator = OpenStackSecurityGroup(self._os_creds, settings) + out.append(creator) + creator.initialize() + + return out + + def get_router_creators(self): + """ + Returns a list of router creator objects as configured by the heat + template + :return: list() of OpenStackRouter objects + """ + + out = list() + stack_routers = heat_utils.get_stack_routers( + self.__heat_cli, self.__neutron, self.__stack) + + for routers in stack_routers: + settings = settings_utils.create_router_config( + self.__neutron, routers) + creator = OpenStackRouter(self._os_creds, settings) + out.append(creator) + creator.initialize() + + return out + + def get_vm_inst_creators(self, heat_keypair_option=None): + """ + Returns a list of VM Instance creator objects as configured by the heat + template + :return: list() of OpenStackVmInstance objects + """ + + out = list() + + stack_servers = heat_utils.get_stack_servers( + self.__heat_cli, self.__nova, self.__neutron, self._keystone, + self.__stack, self._os_creds.project_name) + + for stack_server in stack_servers: + vm_inst_settings = settings_utils.create_vm_inst_config( + self.__nova, self._keystone, self.__neutron, stack_server, + self._os_creds.project_name) + image_settings = settings_utils.determine_image_config( + self.__glance, stack_server, self.image_settings) + keypair_settings = settings_utils.determine_keypair_config( + self.__heat_cli, self.__stack, stack_server, + keypair_settings=self.keypair_settings, + priv_key_key=heat_keypair_option) + vm_inst_creator = OpenStackVmInstance( + self._os_creds, vm_inst_settings, image_settings, + keypair_settings) + out.append(vm_inst_creator) + vm_inst_creator.initialize() + + return out + + def get_volume_creators(self): + """ + Returns a list of Volume creator objects as configured by the heat + template + :return: list() of OpenStackVolume objects + """ + + out = list() + volumes = heat_utils.get_stack_volumes( + self.__heat_cli, self.__cinder, self.__stack) + + for volume in volumes: + settings = settings_utils.create_volume_config(volume) + creator = OpenStackVolume(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume creator - %s', e) + + return out + + def get_volume_type_creators(self): + """ + Returns a list of VolumeType creator objects as configured by the heat + template + :return: list() of OpenStackVolumeType objects + """ + + out = list() + vol_types = heat_utils.get_stack_volume_types( + self.__heat_cli, self.__cinder, self.__stack) + + for volume in vol_types: + settings = settings_utils.create_volume_type_config(volume) + creator = OpenStackVolumeType(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume type creator - %s', + e) + + return out + + def get_keypair_creators(self, outputs_pk_key=None): + """ + Returns a list of keypair creator objects as configured by the heat + template + :return: list() of OpenStackKeypair objects + """ + + out = list() + + keypairs = heat_utils.get_stack_keypairs( + self.__heat_cli, self.__nova, self.__stack) + + for keypair in keypairs: + settings = settings_utils.create_keypair_config( + self.__heat_cli, self.__stack, keypair, outputs_pk_key) + creator = OpenStackKeypair(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume type creator - %s', + e) + + return out + + def get_flavor_creators(self): + """ + Returns a list of Flavor creator objects as configured by the heat + template + :return: list() of OpenStackFlavor objects + """ + + out = list() + + flavors = heat_utils.get_stack_flavors( + self.__heat_cli, self.__nova, self.__stack) + + for flavor in flavors: + settings = settings_utils.create_flavor_config(flavor) + creator = OpenStackFlavor(self._os_creds, settings) + out.append(creator) + + try: + creator.initialize() + except Exception as e: + logger.error( + 'Unexpected error initializing volume creator - %s', e) + + return out def _stack_status_check(self, expected_status_code, block, timeout, - poll_interval): + poll_interval, fail_status): """ Returns true when the stack status returns the value of expected_status_code @@ -143,6 +438,7 @@ class OpenStackHeatStack: value in seconds has been exceeded (False) :param timeout: The timeout value :param poll_interval: The polling interval in seconds + :param fail_status: Returns false if the fail_status code is found :return: T/F """ # sleep and wait for stack status change @@ -152,7 +448,7 @@ class OpenStackHeatStack: start = time.time() - timeout while timeout > time.time() - start: - status = self._status(expected_status_code) + status = self._status(expected_status_code, fail_status) if status: logger.debug( 'Stack is active with name - ' + self.stack_settings.name) @@ -168,7 +464,8 @@ class OpenStackHeatStack: 'Timeout checking for stack status for ' + expected_status_code) return False - def _status(self, expected_status_code): + def _status(self, expected_status_code, + fail_status=snaps.config.stack.STATUS_CREATE_FAILED): """ Returns True when active else False :param expected_status_code: stack status evaluated with this string @@ -181,55 +478,54 @@ class OpenStackHeatStack: 'Cannot stack status for stack with ID - ' + self.__stack.id) return False - if status == STATUS_CREATE_FAILED: - raise StackCreationError('Stack had an error during deployment') + if fail_status and status == fail_status: + resources = heat_utils.get_resources( + self.__heat_cli, self.__stack.id) + logger.error('Stack %s failed', self.__stack.name) + for resource in resources: + if (resource.status != + snaps.config.stack.STATUS_CREATE_COMPLETE): + logger.error( + 'Resource: [%s] status: [%s] reason: [%s]', + resource.name, resource.status, resource.status_reason) + else: + logger.debug( + 'Resource: [%s] status: [%s] reason: [%s]', + resource.name, resource.status, resource.status_reason) + + raise StackError('Stack had an error') logger.debug('Stack status is - ' + status) return status == expected_status_code -class StackSettings: - def __init__(self, **kwargs): - """ - Constructor - :param name: the stack's name (required) - :param template: the heat template in dict() format (required if - template_path attribute is None) - :param template_path: the location of the heat template file (required - if template attribute is None) - :param env_values: k/v pairs of strings for substitution of template - default values (optional) - """ - - self.name = kwargs.get('name') - self.template = kwargs.get('template') - self.template_path = kwargs.get('template_path') - self.env_values = kwargs.get('env_values') - if 'stack_create_timeout' in kwargs: - self.stack_create_timeout = kwargs['stack_create_timeout'] - else: - self.stack_create_timeout = STACK_COMPLETE_TIMEOUT - - if not self.name: - raise StackSettingsError('name is required') - - if not self.template and not self.template_path: - raise StackSettingsError('A Heat template is required') +def generate_creator(os_creds, stack_inst, image_settings): + """ + Initializes an OpenStackHeatStack object + :param os_creds: the OpenStack credentials + :param stack_inst: the SNAPS-OO VmInst domain object + :param image_settings: list of SNAPS-OO ImageConfig objects + :return: an initialized OpenStackHeatStack object + """ - def __eq__(self, other): - return (self.name == other.name and - self.template == other.template and - self.template_path == other.template_path and - self.env_values == other.env_values and - self.stack_create_timeout == other.stack_create_timeout) + heat_config = StackConfig( + name=stack_inst.name, template={'place': 'holder'}) + heat_creator = OpenStackHeatStack(os_creds, heat_config, image_settings) + heat_creator.initialize() + return heat_creator -class StackSettingsError(Exception): +class StackSettings(StackConfig): """ - Exception to be thrown when an stack settings are incorrect + Class to hold the configuration settings required for creating OpenStack + stack objects + deprecated """ - def __init__(self, message): - Exception.__init__(self, message) + def __init__(self, **kwargs): + from warnings import warn + warn('Use snaps.config.stack.StackConfig instead', + DeprecationWarning) + super(self.__class__, self).__init__(**kwargs) class StackCreationError(Exception): @@ -237,5 +533,8 @@ class StackCreationError(Exception): Exception to be thrown when an stack cannot be created """ - def __init__(self, message): - Exception.__init__(self, message) + +class StackError(Exception): + """ + General exception + """