Esempio n. 1
0
    def _setUpClass(cls):
        for prefix_name in cls.DATA:
            prefix = cls.DATA[prefix_name]
            net = IPNetwork(prefix) if "/" in prefix else IPAddress(prefix)
            if str(net) != prefix:
                raise ValueError(
                    "Prefix '{}' is not represented in its canonical form: "
                    "'{}' used, '{}' expected.".format(prefix_name, prefix,
                                                       str(net)))

        cls.info("{}: setting instances up...".format(cls.SHORT_DESCR))

        MockedEnv(cls,
                  base_dir=cls._get_module_dir(),
                  peering_db=cls.MOCK_PEERING_DB,
                  ripe_rpki_cache=cls.MOCK_RIPE_RPKI_CACHE,
                  irrdb=cls.MOCK_IRRDB,
                  rttgetter=cls.MOCK_RTTGETTER,
                  arin_db_dump=cls.MOCK_ARIN_DB_DUMP)

        cls.rs_cfg_file_path = None

        try:
            cls._setup_instances()
        except:
            cls.tearDownClass()
            raise

        if "BUILD_ONLY" in os.environ or \
            (cls.SKIP_ON_TRAVIS and "TRAVIS" in os.environ):
            cls.debug("Skipping starting instances")
            return

        try:
            for instance in cls.INSTANCES:
                instance.set_var_dir("{}/var".format(cls._get_module_dir()))

                if cls._do_not_stop_instances() and instance.is_running():
                    cls.debug(
                        "Instance '{}' already running, reloading config".
                        format(instance.name))
                    if not instance.reload_config():
                        raise InstanceError(
                            "An error occurred while reloading '{}' configuration."
                            .format(instance.name))
                    continue

                cls.debug("Starting instance '{}'...".format(instance.name))
                instance.start()
        except Exception as e:
            try:
                cls.tearDownClass()
            except:
                pass
            raise e
Esempio n. 2
0
    def receive_route(self,
                      inst,
                      prefix,
                      other_inst=None,
                      as_path=None,
                      next_hop=None,
                      std_comms=None,
                      lrg_comms=None,
                      ext_comms=None,
                      local_pref=None,
                      filtered=None,
                      only_best=None,
                      reject_reason=None):
        """Test if the BGP speaker receives the expected route(s).

        If no routes matching the given criteria are found, the
        ``TestCase.fail()`` method is called and the test fails.

        Args:
            inst: the :class:`BGPSpeakerInstance` instance where the routes
                are searched on.

            prefix (str): the IPv4/IPv6 prefix of the routes to search for.

            other_inst: if given, only routes received from this
                :class:`BGPSpeakerInstance` instance are considered.

            as_path (str): if given, only routes with this AS_PATH are
                considered.

            next_hop: can be a string or a :class:`BGPSpeakerInstance`
                instance; if given, only routes that have a NEXT_HOP
                address matching this one are considered.

            std_comms, lrg_comms, ext_comms (list): if given, only routes
                that carry these BGP communities are considered. Use an
                empty list ([]) to consider only routes with no BGP comms.

            local_pref (int): if given, only routes with local-pref equal
                to this value are considered.

            filtered (bool): if given, only routes that have been (not)
                filtered are considered.

            only_best (bool): if given, only best routes are considered.

            reject_reason (int): valid only if `filtered` is True: if given
                the route must be reject with this reason code.
                It can be also a set of codes: in this case, the route must
                be rejected with one of those codes.

                The list of valid codes is reported in docs/CONFIG.rst or at
                https://arouteserver.readthedocs.io/en/latest/CONFIG.html#reject-policy
        """
        assert isinstance(inst, BGPSpeakerInstance), \
            "inst must be of class BGPSpeakerInstance"

        try:
            IPNetwork(prefix)
        except:
            raise AssertionError("prefix must be a valid IPv4/IPv6 prefix")

        if other_inst:
            assert isinstance(other_inst, BGPSpeakerInstance), \
                "other_inst must be of class BGPSpeakerInstance"

        if as_path:
            try:
                if not isinstance(as_path, str):
                    raise AssertionError()
                for asn in as_path.split(" "):
                    assert asn.strip().isdigit()
            except:
                raise AssertionError("as_path must be a string in the format "
                                     "'<asn1> <asn2>' with <asnX> positive "
                                     "integers")

        next_hop_ip = None
        if next_hop:
            assert isinstance(next_hop, (str, BGPSpeakerInstance)), \
                ("next_hop must be a string representing one IP address or "
                 "a BGPSpeakerInstance object")
            next_hop_ip = next_hop if isinstance(next_hop,
                                                 str) else next_hop.ip
            try:
                IPAddress(next_hop_ip)
            except:
                raise AssertionError(
                    "Invalid next_hop IP address: {}".format(next_hop_ip))

        if std_comms:
            assert isinstance(std_comms, list), \
                ("std_comms must be a list of strings representing "
                 "BGP standard communities")

        if ext_comms:
            assert isinstance(ext_comms, list), \
                ("ext_comms must be a list of strings representing "
                 "BGP extended communities")

        if lrg_comms:
            assert isinstance(lrg_comms, list), \
                ("lrg_comms must be a list of strings representing "
                 "BGP large communities")

        if local_pref:
            assert isinstance(local_pref, int), \
                "local_pref must be an integer >= 0"
            assert local_pref >= 0, \
                "local_pref must be an integer >= 0"

        if reject_reason is not None and not filtered:
            raise AssertionError(
                "reject_reason can be set only if filtered is True")

        reject_reasons = None
        if reject_reason is not None:
            if isinstance(reject_reason, int):
                reject_reasons = [reject_reason]
            else:
                reject_reasons = list(reject_reason)
            for code in reject_reasons:
                assert code in range(1, 15), "invalid reject_reason"

        include_filtered = filtered if filtered is not None else False
        best_only = only_best if only_best is not None else False

        routes = inst.get_routes(prefix,
                                 include_filtered=include_filtered,
                                 only_best=best_only)
        self.process_reject_cause_routes(routes)

        errors = []
        if not routes:
            errors.append("{inst} does not receive {prefix} at all.")
        else:
            for route in routes:
                err = False
                if other_inst and route.via != other_inst.ip:
                    errors.append(
                        "{{inst}} receives {{prefix}} from {via} and not from {{other_inst}}."
                        .format(via=route.via))
                    err = True
                if as_path and route.as_path != as_path:
                    errors.append(
                        "{{inst}} receives {{prefix}} with AS_PATH {as_path} and not with {{as_path}}."
                        .format(as_path=route.as_path))
                    err = True
                if next_hop_ip and route.next_hop != next_hop_ip:
                    errors.append(
                        "{{inst}} receives {{prefix}} with NEXT_HOP {next_hop} and not with {{next_hop_ip}}."
                        .format(next_hop=route.next_hop))
                    err = True
                if std_comms is not None and sorted(
                        route.std_comms) != sorted(std_comms):
                    errors.append(
                        "{{inst}} receives {{prefix}} with std comms {comms} and not with {{std_comms}}."
                        .format(comms=route.std_comms))
                    err = True
                if lrg_comms is not None and sorted(
                        route.lrg_comms) != sorted(lrg_comms):
                    errors.append(
                        "{{inst}} receives {{prefix}} with lrg comms {comms} and not with {{lrg_comms}}."
                        .format(comms=route.lrg_comms))
                    err = True
                if ext_comms is not None and sorted(
                        route.ext_comms) != sorted(ext_comms):
                    errors.append(
                        "{{inst}} receives {{prefix}} with ext comms {comms} and not with {{ext_comms}}."
                        .format(comms=route.ext_comms))
                    err = True
                if local_pref is not None and route.localpref != local_pref:
                    errors.append(
                        "{{inst}} receives {{prefix}} with local-pref {local_pref} and not with {{local_pref}}."
                        .format(local_pref=route.localpref))
                    err = True
                if filtered is not None and route.filtered != filtered:
                    errors.append(
                        "{{inst}} receives {{prefix}} from {via}, AS_PATH {as_path}, NEXT_HOP {next_hop} "
                        "but it is {filtered_status} while it is expected to be {filtered_exp}."
                        .format(via=route.via,
                                as_path=route.as_path,
                                next_hop=route.next_hop,
                                filtered_status="filtered"
                                if route.filtered else "not filtered",
                                filtered_exp="filtered"
                                if filtered else "not filtered"))
                    err = True
                if filtered is True and route.filtered and \
                    reject_reasons is not None and len(route.reject_reasons) > 0:

                    reject_reason_found = False
                    for real_reason in route.reject_reasons:
                        if real_reason in reject_reasons:
                            reject_reason_found = True

                    if not reject_reason_found:
                        if len(reject_reasons) == 1:
                            exp_reason = reject_reasons[0]
                        else:
                            exp_reason = "one of {}".format(", ".join(
                                map(str, reject_reasons)))

                        errors.append(
                            "{{inst}} receives {{prefix}} from {via}, AS_PATH {as_path}, NEXT_HOP {next_hop}, "
                            "it is filtered but reject reasons don't match: real reasons {reason}, "
                            "expected reason {exp_reason}.".format(
                                via=route.via,
                                as_path=route.as_path,
                                next_hop=route.next_hop,
                                reason=", ".join(map(str,
                                                     route.reject_reasons)),
                                exp_reason=exp_reason))
                        err = True
                if not err:
                    return route

        if errors:
            criteria = []
            if other_inst:
                criteria.append("from {} ({})".format(other_inst.ip,
                                                      other_inst.name))
            if as_path:
                criteria.append("with AS_PATH {}".format(as_path))
            if next_hop_ip:
                criteria.append("with next-hop {}".format(next_hop_ip))
            if std_comms:
                criteria.append("with std comms {}".format(std_comms))
            if lrg_comms:
                criteria.append("with lrg comms {}".format(lrg_comms))
            if ext_comms:
                criteria.append("with ext comms {}".format(ext_comms))
            if local_pref:
                criteria.append("with local-pref {}".format(local_pref))
            if filtered is True:
                criteria.append("filtered")
            if reject_reasons:
                if len(reject_reasons) == 1:
                    criteria.append("with reject reason {}".format(
                        reject_reasons[0]))
                else:
                    criteria.append("with reject reason in {}".format(
                        ", ".join(map(str, reject_reasons))))

            failure = "Routes not found.\n"
            failure += "Looking for {prefix} on {inst} {criteria}:\n\t".format(
                prefix=prefix, inst=inst.name, criteria=", ".join(criteria))
            failure += "\n\t".join([
                err_msg.format(
                    inst=inst.name,
                    prefix=prefix,
                    other_inst="{} ({})".format(other_inst.ip, other_inst.name)
                    if other_inst else "",
                    as_path=as_path,
                    next_hop_ip=next_hop_ip,
                    std_comms=std_comms,
                    lrg_comms=lrg_comms,
                    ext_comms=ext_comms,
                    local_pref=local_pref,
                ) for err_msg in errors
            ])
            failure += self._instance_log_contains_errors_warning(inst)
            self.fail(failure)