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))
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()
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)
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)
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)
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()]
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
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()
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))
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)))
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)
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)
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()
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
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)
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()))