+
+ def store_results(self, file_name=None, threshold=None):
+ """ Stores specified results into files based on file_name value.
+
+ Arguments:
+ :param file_name: Trailing (common) part of result file names
+ :param threshold: Minimum number of sent updates needed for each
+ result to be included into result csv file
+ (mainly needed because of the result accuracy)
+ Returns:
+ :return: n/a
+ """
+ # default values handling
+ # TODO optimize default values handling (use e.g. dicionary.update() approach)
+ if file_name is None:
+ file_name = self.results_file_name_default
+ if threshold is None:
+ threshold = self.performance_threshold_default
+ # performance calculation
+ if self.phase1_updates_sent >= threshold:
+ totals1 = self.phase1_updates_sent
+ performance1 = int(self.phase1_updates_sent /
+ (self.phase1_stop_time - self.phase1_start_time))
+ else:
+ totals1 = None
+ performance1 = None
+ if self.phase2_updates_sent >= threshold:
+ totals2 = self.phase2_updates_sent
+ performance2 = int(self.phase2_updates_sent /
+ (self.phase2_stop_time - self.phase2_start_time))
+ else:
+ totals2 = None
+ performance2 = None
+
+ logger.info("#" * 10 + " Final results " + "#" * 10)
+ logger.info("Number of iterations: " + str(self.iteration))
+ logger.info("Number of UPDATE messages sent in the pre-fill phase: " +
+ str(self.phase1_updates_sent))
+ logger.info("The pre-fill phase duration: " +
+ str(self.phase1_stop_time - self.phase1_start_time) + "s")
+ logger.info("Number of UPDATE messages sent in the 2nd test phase: " +
+ str(self.phase2_updates_sent))
+ logger.info("The 2nd test phase duration: " +
+ str(self.phase2_stop_time - self.phase2_start_time) + "s")
+ logger.info("Threshold for performance reporting: " + str(threshold))
+
+ # making labels
+ phase1_label = ("pre-fill " + str(self.prefix_count_to_add_default) +
+ " route(s) per UPDATE")
+ if self.single_update_default:
+ phase2_label = "+" + (str(self.prefix_count_to_add_default) +
+ "/-" + str(self.prefix_count_to_del_default) +
+ " routes per UPDATE")
+ else:
+ phase2_label = "+" + (str(self.prefix_count_to_add_default) +
+ "/-" + str(self.prefix_count_to_del_default) +
+ " routes in two UPDATEs")
+ # collecting capacity and performance results
+ totals = {}
+ performance = {}
+ if totals1 is not None:
+ totals[phase1_label] = totals1
+ performance[phase1_label] = performance1
+ if totals2 is not None:
+ totals[phase2_label] = totals2
+ performance[phase2_label] = performance2
+ self.write_results_to_file(totals, "totals-" + file_name)
+ self.write_results_to_file(performance, "performance-" + file_name)
+
+ def write_results_to_file(self, results, file_name):
+ """Writes results to the csv plot file consumable by Jenkins.
+
+ Arguments:
+ :param file_name: Name of the (csv) file to be created
+ Returns:
+ :return: none
+ """
+ first_line = ""
+ second_line = ""
+ f = open(file_name, "wt")
+ try:
+ for key in sorted(results):
+ first_line += key + ", "
+ second_line += str(results[key]) + ", "
+ first_line = first_line[:-2]
+ second_line = second_line[:-2]
+ f.write(first_line + "\n")
+ f.write(second_line + "\n")
+ logger.info("Message generator performance results stored in " +
+ file_name + ":")
+ logger.info(" " + first_line)
+ logger.info(" " + second_line)
+ finally:
+ f.close()
+
+ # Return pseudo-randomized (reproducible) index for selected range
+ def randomize_index(self, index, lowest=None, highest=None):
+ """Calculates pseudo-randomized index from selected range.
+
+ Arguments:
+ :param index: input index
+ :param lowest: the lowes index from the randomized area
+ :param highest: the highest index from the randomized area
+ Returns:
+ :return: the (pseudo)randomized index
+ Notes:
+ Created just as a fame for future generator enhancement.
+ """
+ # default values handling
+ # TODO optimize default values handling (use e.g. dicionary.update() approach)
+ if lowest is None:
+ lowest = self.randomize_lowest_default
+ if highest is None:
+ highest = self.randomize_highest_default
+ # randomize
+ if (index >= lowest) and (index <= highest):
+ # we are in the randomized range -> shuffle it inside
+ # the range (now just reverse the order)
+ new_index = highest - (index - lowest)
+ else:
+ # we are out of the randomized range -> nothing to do
+ new_index = index
+ return new_index
+
+ # Get list of prefixes
+ def get_prefix_list(self, slot_index, slot_size=None, prefix_base=None,
+ prefix_len=None, prefix_count=None, randomize=None):
+ """Generates list of IP address prefixes.
+
+ Arguments:
+ :param slot_index: index of group of prefix addresses
+ :param slot_size: size of group of prefix addresses
+ in [number of included prefixes]
+ :param prefix_base: IP address of the first prefix
+ (slot_index = 0, prefix_index = 0)
+ :param prefix_len: length of the prefix in bites
+ (the same as size of netmask)
+ :param prefix_count: number of prefixes to be returned
+ from the specified slot
+ Returns:
+ :return: list of generated IP address prefixes
+ """
+ # default values handling
+ # TODO optimize default values handling (use e.g. dicionary.update() approach)
+ if slot_size is None:
+ slot_size = self.slot_size_default
+ if prefix_base is None:
+ prefix_base = self.prefix_base_default
+ if prefix_len is None:
+ prefix_len = self.prefix_length_default
+ if prefix_count is None:
+ prefix_count = slot_size
+ if randomize is None:
+ randomize = self.randomize_updates_default
+ # generating list of prefixes
+ indexes = []
+ prefixes = []
+ prefix_gap = 2 ** (32 - prefix_len)
+ for i in range(prefix_count):
+ prefix_index = slot_index * slot_size + i
+ if randomize:
+ prefix_index = self.randomize_index(prefix_index)
+ indexes.append(prefix_index)
+ prefixes.append(prefix_base + prefix_index * prefix_gap)
+ if self.log_debug:
+ logger.debug(" Prefix slot index: " + str(slot_index))
+ logger.debug(" Prefix slot size: " + str(slot_size))
+ logger.debug(" Prefix count: " + str(prefix_count))
+ logger.debug(" Prefix indexes: " + str(indexes))
+ logger.debug(" Prefix list: " + str(prefixes))
+ return prefixes
+
+ def compose_update_message(self, prefix_count_to_add=None,
+ prefix_count_to_del=None):
+ """Composes an UPDATE message
+
+ Arguments:
+ :param prefix_count_to_add: # of prefixes to put into NLRI list
+ :param prefix_count_to_del: # of prefixes to put into WITHDRAWN list
+ Returns:
+ :return: encoded UPDATE message in HEX
+ Notes:
+ Optionally generates separate UPDATEs for NLRI and WITHDRAWN
+ lists or common message wich includes both prefix lists.
+ Updates global counters.
+ """
+ # default values handling
+ # TODO optimize default values handling (use e.g. dicionary.update() approach)
+ if prefix_count_to_add is None:
+ prefix_count_to_add = self.prefix_count_to_add_default
+ if prefix_count_to_del is None:
+ prefix_count_to_del = self.prefix_count_to_del_default
+ # logging
+ if self.log_info and not (self.iteration % 1000):
+ logger.info("Iteration: " + str(self.iteration) +
+ " - total remaining prefixes: " +
+ str(self.remaining_prefixes))
+ if self.log_debug:
+ logger.debug("#" * 10 + " Iteration: " +
+ str(self.iteration) + " " + "#" * 10)
+ logger.debug("Remaining prefixes: " +
+ str(self.remaining_prefixes))
+ # scenario type & one-shot counter
+ straightforward_scenario = (self.remaining_prefixes >
+ self.remaining_prefixes_threshold)
+ if straightforward_scenario:
+ prefix_count_to_del = 0
+ if self.log_debug:
+ logger.debug("--- STARAIGHTFORWARD SCENARIO ---")
+ if not self.phase1_start_time:
+ self.phase1_start_time = time.time()
+ else:
+ if self.log_debug:
+ logger.debug("--- COMBINED SCENARIO ---")
+ if not self.phase2_start_time:
+ self.phase2_start_time = time.time()
+ # tailor the number of prefixes if needed
+ prefix_count_to_add = (prefix_count_to_del +
+ min(prefix_count_to_add - prefix_count_to_del,
+ self.remaining_prefixes))
+ # prefix slots selection for insertion and withdrawal
+ slot_index_to_add = self.iteration
+ slot_index_to_del = slot_index_to_add - self.slot_gap_default
+ # getting lists of prefixes for insertion in this iteration
+ if self.log_debug:
+ logger.debug("Prefixes to be inserted in this iteration:")
+ prefix_list_to_add = self.get_prefix_list(slot_index_to_add,
+ prefix_count=prefix_count_to_add)
+ # getting lists of prefixes for withdrawal in this iteration
+ if self.log_debug:
+ logger.debug("Prefixes to be withdrawn in this iteration:")
+ prefix_list_to_del = self.get_prefix_list(slot_index_to_del,
+ prefix_count=prefix_count_to_del)
+ # generating the mesage
+ if self.single_update_default:
+ # Send prefixes to be introduced and withdrawn
+ # in one UPDATE message
+ msg_out = self.update_message(wr_prefixes=prefix_list_to_del,
+ nlri_prefixes=prefix_list_to_add)
+ else:
+ # Send prefixes to be introduced and withdrawn
+ # in separate UPDATE messages (if needed)
+ msg_out = self.update_message(wr_prefixes=[],
+ nlri_prefixes=prefix_list_to_add)
+ if prefix_count_to_del:
+ msg_out += self.update_message(wr_prefixes=prefix_list_to_del,
+ nlri_prefixes=[])
+ # updating counters - who knows ... maybe I am last time here ;)
+ if straightforward_scenario:
+ self.phase1_stop_time = time.time()
+ self.phase1_updates_sent = self.updates_sent
+ else:
+ self.phase2_stop_time = time.time()
+ self.phase2_updates_sent = (self.updates_sent -
+ self.phase1_updates_sent)
+ # updating totals for the next iteration
+ self.iteration += 1
+ self.remaining_prefixes -= (prefix_count_to_add - prefix_count_to_del)
+ # returning the encoded message
+ return msg_out
+
+ # Section of message encoders
+
+ def open_message(self, version=None, my_autonomous_system=None,
+ hold_time=None, bgp_identifier=None):
+ """Generates an OPEN Message (rfc4271#section-4.2)
+
+ Arguments:
+ :param version: see the rfc4271#section-4.2
+ :param my_autonomous_system: see the rfc4271#section-4.2
+ :param hold_time: see the rfc4271#section-4.2
+ :param bgp_identifier: see the rfc4271#section-4.2
+ Returns:
+ :return: encoded OPEN message in HEX
+ """
+
+ # default values handling
+ # TODO optimize default values handling (use e.g. dicionary.update() approach)
+ if version is None:
+ version = self.version_default
+ if my_autonomous_system is None:
+ my_autonomous_system = self.my_autonomous_system_default
+ if hold_time is None:
+ hold_time = self.hold_time_default
+ if bgp_identifier is None:
+ bgp_identifier = self.bgp_identifier_default
+
+ # Marker
+ marker_hex = "\xFF" * 16
+
+ # Type
+ type = 1
+ type_hex = struct.pack("B", type)
+
+ # version
+ version_hex = struct.pack("B", version)
+
+ # my_autonomous_system
+ # AS_TRANS value, 23456 decadic.
+ my_autonomous_system_2_bytes = 23456
+ # AS number is mappable to 2 bytes
+ if my_autonomous_system < 65536:
+ my_autonomous_system_2_bytes = my_autonomous_system
+ my_autonomous_system_hex_2_bytes = struct.pack(">H",
+ my_autonomous_system)
+
+ # Hold Time
+ hold_time_hex = struct.pack(">H", hold_time)
+
+ # BGP Identifier
+ bgp_identifier_hex = struct.pack(">I", bgp_identifier)
+
+ # Optional Parameters
+ optional_parameters_hex = ""
+ if self.rfc4760:
+ optional_parameter_hex = (
+ "\x02" # Param type ("Capability Ad")
+ "\x06" # Length (6 bytes)
+ "\x01" # Capability type (NLRI Unicast),
+ # see RFC 4760, secton 8
+ "\x04" # Capability value length
+ "\x00\x01" # AFI (Ipv4)
+ "\x00" # (reserved)
+ "\x01" # SAFI (Unicast)
+ )
+ optional_parameters_hex += optional_parameter_hex
+
+ optional_parameter_hex = (