예제 #1
0
    def test_addr_assignment(self):
        """Test correct assignment of Docker host addresses to border router interfaces."""
        net = HostNetwork("host_network", ipaddress.ip_network("10.0.0.0/24"))
        hosts = [LocalHost(), LocalHost()]
        asys = [AS(host, False) for host in hosts]

        net.set_host_ip(hosts[0], ipaddress.ip_address("10.0.0.10"))
        net.set_host_ip(hosts[1], ipaddress.ip_address("10.0.0.11"))

        with self.assertRaises(errors.NotAvailable):
            net.assign_br_address(ISD_AS("1-ff00:0:000"),
                                  asys[0],
                                  IfId(1),
                                  pref_ip=ipaddress.ip_address("10.0.0.2"))

        ip, port = net.assign_br_address(ISD_AS("1-ff00:0:000"), asys[0],
                                         IfId(1))
        self.assertEqual(ip, ipaddress.ip_address("10.0.0.10"))
        self.assertEqual(port, L4Port(50000))

        ip, port = net.assign_br_address(ISD_AS("1-ff00:0:001"), asys[1],
                                         IfId(1))
        self.assertEqual(ip, ipaddress.ip_address("10.0.0.11"))
        self.assertEqual(port, L4Port(50000))

        ip, port = net.assign_br_address(ISD_AS("1-ff00:0:001"), asys[1],
                                         IfId(2))
        self.assertEqual(ip, ipaddress.ip_address("10.0.0.11"))
        self.assertEqual(port, L4Port(50001))
예제 #2
0
def update(args):
    """Fetch the AS configuration files from the coordinator."""
    open_log_file(args.workdir)
    log.debug("Command: update")
    topo = Topology.load(args.workdir.joinpath(CONFIG_DATA_FILE))
    try:
        if topo.coordinator is None:
            log.error("Topology has no coordinator.")
        else:
            if args.as_list is None:
                fetch_config(topo,
                             as_selector=args.as_pattern,
                             detach=args.detach,
                             force=args.force,
                             no_restart=args.no_restart,
                             rate=args.rate)
            else:
                as_list = []
                with open(args.as_list) as file:
                    for line in file.readlines():
                        as_list.append(ISD_AS(line))
                fetch_config(topo,
                             as_selector=as_list,
                             detach=args.detach,
                             force=args.force,
                             no_restart=args.no_restart,
                             rate=args.rate)
    finally:
        topo.close_host_connections()
예제 #3
0
def _modify_topo(topo: Topology, isd_as: ISD_AS, asys: AS,
    mod_func: Callable[[Mapping[str, Any]], None],
    workdir: Path, dc: docker.DockerClient):
    """Modify the configuration of the given AS.

    The actual modifications are made by `mod_func`. If the AS is running, it is stopped first and
    restarted after `mod_func` returns.

    :param topo: Topology
    :param isd_as: AS to modify.
    :param asys: AS to modify.
    :param mod_func: Function that performs the  desired modifications by altering the topology.json
                     file it is given in form of a mapping.
    :param workdir: Topology working directory.
    :param dc: Docker client object connected to the Docker daemon.
    """
    restart = topo.is_scion_running(isd_as, asys)
    if restart: # stop SCION in the affected AS
        log.info("Stopping SCION in AS{}.".format(isd_as))
        topo.stop_scion_asys(isd_as, asys)

    modify_as_topo_file(workdir.joinpath(isd_as.file_fmt(), "gen"), isd_as, mod_func)

    if restart: # restart SCION
        log.info("Starting SCION in AS{}.".format(isd_as))
        topo.run_scion_asys(isd_as, asys)
예제 #4
0
    def test_ip_addr_assignment(self):
        br = DockerBridge("test", LocalHost(),
                          ipaddress.IPv4Network("10.0.0.0/29"))
        asys = AS(LocalHost(), False)

        # Assign coordinator IP
        for _ in range(2):  # Multiple calls must return the same address
            ip = br.assign_ip_address("coordinator")
            self.assertEqual(ip, ipaddress.IPv4Address(0x0A000002))

        # Assign AS IPs
        for asys in range(1, 5):
            ip = br.assign_ip_address(ISD_AS(asys))
            self.assertEqual(ip, ipaddress.IPv4Address(0x0A000000 + asys + 2))

        with self.assertRaises(errors.OutOfResources):
            br.assign_ip_address(ISD_AS(8))

        # Retrive assigned addresses
        self.assertEqual(br.get_ip_address("coordinator"),
                         ipaddress.IPv4Address(0x0A000002))
        for asys in range(1, 5):
            ip = br.get_ip_address(ISD_AS(asys))
            self.assertEqual(ip, ipaddress.IPv4Address(0x0A000000 + asys + 2))

        # Free and reassign an addresses
        ip = br.get_ip_address(ISD_AS(2))
        self.assertEqual(br.free_ip_address(ISD_AS(2)), 0)
        self.assertIsNone(br.get_ip_address(ISD_AS(2)))
        self.assertEqual(br.assign_ip_address(ISD_AS(6)), ip)
예제 #5
0
    def get_name(self, isd_as: ISD_AS) -> str:
        """Construct the name of the border router as found in topology.json files.

        :param isd_as: AS the border router belongs to.

        Example:
        BorderRouter(1).get_name(ISD_AS("1-ff00_0_110")) == 'br1-ff00_0_110-1'
        """
        return "br{}-{}".format(isd_as.file_fmt(), self.id)
예제 #6
0
 def get_br_prom_ports(self, isd_as: ISD_AS) -> List[L4Port]:
     """Get the Prometheus endpoint ports of all border routers in the given AS."""
     ports = io.StringIO()
     cntr = self.get_django_container()
     user = const.SCIONLAB_USER_DEBUG if self.debug else const.SCIONLAB_USER_PRODUCTION
     cmd = "./manage.py runscript print_prom_ports --script-args %s" % isd_as.as_str(
     )
     run_cmd_in_cntr(cntr, user, cmd, output=ports, check=True)
     return [L4Port(int(port)) for port in ports.getvalue().split()]
예제 #7
0
def _parse_api_secrets(input: str) -> Dict[ISD_AS, ApiCredentials]:
    """Parse the output of the 'print_api_secrets.py' script running in context of the coordinator.
    """
    output = {}

    for line in input.splitlines():
        isd_as_str, uid, secret = line.split()
        output[ISD_AS(isd_as_str)] = ApiCredentials(uid, secret)

    return output
예제 #8
0
def policy(args):
    """Wrapper around the coordinator's peering policy REST API."""
    open_log_file(args.workdir)
    log.debug("Command: policy %s" % args.action)
    topo = Topology.load(args.workdir.joinpath(CONFIG_DATA_FILE))

    # Parse and validate ISD-AS string.
    try:
        isd_as = ISD_AS(args.isd_as)
    except SCIONParseError:
        log.error("Invalid ISD-AS string.")
        return
    try:
        if not topo.ases[isd_as].is_user_as():
            log.error("'%s' is not a user AS.", isd_as)
            return
    except KeyError:
        log.error("Unknown AS: %s", isd_as)
        return

    try:
        if topo.coordinator is None:
            log.error("Topology has no coordinator.")
        else:
            response = None
            if args.action == "get_peers":
                response = topo.coordinator.get_peers(isd_as, args.ixp)
            elif args.action == "get":
                response = topo.coordinator.get_policies(isd_as, args.ixp)
            elif args.action == "create":
                policies = sys.stdin.read() if args.data is None else args.data
                print(topo.coordinator.create_policies(isd_as, policies))
            elif args.action == "delete":
                policies = sys.stdin.read() if args.data is None else args.data
                print(topo.coordinator.delete_policies(isd_as, policies))

            if response is not None:
                print(json.dumps(response, indent=2))

    finally:
        topo.close_host_connections()
예제 #9
0
    def test_br_addr_assignment(self):
        """Test assignment of (IP, port) tuples to border router interfaces."""
        br = DockerBridge("test", LocalHost(),
                          ipaddress.IPv4Network("10.0.0.0/29"))
        asys = AS(LocalHost(), False)

        # Assign BR interface addresses
        for _ in range(2):  # Multiple calls must return the same addresses
            for ifid in range(1, 3):
                for as_id in range(1, 6):
                    ip, port = br.assign_br_address(ISD_AS(as_id), asys,
                                                    IfId(ifid))
                    self.assertEqual(
                        ip, ipaddress.IPv4Address(0x0A000000 + as_id + 1))
                    self.assertEqual(port, 50000 + ifid - 1)

        with self.assertRaises(errors.OutOfResources):
            br.assign_br_address(ISD_AS(8), asys, IfId(1))

        # AS IP assignment
        for as_id in range(1, 6):
            isd_as = ISD_AS(as_id)
            self.assertEqual(br.get_ip_address(isd_as),
                             br.assign_ip_address(isd_as))

        # Retrieve BR interface underlay addresses
        for ifid in range(1, 3):
            for asys in range(1, 6):
                ip, port = unwrap(br.get_br_address(ISD_AS(asys), IfId(ifid)))
                self.assertEqual(ip,
                                 ipaddress.IPv4Address(0x0A000000 + asys + 1))
                self.assertEqual(port, 50000 + ifid - 1)

        # Free BR interfaces
        for asys in range(1, 6):
            for ifid in range(1, 3):
                self.assertEqual(br.free_br_address(ISD_AS(asys), IfId(ifid)),
                                 3 - ifid)

        # Check whether IPs are still bound
        for asys in range(1, 6):
            ip = br.get_ip_address(ISD_AS(asys))
            self.assertEqual(ip, ipaddress.IPv4Address(0x0A000000 + asys + 1))
예제 #10
0
    def test(self):
        """Test successful address assignment."""
        topo = self.topo
        assign_underlay_addresses(topo)

        self.assertEqual(len(topo.bridges), 4)

        # link 1
        net = topo.get_bridge_subnet(ipaddress.ip_network("10.0.10.0/29"))
        self.assertIsInstance(net, DockerBridge)
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:110"), IfId(1)),
            UnderlayAddress(ipaddress.ip_address("10.0.10.2"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:111"), IfId(1)),
            UnderlayAddress(ipaddress.ip_address("10.0.10.3"), L4Port(50000)))

        # link 2
        net = topo.get_bridge_subnet(ipaddress.ip_network("10.0.11.0/29"))
        self.assertIsInstance(net, DockerBridge)
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:110"), IfId(2)),
            UnderlayAddress(ipaddress.ip_address("10.0.11.2"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:112"), IfId(1)),
            UnderlayAddress(ipaddress.ip_address("10.0.11.3"), L4Port(50000)))

        # links 3 to 5
        net = topo.get_bridge_subnet(ipaddress.ip_network("10.0.20.0/24"))
        self.assertIsInstance(net, OvsBridge)
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:111"), IfId(2)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.2"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:111"), IfId(3)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.2"), L4Port(50001)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:112"), IfId(2)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.3"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:112"), IfId(3)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.3"), L4Port(50001)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:113"), IfId(1)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.4"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:113"), IfId(2)),
            UnderlayAddress(ipaddress.ip_address("10.0.20.4"), L4Port(50001)))

        # link 6
        net = topo.get_bridge_subnet(ipaddress.ip_network("10.0.21.0/24"))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:112"), IfId(4)),
            UnderlayAddress(ipaddress.ip_address("10.0.21.9"), L4Port(50000)))
        self.assertEqual(
            net.get_br_address(ISD_AS("1-ff00:0:113"), IfId(3)),
            UnderlayAddress(ipaddress.ip_address("10.0.21.10"), L4Port(50000)))
예제 #11
0
    def test(self):
        """Test successful parse."""
        topo_file = yaml.safe_load(TEST_TOPO)
        topo = extract_topo_info(topo_file)
        ASES = [
            ISD_AS("1-ff00:0:110"),
            ISD_AS("1-ff00:0:111"),
            ISD_AS("1-ff00:0:112")
        ]

        self.assertIsNone(topo.coordinator)
        self.assertEqual(len(topo.additional_services), 0)

        # IPv6
        self.assertFalse(topo.ipv6_enabled)

        # Default link subnet
        self.assertEqual(topo.default_link_subnet,
                         ipaddress.ip_network("10.0.10.0/24"))

        # ASes
        self.assertEqual(len(topo.ases), 3)
        for asys in ASES:
            self.assertIn(asys, topo.ases)

        # IXPs
        self.assertIn("ixp1", topo.ixps)
        ixp = topo.ixps["ixp1"]
        for isd_as in [ISD_AS("1-ff00:0:111"), ISD_AS("1-ff00:0:112")]:
            self.assertIn(isd_as, ixp.ases)
            self.assertIs(ixp.ases[isd_as], topo.ases[isd_as])

        # Networks
        self.assertEqual(len(topo.bridges), 2)
        ip_net = ipaddress.ip_network("10.0.11.0/29")
        self.assertIsInstance(topo.get_bridge_subnet(ip_net), DockerBridge)
        ip_net = ipaddress.ip_network("10.0.20.0/24")
        self.assertIsInstance(topo.get_bridge_subnet(ip_net), OvsBridge)

        # Links
        self.assertEqual(len(topo.links), 3)
        self.assertEqual(topo.links[0].ep_a, LinkEp("1-ff00:0:110#1"))
        self.assertEqual(topo.links[0].ep_b, LinkEp("1-ff00:0:111#1"))
        self.assertIsNone(topo.links[0].bridge)
        self.assertEqual(topo.links[1].ep_a, LinkEp("1-ff00:0:110#2"))
        self.assertEqual(topo.links[1].ep_b, LinkEp("1-ff00:0:112#1"))
        self.assertEqual(
            topo.links[1].bridge,
            topo.get_bridge_subnet(ipaddress.ip_network("10.0.11.0/29")))
        self.assertEqual(topo.links[2].ep_a, LinkEp("1-ff00:0:111#2"))
        self.assertEqual(topo.links[2].ep_b, LinkEp("1-ff00:0:112#2"))
        self.assertEqual(
            topo.links[2].bridge,
            topo.get_bridge_subnet(ipaddress.ip_network("10.0.20.0/24")))

        # BRs
        self.assertEqual(len(topo.ases[ASES[0]].border_routers), 2)
        self.assertEqual(len(topo.ases[ASES[1]].border_routers), 2)
        self.assertEqual(len(topo.ases[ASES[2]].border_routers), 1)

        # br1-ff00_0_110-1
        br = topo.ases[ASES[0]].border_routers[0]
        self.assertEqual(br.id, 1)
        self.assertEqual(br.get_name(ASES[0]), "br1-ff00_0_110-1")
        self.assertEqual(len(br.links), 1)
        self.assertIs(br.links[IfId(1)], topo.links[0])

        # br1-ff00_0_110-2
        br = topo.ases[ASES[0]].border_routers[1]
        self.assertEqual(br.id, 2)
        self.assertEqual(br.get_name(ASES[0]), "br1-ff00_0_110-2")
        self.assertEqual(len(br.links), 1)
        self.assertIs(br.links[IfId(2)], topo.links[1])

        # br1-ff00_0_111-1
        br = topo.ases[ASES[1]].border_routers[0]
        self.assertEqual(br.id, 1)
        self.assertEqual(br.get_name(ASES[1]), "br1-ff00_0_111-1")
        self.assertEqual(len(br.links), 1)
        self.assertIs(br.links[IfId(1)], topo.links[0])

        # br1-ff00_0_111-2
        br = topo.ases[ASES[1]].border_routers[1]
        self.assertEqual(br.id, 2)
        self.assertEqual(br.get_name(ASES[1]), "br1-ff00_0_111-2")
        self.assertEqual(len(br.links), 1)
        self.assertIs(br.links[IfId(2)], topo.links[2])

        # br1-ff00_0_112-1
        br = topo.ases[ASES[2]].border_routers[0]
        self.assertEqual(br.id, 1)
        self.assertEqual(br.get_name(ASES[2]), "br1-ff00_0_112-1")
        self.assertEqual(len(br.links), 2)
        self.assertIs(br.links[IfId(1)], topo.links[1])
        self.assertIs(br.links[IfId(2)], topo.links[2])

        # topo_file
        self.assertNotIn("link_subnet", topo_file['defaults'])
        self.assertNotIn("IXPs", topo_file)
        for link in topo_file['links']:
            self.assertNotIn('network', link)
예제 #12
0
    def _start_container(self, isd_as: ISD_AS, asys: AS, workdir: Path,
                         sc: Path) -> None:
        """Start the Docker container hosting the given AS and connect it to the necessary bridges.
        """
        dc = asys.host.docker_client

        # Check if container is already running
        if asys.container_id:
            try:
                cntr = dc.containers.get(asys.container_id)
            except docker.errors.NotFound:
                # container has been removed
                asys.container_id = None
            else:
                if cntr.status == "running":
                    return  # container is already running
                elif cntr.status == 'paused':
                    cntr.unpause()
                    log.info("Unpaused container %s [%s] (%s).", cntr.name,
                             asys.host.name, cntr.id)
                    return
                else:
                    if self._restart_container(cntr, isd_as, asys):
                        return  # restart successful

        # Create and start a new container
        cntr_name = self.get_cntr_name(isd_as)
        ports = get_published_ports(isd_as, asys)
        for cntr_port, (host_ip, host_port) in ports.items():
            log.info("Exposing port %s of %s on %s:%s [%s].", cntr_port,
                     cntr_name, host_ip, host_port, asys.host.name)

        cntr = None
        if asys.host.is_local:
            mount_dir = workdir.joinpath(isd_as.file_fmt()).resolve()
            if self.coordinator is not None:
                # Starting a new instance of the coordinator generates new configuration files,
                # certificates, etc. If there are configuration or cache files from a previous run,
                # we remove them here.
                shutil.rmtree(mount_dir.joinpath("gen"), ignore_errors=True)
                shutil.rmtree(mount_dir.joinpath("gen-cache"),
                              ignore_errors=True)

            kwargs = {}
            if not asys.cpu_affinity.is_unrestricted():
                kwargs['cpuset_cpus'] = str(asys.cpu_affinity)
            cntr = start_scion_cntr(dc,
                                    const.AS_IMG_NAME,
                                    cntr_name=cntr_name,
                                    mount_dir=mount_dir,
                                    ports=ports,
                                    extra_args=kwargs)
            asys.container_id = cntr.id

        else:  # Start container on a remote host
            kwargs = {}
            if not asys.cpu_affinity.is_unrestricted():
                kwargs['cpuset_cpus'] = str(asys.cpu_affinity)
            cntr = dc.containers.run(
                const.AS_IMG_NAME,
                name=cntr_name,
                tty=True,  # keep the container running
                detach=True,
                ports=ports,
                **kwargs)
            asys.container_id = cntr.id

        log.info("Started container %s [%s] with ID %s.", cntr_name,
                 asys.host.name, asys.container_id)

        if self.coordinator is None:
            # If the coordinator creates the gen folder, 'gen-certs.sh' is invoked by
            # 'scionlab-config-user'.
            # If the topology is generated by 'scion.sh topology', we create the certificates
            # now.
            run_cmd_in_cntr(cntr,
                            const.SCION_USER,
                            "./gen-certs.sh",
                            check=True)
        else:
            # Connect the new container to the coordinator.
            self.coordinator.bridge.connect(isd_as, asys)
            if asys.is_attachment_point or self.coordinator.ssh_management:
                # Allow the coordinator to access the container via SSH.
                self._authorize_coord_ssh_key(cntr, workdir)
                self._start_sshd(cntr)

        # Connect bridges SCION links.
        connect_bridges(isd_as, asys)
예제 #13
0
 def get_cntr_name(self, isd_as: ISD_AS) -> str:
     """Returns the name of the container for the given AS."""
     return self.get_name_prefix() + isd_as.file_fmt()
예제 #14
0
def extract_topo_info(topo_file: MutableMapping[str, Any],
                      name: Optional[str] = None) -> Topology:
    """Initialize a Topology object with information read from a topology definition.

    Interface identifiers not specified in the input file are automatically assigned and added to
    the returned Topology object and to `topo_file`.

    :param topo_file: The input topology file parsed into a dictionary. When the function returns,
                      the IXP testbed specific entries have been removed.
    :param name: An optional name for the topology. This name is added to all containers, network
                 bridges, etc. to distinguish them from other testbed instances.
    :returns: Extracted topology information.
    :raises InvalidTopo: The topology file is invalid.
    """
    topo = Topology(name)
    networks = NetworkFactory()
    brs = BrFactory()
    ifids = IfIdMapping(topo_file)

    # Subnet for automatically generated local docker bridges
    if 'link_subnet' in topo_file.get('defaults', {}):
        topo.default_link_subnet = ipaddress.ip_network(
            topo_file['defaults'].pop("link_subnet"))
        topo.ipv6_enabled |= (topo.default_link_subnet.version == 6)
    else:
        topo.default_link_subnet = None

    # Hosts (first pass: create host objects)
    localhost = topo.hosts['localhost'] = LocalHost()  # always exists
    for host_name, host_def in topo_file.get('hosts', {}).items():
        if host_name != 'localhost':
            if host_name in topo.hosts:
                log.error("Multiple hosts with name '%s'.", host_name)
                raise errors.InvalidTopo()

            if not 'coordinator' in topo_file:
                log.error(
                    "Running a topology spanning multiple hosts requires a coordinator."
                )
                raise errors.InvalidTopo()

            topo.hosts[host_name] = RemoteHost(
                host_name,
                _get_ip(host_def, 'ssh_host', host_name),
                _get_value(host_def, 'username', host_name),
                identity_file=host_def.get("identity_file"),
                ssh_port=L4Port(int(host_def.get('ssh_port', 22))))

    # Networks
    if 'networks' in topo_file:
        net_defs = topo_file.pop('networks')  # remove networks section
        for net_name, net_def in net_defs.items():
            type = _get_value(net_def, 'type', net_name)
            subnet = _get_value(net_def, 'subnet', net_name)
            host = topo.hosts[net_def.get('host', 'localhost')]
            networks.create(net_name, topo.get_name_prefix(), type, host,
                            subnet, net_def)

    # Hosts (second pass: parse network addresses for host networks)
    for host_name, host_def in topo_file.get('hosts', {}).items():
        for net, addr in host_def.get('addresses', {}).items():
            networks.set_host_ip(net, topo.hosts[host_name], addr)
    topo_file.pop('hosts', None)  # remove host section

    # Coordinator
    if 'coordinator' in topo_file:
        coord_def = topo_file.pop('coordinator')  # remove coordinator section
        host = topo.hosts[coord_def.get('host', 'localhost')]
        def_name = lambda: topo.get_name_prefix() + const.COORD_NET_NAME
        bridge = networks.get(_get_value(coord_def, 'network', 'coordinator'),
                              def_name, localhost)
        cpu_affinity = CpuSet(coord_def.get('cpu_affinity'))
        ssh_management = coord_def.get('ssh_management', False)

        debug = coord_def.get('debug', True)
        compose_path = None
        if debug:
            if ssh_management:
                log.warning(
                    "Coordinator in debug mode, 'ssh_management' has no effect."
                )
        else:
            compose_path = Path(
                _get_value(coord_def, 'compose_path', 'coordinator'))
            if 'expose' not in coord_def:
                log.warning(
                    "No interface to publish the coordinator on given. The coordinator will"
                    " be exposed at http://127.0.0.1:8000.")

        coord = Coordinator(topo.get_coord_name(), host, bridge, cpu_affinity,
                            ssh_management, debug, compose_path)
        coord.exposed_at = _get_external_address(coord_def)

        for name, data in coord_def['users'].items():
            if name is None:
                log.error("User name missing.")
                raise errors.InvalidTopo()
            coord.users[name] = User(data['email'], data['password'],
                                     data.get('superuser', False))

        topo.coordinator = coord

    # Prometheus
    if 'prometheus' in topo_file:
        prom_def = topo_file.pop('prometheus')  # remove prometheus section
        host = topo.hosts[prom_def.get('host', 'localhost')]

        def_name = lambda: topo.gen_bridge_name()
        bridge = networks.get(_get_value(prom_def, 'network', 'coordinator'),
                              def_name, localhost)
        if not bridge.is_docker_managed:
            log.error("Invalid network type for Prometheus.")
            raise InvalidTopo()

        prom = Prometheus(
            host,
            cast(DockerNetwork, bridge),
            cpu_affinity=CpuSet(prom_def.get('cpu_affinity')),
            scrape_interval=prom_def.get('scrape_interval', "30s"),
            storage_dir=_get_optional_path(prom_def, 'storage_dir'),
            targets=[ISD_AS(target) for target in prom_def['targets']])
        prom.exposed_at = _get_external_address(prom_def)

        topo.additional_services.append(prom)

    # IXP definitions
    for ixp_name, ixp_def in topo_file.pop('IXPs',
                                           {}).items():  # remove IXP section
        if ixp_name in topo.ixps:
            log.error("IXP %s is defined multiple times.", name)
            raise errors.InvalidTopo()
        net_name = _get_value(ixp_def, 'network', ixp_name)
        def_name = lambda: topo.get_name_prefix() + ixp_name
        bridge = networks.get(net_name, def_name, localhost)
        topo.ixps[ixp_name] = Ixp(bridge)

    # ASes
    for as_name, as_def in topo_file['ASes'].items():
        isd_as = ISD_AS(as_name)
        host_name = as_def.get('host', 'localhost')
        host = None
        try:
            host = topo.hosts[host_name]
        except KeyError:
            log.error("Invalid host: '%s'.", as_def[host_name])
            raise
        cpu_affinity = CpuSet(as_def.get('cpu_affinity'))
        asys = AS(host, as_def.get('core', False), cpu_affinity)

        asys.is_attachment_point = as_def.pop('attachment_point', False)
        asys.owner = as_def.pop('owner', None)
        topo.ases[isd_as] = asys

        if topo.coordinator:
            for ixp_name in as_def.pop('ixps', []):
                if asys.owner is None:
                    log.error("Infrastructure AS %s has an IXP list.", isd_as)
                    raise errors.InvalidTopo()
                ixp = topo.ixps[ixp_name]
                ixp.ases[isd_as] = asys
                # Add dummy link to IXP to make sure there is a network connection.
                # Actual links will be configured by the coordinator.
                # The border router of the link endpoint is labeled here to avoid creating a new
                # border router for every IXP link.
                end_point = LinkEp(isd_as,
                                   ifid=ifids.assign_ifid(isd_as),
                                   br_label='peer')
                link = Link(end_point, LinkEp(), LinkType.UNSET)
                link.bridge = ixp.bridge
                topo.links.append(link)
                brs.add_link_ep(end_point, link)

    # Link definitions
    for link in topo_file['links']:
        a, b = LinkEp(link['a']), LinkEp(link['b'])

        # Assing IfIds if not given in the original topo file.
        # Setting the IDs of all interfaces in the processed topology file ensures we can identify
        # the interfaces in the configuration files generated by scion.sh.
        for ep, name in [(a, 'a'), (b, 'b')]:
            if ep.ifid is None:
                ep.ifid = ifids.assign_ifid(ep)
                link[name] = "{}#{}".format(link[name], ep.ifid)

        topo.links.append(Link(a, b, link['linkAtoB']))

        # Keep track of border routers that will be created for the links.
        brs.add_link_ep(a, topo.links[-1])
        brs.add_link_ep(b, topo.links[-1])

        # Assign to a network if an IXP name or an explicit IP network is given.
        if "network" in link:
            net = link.pop('network')
            if net in topo.ixps:  # use the IXPs network
                ixp = topo.ixps[net]
                topo.links[-1].bridge = ixp.bridge
                ixp.ases[a] = topo.ases[a]
                ixp.ases[b] = topo.ases[b]
            else:
                def_name = lambda: topo.gen_bridge_name()
                topo.links[-1].bridge = networks.get(net, def_name, localhost)
        else:
            if topo.ases[a].host != topo.ases[b].host:
                log.error(
                    "Links between ASes on different hosts must specify the network to use."
                )
                raise errors.InvalidTopo()

    # Enable IPv6 support if needed.
    topo.ipv6_enabled = networks.is_ipv6_required()

    # Store bridges in topology.
    topo.bridges = networks.get_bridges()

    # Store border router info in corresponsing AS.
    for isd_as, asys in topo.ases.items():
        asys.border_routers = brs.get_brs(isd_as)

    return topo
예제 #15
0
    def test(self):
        """Test successful parse of a topology with multiple hosts and a coordinator."""
        topo_file = yaml.safe_load(TEST_TOPO)
        topo = extract_topo_info(topo_file)
        assign_underlay_addresses(topo)

        # IPv6
        self.assertFalse(topo.ipv6_enabled)

        # Default link subnet
        self.assertEqual(topo.default_link_subnet,
                         ipaddress.ip_network("10.0.10.0/24"))

        # Hosts
        self.assertEqual(len(topo.hosts), 2)
        self.assertIsInstance(topo.hosts['localhost'], LocalHost)
        self.assertIsInstance(topo.hosts['host1'], RemoteHost)

        host = cast(RemoteHost, topo.hosts['host1'])
        self.assertEqual(host.name, 'host1')
        self.assertEqual(host.ssh_host, ipaddress.ip_address("192.168.244.3"))
        self.assertEqual(host._ssh_port, 22)
        self.assertEqual(host._username, "scion")
        self.assertEqual(host._identity_file, ".ssh/id_rsa")

        # Networks
        self.assertEqual(len(topo.bridges), 8)

        bridge = topo.get_bridge_subnet(ipaddress.ip_network("10.0.20.0/24"))
        self.assertIsInstance(bridge, DockerBridge)
        self.assertEqual(bridge.name, "bridge1")
        self.assertEqual(
            cast(DockerBridge, bridge)._host, topo.hosts['localhost'])

        bridge = topo.get_bridge_subnet(ipaddress.ip_network("10.0.21.0/24"))
        self.assertIsInstance(bridge, OvsBridge)
        self.assertEqual(bridge.name, "ovs_bridge1")
        self.assertEqual(
            cast(OvsBridge, bridge)._host, topo.hosts['localhost'])

        bridge = topo.get_bridge_subnet(ipaddress.ip_network("10.0.22.0/24"))
        self.assertIsInstance(bridge, OverlayNetwork)
        self.assertEqual(bridge.name, "overlay_bridge1")
        self.assertEqual(
            cast(OverlayNetwork, bridge)._host, topo.hosts['host1'])
        self.assertEqual(cast(OverlayNetwork, bridge).encrypted, True)
        bridge = topo.get_bridge_subnet(ipaddress.ip_network("10.0.23.0/24"))

        self.assertIsInstance(bridge, HostNetwork)
        self.assertEqual(bridge.name, "physical_network1")

        # Coordinator
        self.assertIs(topo.coordinator.bridge,
                      topo.get_bridge_name("overlay_bridge1"))
        self.assertIs(topo.coordinator.host, topo.hosts['localhost'])
        self.assertFalse(topo.coordinator.cpu_affinity.is_unrestricted())
        self.assertEqual(str(topo.coordinator.cpu_affinity), "0")
        expected = UnderlayAddress(ipaddress.ip_address("192.168.244.2"),
                                   L4Port(8000))
        self.assertEqual(topo.coordinator.exposed_at, expected)

        self.assertEqual(len(topo.coordinator.users), 3)

        user = topo.coordinator.users['admin']
        self.assertEqual(user.email, "*****@*****.**")
        self.assertEqual(user.password, "admin")
        self.assertTrue(user.is_admin)

        user = topo.coordinator.users['user1']
        self.assertEqual(user.email, "*****@*****.**")
        self.assertEqual(user.password, "user1")
        self.assertFalse(user.is_admin)

        user = topo.coordinator.users['user2']
        self.assertEqual(user.email, "*****@*****.**")
        self.assertEqual(user.password, "user2")
        self.assertFalse(user.is_admin)

        # Prometheus
        self.assertEqual(len(topo.additional_services), 1)
        prom = cast(Prometheus, topo.additional_services[0])
        self.assertIs(prom.bridge, topo.get_bridge_name("overlay_bridge1"))
        self.assertEqual(prom.host, topo.hosts['localhost'])
        self.assertFalse(prom.cpu_affinity.is_unrestricted())
        self.assertEqual(str(prom.cpu_affinity), "1")
        expected = UnderlayAddress(ipaddress.ip_address("192.168.244.2"),
                                   L4Port(9090))
        self.assertEqual(prom.exposed_at, expected)

        self.assertEqual(prom.scrape_interval, "5s")
        self.assertIn(ISD_AS("1-ff00:0:110"), prom.targets)
        self.assertIn(ISD_AS("1-ff00:0:111"), prom.targets)
        self.assertIn(ISD_AS("1-ff00:0:112"), prom.targets)

        # ASes
        self.assertEqual(len(topo.ases), 8)

        asys = topo.ases[ISD_AS("1-ff00:0:110")]
        self.assertTrue(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertTrue(asys.cpu_affinity.is_unrestricted())
        self.assertIsNone(asys.owner)

        asys = topo.ases[ISD_AS("1-ff00:0:111")]
        self.assertFalse(asys.is_core)
        self.assertTrue(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertTrue(asys.cpu_affinity.is_unrestricted())
        self.assertIsNone(asys.owner)

        asys = topo.ases[ISD_AS("1-ff00:0:112")]
        self.assertFalse(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertFalse(asys.cpu_affinity.is_unrestricted())
        self.assertEqual(str(asys.cpu_affinity), "0,1,2,3")
        self.assertEqual(asys.owner, "user1")

        asys = topo.ases[ISD_AS("1-ff00:0:113")]
        self.assertFalse(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertFalse(asys.cpu_affinity.is_unrestricted())
        self.assertEqual(str(asys.cpu_affinity), "2,3,5")
        self.assertEqual(asys.owner, "user1")

        asys = topo.ases[ISD_AS("2-ff00:0:210")]
        self.assertTrue(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertIsNone(asys.owner)

        asys = topo.ases[ISD_AS("2-ff00:0:211")]
        self.assertFalse(asys.is_core)
        self.assertTrue(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['localhost'])
        self.assertIsNone(asys.owner)

        asys = topo.ases[ISD_AS("2-ff00:0:212")]
        self.assertFalse(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['host1'])
        self.assertEqual(asys.owner, "user2")

        asys = topo.ases[ISD_AS("2-ff00:0:213")]
        self.assertFalse(asys.is_core)
        self.assertFalse(asys.is_attachment_point)
        self.assertIs(asys.host, topo.hosts['host1'])
        self.assertEqual(asys.owner, "user2")

        # IXPs
        self.assertIn("ixp1", topo.ixps)
        ixp = topo.ixps["ixp1"]
        expected_ases = [ISD_AS("1-ff00:0:112"), ISD_AS("1-ff00:0:113")]
        for isd_as in expected_ases:
            self.assertIn(isd_as, ixp.ases)
            self.assertIs(ixp.ases[isd_as], topo.ases[isd_as])
        self.assertIsInstance(ixp.bridge, DockerBridge)
        self.assertEqual(ixp.bridge.ip_network,
                         ipaddress.ip_network("10.0.12.0/24"))

        self.assertIn("ixp2", topo.ixps)
        ixp = topo.ixps["ixp2"]
        expected_ases = [
            ISD_AS("1-ff00:0:112"),
            ISD_AS("1-ff00:0:113"),
            ISD_AS("2-ff00:0:212"),
            ISD_AS("2-ff00:0:213")
        ]
        for isd_as in expected_ases:
            self.assertIn(isd_as, ixp.ases)
            self.assertIs(ixp.ases[isd_as], topo.ases[isd_as])
        self.assertIsInstance(ixp.bridge, HostNetwork)
        self.assertIs(ixp.bridge, topo.get_bridge_name("physical_network1"))

        # Links
        self.assertEqual(len(topo.links), 13)

        interfaces = dict(topo.ases[ISD_AS("1-ff00:0:110")].links())
        link = interfaces[IfId(1)]
        subnet = ipaddress.ip_network("10.0.13.0/24")
        self.assertIs(link.bridge, topo.get_bridge_subnet(subnet))

        interfaces = dict(topo.ases[ISD_AS("1-ff00:0:111")].links())
        link = interfaces[IfId(1)]
        self.assertTrue(
            link.bridge.ip_network.overlaps(topo.default_link_subnet))
        link = interfaces[IfId(2)]
        self.assertTrue(
            link.bridge.ip_network.overlaps(topo.default_link_subnet))

        interfaces = dict(topo.ases[ISD_AS("2-ff00:0:211")].links())
        link = interfaces[IfId(1)]
        self.assertIs(link.bridge, topo.get_bridge_name("overlay_bridge1"))
        link = interfaces[IfId(2)]
        self.assertIs(link.bridge, topo.get_bridge_name("overlay_bridge1"))

        null = LinkEp()
        dummy_link_count = 0
        for link in topo.links:
            if link.type == LinkType.UNSET:
                dummy_link_count += 1
                self.assertEqual(link.ep_b, null)
            elif link.ep_b == null:
                self.assertEqual(link.type, LinkType.UNSET)
            self.assertNotEqual(link.ep_a, null)
        self.assertEqual(dummy_link_count, 6)

        # topo_file
        self.assertNotIn("link_subnet", topo_file['defaults'])
        self.assertNotIn("hosts", topo_file)
        self.assertNotIn("networks", topo_file)
        self.assertNotIn("coordinator", topo_file)
        self.assertNotIn("IXPs", topo_file)
        for link in topo_file['links']:
            self.assertNotIn('network', link)
예제 #16
0
def _get_as_path(isd_as: ISD_AS) -> Path:
    """Returns the relative path of an ASes configuration in the gen folder."""
    return Path("ISD{}/AS{}/".format(isd_as.isd_str(), isd_as.as_file_fmt()))