def upgrade(): conf = cfg.ConfigOpts() conf.register_cli_opts([ cfg.BoolOpt('skip-gnocchi-resource-types', help='Skip gnocchi resource-types upgrade.', default=False), cfg.IntOpt('retry', min=0, help='Number of times to retry on failure. ' 'Default is to retry forever.'), ]) service.prepare_service(conf=conf) if conf.skip_gnocchi_resource_types: LOG.info("Skipping Gnocchi resource types upgrade") else: LOG.debug("Upgrading Gnocchi resource types") from ceilometer import gnocchi_client from gnocchiclient import exceptions if conf.retry is None: stop = tenacity.stop_never else: stop = tenacity.stop_after_attempt(conf.retry) tenacity.Retrying( stop=stop, retry=tenacity.retry_if_exception_type(( exceptions.ConnectionFailure, exceptions.UnknownConnectionError, exceptions.ConnectionTimeout, exceptions.SSLError, )) )(gnocchi_client.upgrade_resource_types, conf)
def sync_to_db(self, session: Optional[Session] = None): """Save attributes about list of DAG to the DB.""" # To avoid circular import - airflow.models.dagbag -> airflow.models.dag -> airflow.models.dagbag from airflow.models.dag import DAG from airflow.models.serialized_dag import SerializedDagModel # Retry 'DAG.bulk_write_to_db' & 'SerializedDagModel.bulk_sync_to_db' in case # of any Operational Errors # In case of failures, provide_session handles rollback for attempt in tenacity.Retrying( retry=tenacity.retry_if_exception_type( exception_types=OperationalError), wait=tenacity.wait_random_exponential(multiplier=0.5, max=5), stop=tenacity.stop_after_attempt(settings.MAX_DB_RETRIES), before_sleep=tenacity.before_sleep_log(self.log, logging.DEBUG), reraise=True): with attempt: self.log.debug( "Running dagbag.sync_to_db with retries. Try %d of %d", attempt.retry_state.attempt_number, settings.MAX_DB_RETRIES) self.log.debug("Calling the DAG.bulk_sync_to_db method") try: DAG.bulk_write_to_db(self.dags.values(), session=session) # Write Serialized DAGs to DB self.log.debug( "Calling the SerializedDagModel.bulk_sync_to_db method" ) SerializedDagModel.bulk_sync_to_db(self.dags.values(), session=session) except OperationalError: session.rollback() raise
def sync_images(): """Run image sync using an action. Execute an initial image sync using an action to ensure that the cloud is populated with images at the right point in time during deployment. """ logging.info("Synchronising images using glance-simplestreams-sync") catalog = None try: for attempt in tenacity.Retrying(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential( multiplier=1, min=2, max=10), reraise=True): with attempt: # Proactively retrieve the Keystone service catalog so that we # can log it in the event of a failure. catalog = _get_catalog() generic_utils.assertActionRanOK( zaza_model.run_action_on_leader( "glance-simplestreams-sync", "sync-images", raise_on_failure=True, action_params={}, )) except Exception: logging.info('Contents of Keystone service catalog: "{}"'.format( pprint.pformat(catalog))) raise
def _beat_forever_until_stopped(self): """Inner beating loop.""" retry = tenacity.Retrying( wait=tenacity.wait_fixed(1), before_sleep=tenacity.before_sleep_log(LOG, logging.WARNING), ) while not self._dead.is_set(): with timeutils.StopWatch() as w: wait_until_next_beat = retry(self._driver.heartbeat) ran_for = w.elapsed() has_to_sleep_for = wait_until_next_beat - ran_for if has_to_sleep_for < 0: LOG.warning( "Heartbeating took too long to execute (it ran for" " %0.2f seconds which is %0.2f seconds longer than" " the next heartbeat idle time). This may cause" " timeouts (in locks, leadership, ...) to" " happen (which will not end well).", ran_for, ran_for - wait_until_next_beat) self._beats += 1 # NOTE(harlowja): use the event object for waiting and # not a sleep function since doing that will allow this code # to terminate early if stopped via the stop() method vs # having to wait until the sleep function returns. # NOTE(jd): Wait for only the half time of what we should. # This is a measure of safety, better be too soon than too late. self._dead.wait(has_to_sleep_for / 2.0)
def run_with_advanced_retry(self, _retry_args: Dict[Any, Any], *args: Any, **kwargs: Any) -> Any: """ Runs Hook.run() with a Tenacity decorator attached to it. This is useful for connectors which might be disturbed by intermittent issues and should not instantly fail. :param _retry_args: Arguments which define the retry behaviour. See Tenacity documentation at https://github.com/jd/tenacity :type _retry_args: dict .. code-block:: python hook = HttpHook(http_conn_id='my_conn',method='GET') retry_args = dict( wait=tenacity.wait_exponential(), stop=tenacity.stop_after_attempt(10), retry=requests.exceptions.ConnectionError ) hook.run_with_advanced_retry( endpoint='v1/test', _retry_args=retry_args ) """ self._retry_obj = tenacity.Retrying(**_retry_args) return self._retry_obj(self.run, *args, **kwargs)
def _mount_share_on_instance(self, instance_ip, ssh_user_name, ssh_private_key, share_path): """Mount a share into a Nova instance. The mount command is executed via SSH. :param instance_ip: IP of the Nova instance. :type instance_ip: string :param ssh_user_name: SSH user name. :type ssh_user_name: string :param ssh_private_key: SSH private key. :type ssh_private_key: string :param share_path: Share network path. :type share_path: string """ ssh_cmd = ('sudo mkdir -p {0} && ' 'sudo mount -t {1} -o {2} {3} {0}'.format( self.mount_dir, self.share_protocol, self._get_mount_options(), share_path)) for attempt in tenacity.Retrying(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential( multiplier=3, min=2, max=10)): with attempt: openstack_utils.ssh_command( vm_name="instance-{}".format(instance_ip), ip=instance_ip, username=ssh_user_name, privkey=ssh_private_key, command=ssh_cmd, verify=verify_status)
def test_detach(self): """Admin only operation.""" function_id = self.create_function(self.python_zip_file) resp, _ = self.client.create_execution( function_id, input='{"name": "Qinling"}' ) self.assertEqual(201, resp.status) resp, body = self.admin_client.get_function_workers(function_id) self.assertEqual(200, resp.status) self.assertEqual(1, len(body['workers'])) # Detach function resp, _ = self.admin_client.detach_function(function_id) self.assertEqual(202, resp.status) def _assert_workers(): resp, body = self.admin_client.get_function_workers(function_id) self.assertEqual(200, resp.status) self.assertEqual(0, len(body['workers'])) r = tenacity.Retrying( wait=tenacity.wait_fixed(1), stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_if_exception_type(AssertionError) ) r.call(_assert_workers)
def __init__(self, *args, **kwargs): """ Parameters ---------- **kwargs ``amqp_uri`` : str Uri to be used to communicate with the AMQP broker. ``queue_name`` : str Name of the queue that should be used in the AMQP broker, to store the messages. ``alertable_event`` : threading.Event Event to be used to determine when the `self._retrying_policy` must stop re-trying. """ super().__init__() self._amqp_uri: str = kwargs.get("amqp_uri") self._queue_name: str = kwargs.get("queue_name") self._alertable_event = kwargs.get("alertable_event") self._cnx: typing.Optional[pika.BlockingConnection] = None self._channel: typing.Optional[pika.channel.Channel] = None # Define re-trying policy to be used, when fails to successfully communicate with the AMQP broker: self._retrying_policy = tenacity.Retrying( wait=tenacity.wait_random_exponential( multiplier=0.5, max=30), # Random exponential back-off before retry retry=tenacity.retry_if_exception_type( pika.exceptions.AMQPConnectionError), stop=tenacity.stop.stop_when_event_set(self._alertable_event), after=lambda _, __, ___: self._log.warning( f"Unable to connect to AMQP server with uri: {self._amqp_uri}") ) self._log.debug( f"{self.__class__.__name__}.__init__(queue_name={self._queue_name})" )
def check_dns_record_exists(dns_server_ip, query_name, expected_ip, retry_count=3): """Lookup a DNS record against the given dns server address. :param dns_server_ip: IP address to run query against :type dns_server_ip: str :param query_name: Record to lookup :type query_name: str :param expected_ip: IP address expected to be associated with record. :type expected_ip: str :param retry_count: Number of times to retry query. Useful if waiting for record to propagate. :type retry_count: int :raises: AssertionError """ my_resolver = dns.resolver.Resolver() my_resolver.nameservers = [dns_server_ip] for attempt in tenacity.Retrying( stop=tenacity.stop_after_attempt(retry_count), wait=tenacity.wait_exponential(multiplier=1, min=2, max=10), reraise=True): with attempt: logging.info("Checking record {} against {}".format( query_name, dns_server_ip)) answers = my_resolver.query(query_name) for rdata in answers: logging.info( "Checking address returned by {} is correct".format(dns_server_ip)) assert str(rdata) == expected_ip
def __init__(self, configuration=None, header_name=None, header_value=None, cookie=None, pool_threads=1): if configuration is None: configuration = Configuration() self.configuration = configuration self.pool_threads = pool_threads self.retrying = tenacity.Retrying( stop=tenacity.stop_after_attempt(configuration.retry_count), wait=tenacity.wait_random_exponential( multiplier=configuration.back_off, max=configuration.retry_max_delay, min=configuration.retry_delay), retry=(tenacity.retry_if_result(self.is_retry_enabled) and ((tenacity.retry_if_exception_type(RetryableException)) | (tenacity.retry_if_exception_type(HTTPError))))) self.rest_client = rest.RESTClientObject(configuration, retrying=self.retrying) self.default_headers = {} if header_name is not None: self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. self.user_agent = 'opsgenie-sdk-python-2.0.2' # init metric publishers self.http_metric_publisher = self.rest_client.http_metric self.api_metric_publisher = metrics.ApiMetric('ApiMetricPublisher') self.sdk_metric_publisher = metrics.SdkMetric('SdkMetricPublisher')
def create_pull_request( self, *, head: str, title: str, body: Optional[str], labels: AbstractSet[str], ) -> PullRequest: """Create a pull request.""" pull_request = self._repository.create_pull( title=title, body=body, head=head, base=self._repository.default_branch, ) # The GitHub API sometimes responds with 404 when the issue for # a newly created pull request is requested. for attempt in tenacity.Retrying( # type: ignore[no-untyped-call] reraise=True, stop=tenacity.stop_after_attempt( 3), # type: ignore[attr-defined] wait=tenacity.wait_fixed(3), # type: ignore[attr-defined] ): with attempt: pull_request.issue().add_labels(*labels) return PullRequest(pull_request)
def create_workload(self, instance_id): """Create a new workload. :param instance_id: instance ID to create workload from :type instance_id: str :returns: workload ID :rtype: str """ workload_id = juju_utils.remote_run( self.trilio_wlm_unit, remote_cmd=self.WORKLOAD_CREATE_CMD.format( auth_args=self.auth_args, instance_id=instance_id), timeout=180, fatal=True, ).strip() retryer = tenacity.Retrying( wait=tenacity.wait_exponential(multiplier=1, max=30), stop=tenacity.stop_after_delay(180), reraise=True, ) retryer( _resource_reaches_status, self.trilio_wlm_unit, self.auth_args, self.WORKLOAD_STATUS_CMD, self.WORKLOAD_FULL_STATUS_CMD, workload_id, "available", ) return workload_id
def _async_create_description( permalink: Permalink, status_update: Callable[[str], None], attempts: int, ) -> LayoutDescription: """ :param permalink: :param status_update: :return: """ rng = Random(permalink.as_bytes) presets = { i: permalink.get_preset(i) for i in range(permalink.player_count) } retrying = tenacity.Retrying( stop=tenacity.stop_after_attempt(attempts), retry=tenacity.retry_if_exception_type(UnableToGenerate), reraise=True) filler_results = retrying(_create_pools_and_fill, rng, presets, status_update) all_patches = _distribute_remaining_items(rng, filler_results.player_results) return LayoutDescription( permalink=permalink, version=VERSION, all_patches=all_patches, item_order=filler_results.action_log, )
def resource_removed(resource, resource_id, msg='resource', wait_exponential_multiplier=1, wait_iteration_max_time=60, stop_after_attempt=8): """Wait for an openstack resource to no longer be present. :param resource: pointer to os resource type, ex: heat_client.stacks :type resource: str :param resource_id: unique id for the openstack resource :type resource_id: str :param msg: text to identify purpose in logging :type msg: str :param wait_exponential_multiplier: Wait 2^x * wait_exponential_multiplier seconds between each retry :type wait_exponential_multiplier: int :param wait_iteration_max_time: Wait a max of wait_iteration_max_time between retries. :type wait_iteration_max_time: int :param stop_after_attempt: Stop after stop_after_attempt retires. :type stop_after_attempt: int :raises: AssertionError """ retryer = tenacity.Retrying( wait=tenacity.wait_exponential( multiplier=wait_exponential_multiplier, max=wait_iteration_max_time), reraise=True, stop=tenacity.stop_after_attempt(stop_after_attempt)) retryer( _resource_removed, resource, resource_id, msg)
def main(): lifxlan = LifxLAN() clap = Button(GPIO_BUTTON) log.info(startup_msg_tpl.format(LIGHT_NAME, CLAP_COUNT, SEQ_ALLOWANCE)) r = t.Retrying(reraise=True, stop=t.stop_after_attempt(RETRY_COUNT), retry=t.retry_if_exception_type(WorkflowException)) group = r.call(lifxlan.get_devices_by_name, LIGHT_NAME) device = group.devices[0] log.info('Ready for input...') while True: # sleep is to slow the script down, so it consumes less CPU sleep(SCRIPT_SLOWINESS) counter = 0 if clap.is_active: first_clap = time() while time()-first_clap < SEQ_ALLOWANCE: # VM-CLAP1 button should be still active at this point, # so we count our first clap if clap.is_active: counter += 1 log.debug('COUNTER: {}/{}'.format(counter, CLAP_COUNT)) if counter == CLAP_COUNT: toggle_lights(device) break sleep(SCRIPT_SLOWINESS)
def _get_retrying(before_fn): return tenacity.Retrying( retry=_token_needs_refresh, before=before_fn, stop=tenacity.stop_after_attempt(2), reraise=True, ).wraps
def retry_on_db_error(func, retry=None): """Decorates the given function so that it retries on DB errors. Note that the decorator retries the function/method only on some of the DB errors that are considered to be worth retrying, like deadlocks and disconnections. :param func: Function to decorate. :param retry: a Retrying object :return: Decorated function. """ if not retry: retry = tenacity.Retrying( retry=tenacity.retry_if_exception_type(_RETRY_ERRORS), stop=tenacity.stop_after_attempt(50), wait=tenacity.wait_incrementing(start=0, increment=0.1, max=2)) # The `assigned` arg should be empty as some of the default values are not # supported by simply initialized MagicMocks. The consequence may # be that the representation will contain the wrapper and not the # wrapped function. @functools.wraps(func, assigned=[]) def decorate(*args, **kw): # Retrying library decorator might potentially run a decorated # function within a new thread so it's safer not to apply the # decorator directly to a target method/function because we can # lose an authentication context. # The solution is to create one more function and explicitly set # auth context before calling it (potentially in a new thread). auth_ctx = context.ctx() if context.has_ctx() else None return retry.call(_with_auth_context, auth_ctx, func, *args, **kw) return decorate
def query_unique_and_run(clusters, entity_ref, command_fn, wait=False): """Calls query_unique and then calls the given command_fn on the resulting job instance""" def query_unique_and_run(): query_result = query_unique(clusters, entity_ref) if query_result['type'] == Types.JOB: job = query_result['data'] instance = __get_latest_instance(job) directory = mesos.retrieve_instance_sandbox_directory( instance, job) command_fn(instance, directory) elif query_result['type'] == Types.INSTANCE: instance, job = query_result['data'] directory = mesos.retrieve_instance_sandbox_directory( instance, job) command_fn(instance, directory) else: # This should not happen, because query_unique should # only return a map with type "job" or type "instance" raise Exception( f'Encountered error when querying for {entity_ref}.') if wait: # Importing tenacity locally to prevent startup time # from increasing in the default (i.e. don't wait) case import tenacity one_day_in_seconds = 24 * 60 * 60 r = tenacity.Retrying( wait=tenacity.wait_fixed(5), retry=tenacity.retry_if_exception_type(CookRetriableException), stop=tenacity.stop_after_delay(one_day_in_seconds), reraise=True) r.call(query_unique_and_run) else: query_unique_and_run()
def _get_request_json(self, url: str) -> Dict: r = tenacity.Retrying( wait=wait_exponential( multiplier=self.config.api_options.retry_backoff_multiplier, max=self.config.api_options.max_retry_interval, ), retry=retry_if_exception_type(HTTPError429), stop=stop_after_attempt(self.config.api_options.max_attempts), ) @r.wraps def get_request(): try: response = self.session.get(url) response.raise_for_status() return response.json() except HTTPError as http_error: error_response = http_error.response if error_response.status_code == 429: # respect Retry-After sleep_time = error_response.headers.get("retry-after") if sleep_time is not None: time.sleep(sleep_time) raise HTTPError429 raise http_error return {} return get_request()
def __attrs_post_init__(self): if self.max_retry_delay is None: self.max_retry_delay = self.timeout * 3 self._retry_upload = tenacity.Retrying( # Retry after 1s, 2s, 4s, 8s with some randomness wait=tenacity.wait_random_exponential(multiplier=0.5), stop=tenacity.stop_after_delay(self.max_retry_delay), retry_error_cls=UploadFailed, retry=tenacity.retry_if_exception_type((http_client.HTTPException, OSError, IOError)), ) tags = { k: six.ensure_binary(v) for k, v in itertools.chain( parse_tags_str(os.environ.get("DD_TAGS")).items(), parse_tags_str(os.environ.get("DD_PROFILING_TAGS")).items(), ) } tags.update({k: six.ensure_binary(v) for k, v in self.tags.items()}) tags.update( { "host": HOSTNAME.encode("utf-8"), "language": b"python", "runtime": PYTHON_IMPLEMENTATION, "runtime_version": PYTHON_VERSION, "profiler_version": ddtrace.__version__.encode("ascii"), } ) if self.version: tags["version"] = self.version.encode("utf-8") if self.env: tags["env"] = self.env.encode("utf-8") self.tags = tags
def run_compute_singles(run, site, processed_output_path, database): """Compute the singles (underlying uncorrelated) rate.""" path_prefix = os.path.join(processed_output_path, f'EH{site}') ads = common.dets_for(site, run) update_db = True iteration = 0 extra_cut = '1' for ad in ads: infile = os.path.join( path_prefix, f'hadded_ad{ad}/out_ad{ad}_{run}.root', ) # Ideally should check to see if rate has been computed before. # But, this is so fast that I will just re-compute every time. for attempt in tenacity.Retrying( reraise=True, wait=tenacity.wait_random_exponential(max=60), retry=tenacity.retry_if_exception_type(sqlite3.Error), before_sleep=tenacity.before_sleep_log(logging, logging.DEBUG), ): with attempt: compute_singles.main( infile, database, GENERAL_LABEL, update_db, iteration, extra_cut, ) return
def add_port_to_netplan(neutron_client, network_name, application_name): units = { u.data['machine-id']: u.entity_id for u in zaza.model.get_units(application_name=application_name) } # Fold back into zaza.openstack.utilities.openstack network = get_network(neutron_client, network_name) for machine in zaza.model.get_machines(application_name=application_name): unit_name = units[machine.entity_id] port_name = get_port_name(network, machine) port = get_port(neutron_client, port_name) mac_address = port['mac_address'] run_cmd_nic = "ip -f link -br -o addr|grep {}".format(mac_address) logging.info("Running '{}' on {}".format(run_cmd_nic, unit_name)) interface = zaza.model.run_on_unit(unit_name, run_cmd_nic) interface = interface['Stdout'].split(' ')[0] run_cmd_netplan = """sudo egrep -iR '{}|{}$' /etc/netplan/ """.format(mac_address, interface) logging.info("Running '{}' on {}".format(run_cmd_netplan, unit_name)) netplancfg = zaza.model.run_on_unit(unit_name, run_cmd_netplan) if (mac_address in str(netplancfg)) or (interface in str(netplancfg)): logging.warn("mac address {} or nic {} already exists in " "/etc/netplan".format(mac_address, interface)) continue body_value = textwrap.dedent("""\ network: ethernets: {0}: dhcp4: true dhcp6: false optional: true match: macaddress: {1} set-name: {0} version: 2 """.format(interface, mac_address)) for attempt in tenacity.Retrying(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential( multiplier=1, min=2, max=10)): with attempt: with tempfile.NamedTemporaryFile(mode="w") as netplan_file: netplan_file.write(body_value) netplan_file.flush() logging.info("Copying {} to {}".format( unit_name, '/home/ubuntu/60-dataport.yaml')) subprocess.check_call([ 'juju', 'scp', netplan_file.name, '{}:/home/ubuntu/60-dataport.yaml'.format(unit_name) ]) run_cmd_mv = ("sudo mv /home/ubuntu/60-dataport.yaml " "/etc/netplan/") logging.info("Running '{}' on {}".format( run_cmd_mv, unit_name)) zaza.model.run_on_unit(unit_name, run_cmd_mv) logging.info("Running netplan apply") zaza.model.run_on_unit(unit_name, "sudo netplan apply")
def retrying(self): retry_condition = ( tenacity.retry_if_exception_type(ApiConnectionError) | tenacity.retry_if_exception_type(ApiInternalServerError)) return tenacity.Retrying(reraise=True, retry=retry_condition, stop=tenacity.stop_after_attempt( self.config.RETRIES))
def wait_till_jobs_complete(self, node, timeout=3600): name = node.get('metadata', {}).get('name') retryer = tenacity.Retrying( stop=tenacity.stop_after_delay(timeout), retry=tenacity.retry_if_result( lambda result: len(result.get('items', [])) != 0), wait=tenacity.wait_fixed(5)) retryer(self._get_job_pods_in_node, name, "Running")
def wrapped_retry(*args, **kwargs): retryer = tenacity.Retrying( wait=tenacity.wait_exponential(min=3), retry=tenacity.retry_if_exception_type(exception_type), stop=tenacity.stop_after_attempt(5), reraise=True, before_sleep=log_retry) return retryer(func, *args, **kwargs)
def _retry_send(self, function: Callable, attempt=10, *args, **kwargs): retry_configuration = tenacity.Retrying( stop=tenacity.stop_after_attempt(attempt), wait=tenacity.wait_fixed(3) + tenacity.wait_random(0, 2), after=tenacity.after_log(logger, logger.level) if logger else None, reraise=True, ) return retry_configuration(function, *args, **kwargs)
def wait_for(func): """Waits for function to return truthy value.""" def is_falsy(value): """Return whether value if falsy (None or False).""" return not value return tenacity.Retrying( stop=tenacity.stop_after_delay(constants.ux.MAX_USER_WAIT_SECONDS), retry=tenacity.retry_if_result(is_falsy))(func)
def wait_for(func, timeout=constants.timeouts.TWO_MIN_USER_WAIT): """Waits for function to return truthy value.""" def is_falsy(value): """Return whether value if falsy (None or False).""" return not value return tenacity.Retrying(stop=tenacity.stop_after_delay(timeout), retry=tenacity.retry_if_result(is_falsy))(func)
def wrapped_retry(*args, **kwargs): retryer = tenacity.Retrying( wait=tenacity.wait_random(1, 3), retry=tenacity.retry_if_exception_type(exception_type), stop=tenacity.stop_after_attempt(retries + 1), reraise=True, before_sleep=log_retry) return retryer(func, *args, **kwargs)
def _create(self): """Create AWS security group using instance parameters Returns ------- string security group ID for the created security group """ # Create the security group response = clients['ec2'].create_security_group( GroupName=self.name, Description=self.description, VpcId=self.vpc.vpc_id) group_id = response.get('GroupId') # Add ingress rules to the security group ipv4_ranges = [{'CidrIp': '0.0.0.0/0'}] ipv6_ranges = [{'CidrIpv6': '::/0'}] ip_permissions = [{ 'IpProtocol': 'TCP', 'FromPort': 80, 'ToPort': 80, 'IpRanges': ipv4_ranges, 'Ipv6Ranges': ipv6_ranges }, { 'IpProtocol': 'TCP', 'FromPort': 22, 'ToPort': 22, 'IpRanges': ipv4_ranges, 'Ipv6Ranges': ipv6_ranges }] retry = tenacity.Retrying(wait=tenacity.wait_exponential(max=16), stop=tenacity.stop_after_delay(120), retry=tenacity.retry_if_exception_type( clients['ec2'].exceptions.ClientError)) retry.call(clients['ec2'].authorize_security_group_ingress, GroupId=group_id, IpPermissions=ip_permissions) mod_logger.info('Created security group {id:s}'.format(id=group_id)) # Tag the security group with owner=cloudknot retry.call(clients['ec2'].create_tags, Resources=[group_id], Tags=[{ 'Key': 'owner', 'Value': 'cloudknot' }]) # Add this security group to the config file self._section_name = self._get_section_name('security-groups') cloudknot.config.add_resource(self._section_name, group_id, self.name) return group_id