Beispiel #1
0
    def fill_database(self, node):
        db.create_all()
        d = Dimension.from_json(self.dim)
        d.current = True
        s1 = Server(id='00000000-0000-0000-0000-000000000001', name='node1', created_on=now, me=node == 'node1')
        g11 = Gate(id='00000000-0000-0000-0000-000000000011', server=s1, port=5000, dns=s1.name)
        s2 = Server(id='00000000-0000-0000-0000-000000000002', name='node2', created_on=now, me=node == 'node2')
        g12 = Gate(id='00000000-0000-0000-0000-000000000012', server=s2, port=5000, dns=s2.name)
        s3 = Server(id='00000000-0000-0000-0000-000000000003', name='node3', created_on=now, me=node == 'node3')
        g13 = Gate(id='00000000-0000-0000-0000-000000000013', server=s3, port=5000, dns=s3.name)
        s4 = Server(id='00000000-0000-0000-0000-000000000004', name='node4', created_on=now, me=node == 'node4')
        g14 = Gate(id='00000000-0000-0000-0000-000000000014', server=s4, port=5000, dns=s4.name)

        if node == 'node1':
            self.s1 = s1
            self.s2 = s2
            self.s3 = s3
            self.s4 = s4
            Route(s2, g12)
            Route(s3, s2, 1)
        elif node == 'node2':
            Route(s1, g11)
            Route(s3, g13)
        elif node == 'node3':
            Route(s1, s2, 1)
            Route(s2, g12)

        db.session.add_all([d, s1, s2, s3, s4])
        db.session.commit()
Beispiel #2
0
    def setUp(self):
        """Create and configure a new app instance for each test."""
        # create the app with common test config
        self.app = create_app('test')
        self.app_context = self.app.app_context()
        self.app_context.push()
        self.client = self.app.test_client()
        self.headers = {
            "Authorization":
            f"Bearer {create_access_token('00000000-0000-0000-0000-000000000001')}"
        }

        db.create_all()
        set_initial()

        self.n1 = Server("node1", port=8000)
        Route(self.n1, cost=0)
        self.n2 = Server("node2", port=8000)
        Route(self.n2, cost=0)
        db.session.add_all([self.n1, self.n2])
        db.session.commit()
        self.datamark = Catalog.max_catalog(str)
Beispiel #3
0
    def test_get_reachable_servers(self):
        n1 = Server('n1', port=8000, id='22cd859d-ee91-4079-a112-000000000001')
        n2 = Server('n2', port=8000, id='22cd859d-ee91-4079-a112-000000000002')
        n3 = Server('n3', port=8000, id='22cd859d-ee91-4079-a112-000000000003')
        n3.route = None
        n4 = Server('n4', port=8000, id='22cd859d-ee91-4079-a112-000000000004')
        r1 = Server('r1', port=8000, id='22cd859d-ee91-4079-a112-000000000011')
        Route(destination=n1, cost=0)
        Route(destination=n2, proxy_server_or_gate=n2.gates[0])
        Route(destination=n4)
        Route(destination=r1, proxy_server_or_gate=n1, cost=1)

        me = Server('me', port=8000, me=True)
        db.session.add_all([n1, n2, n3, n4, r1, me])

        self.assertListEqual([n1, n2, r1], me.get_reachable_servers())

        self.assertListEqual([n2, r1], me.get_reachable_servers(exclude=n1))
        self.assertListEqual([n2, r1], me.get_reachable_servers(exclude=n1.id))

        self.assertListEqual([r1],
                             me.get_reachable_servers(exclude=[n1.id, n2]))
Beispiel #4
0
    def _fill_database(self, app: Flask):
        with mock.patch('dimensigon.domain.entities.get_now') as mock_get_now:
            mock_get_now.return_value = defaults.INITIAL_DATEMARK
            node = app.config['SERVER_NAME']

            with app.app_context():
                db.create_all()
                event.listen(db.session, 'after_commit', receive_after_commit)
                set_initial(**self.initials)
                d = Dimension.from_json(self.dim)
                d.current = True
                s1 = Server('node1',
                            created_on=defaults.INITIAL_DATEMARK,
                            id=self.SERVER1,
                            me=node == 'node1')
                g11 = Gate(id='00000000-0000-0000-0000-000000000011',
                           server=s1,
                           port=5000,
                           dns=s1.name)
                s2 = Server('node2',
                            created_on=defaults.INITIAL_DATEMARK,
                            id=self.SERVER2,
                            me=node == 'node2')
                g12 = Gate(id='00000000-0000-0000-0000-000000000012',
                           server=s2,
                           port=5000,
                           dns=s2.name)

                if node == 'node1':
                    Route(s2, g12)
                elif node == 'node2':
                    Route(s1, g11)

                self.fill_database()
                db.session.add_all([d, s1, s2])
                db.session.commit()
            if node == 'node1':
                self.s1, self.s2 = db.session.merge(s1), db.session.merge(s2)
Beispiel #5
0
    def test_get_servers_from_scope_more_than_min_quorum(
            self, mock_get_now, mock_app):
        mock_get_now.return_value = dt.datetime(2019,
                                                4,
                                                1,
                                                tzinfo=dt.timezone.utc)
        Server.set_initial()
        servers = []
        for i in range(0, 7):

            s = Server(f'node{i}', port=5000, created_on=old_age)

            if i == 0:
                r = Route(s, cost=0)
            else:
                r = Route(s, random.choice(servers), cost=i)

            db.session.add_all([s, r])

            servers.append(s)

        mock_get_now.return_value = dt.datetime(2019,
                                                4,
                                                2,
                                                tzinfo=dt.timezone.utc)
        mock_app.dm.cluster_manager.get_alive.return_value = [
            s.id for s in servers
        ]
        s62 = Server(f'node72', port=5000)
        Route(s62, random.choice(servers), cost=6)
        db.session.add(s62)

        quorum = get_servers_from_scope(scope=Scope.CATALOG)

        self.assertEqual(8, len(quorum))
        self.assertNotIn(s62, quorum)
        self.assertIn(s, quorum)
        self.assertIn(Server.get_current(), quorum)
    def test_upgrade_catalog_catalog_mismatch(self, mock_lock, mock_entities):
        mock_lock.return_value.__enter__.return_value = 'applicant'
        mock_entities.return_value = [('ActionTemplate', ActionTemplate),
                                      ('Server', Server)]

        s = Server('server',
                   last_modified_at=dt.datetime(2019,
                                                4,
                                                1,
                                                tzinfo=dt.timezone.utc),
                   port=8000)
        Route(s, cost=0)

        at1 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-56781234aaa1',
                             name='mkdir',
                             version=1,
                             action_type=ActionType.SHELL,
                             code='mkdir {dir}',
                             expected_output=None,
                             expected_rc=None,
                             system_kwargs={},
                             last_modified_at=dt.datetime(
                                 2019, 4, 1, tzinfo=dt.timezone.utc))

        at2 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-56781234aaa2',
                             name='rmdir',
                             version=1,
                             action_type=ActionType.SHELL,
                             code='rmdir {dir}',
                             expected_output=None,
                             expected_rc=None,
                             system_kwargs={},
                             last_modified_at=dt.datetime(
                                 2019, 4, 2, tzinfo=dt.timezone.utc))

        responses.add(
            method='GET',
            url=re.compile('^' + s.url(
                'api_1_0.catalog', data_mark='12345').replace('12345', '')),
            json={"ActionTemplate": [at1.to_json(),
                                     at2.to_json()]})

        with self.assertRaises(errors.CatalogMismatch):
            upgrade_catalog_from_server(s)
Beispiel #7
0
    def test_sqlalchemy_entity_events_server(self, mock_now):
        mock_now.return_value = dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc)
        s = Server('source', port=5000)
        d = Server('destination', port=5000)
        db.session.add_all([s, d])
        db.session.commit()

        mock_now.return_value = dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc)

        s.route = Route(destination=d, proxy_server_or_gate=d.gates[0], cost=0)
        db.session.commit()
        self.assertEqual(dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc),
                         s.last_modified_at)

        g = s.add_new_gate(dns_or_ip='dns', port=5000)
        db.session.add(g)
        db.session.commit()
        self.assertEqual(dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc),
                         s.last_modified_at)
    def setUp(self):
        """Create and configure a new self.app instance for each test."""
        # create a temporary file to isolate the database for each test
        # create the self.app with common test config

        self.app = Flask(__name__)

        @self.app.route('/', methods=['GET', 'POST'])
        @securizer
        def hello():
            return request.get_json() or {'msg': 'default response'}

        @self.app.route('/join', methods=['POST'])
        @securizer
        def join():
            return {'msg': 'default response'}

        @self.app.route('/empty', methods=['GET'])
        @securizer
        def empty():
            return "", 204

        @self.app.route('/list', methods=['GET'])
        @securizer
        def list():
            return [1, 2], 200

        self.app.config['SECURIZER'] = True
        self.app.config['SECURIZER_PLAIN'] = True
        self.app_context = self.app.app_context()
        self.app_context.push()
        self.client = self.app.test_client()
        db.init_app(self.app)
        db.create_all()
        self.d = generate_dimension('test')
        self.d.current = True
        self.srv1 = Server(id='bbbbbbbb-1234-5678-1234-56781234bbb1', name='server1',
                           dns_or_ip='192.168.1.9', port=7123, me=True)
        Route(self.srv1, cost=0)

        db.session.add_all([self.srv1, self.d])
        db.session.commit()
    def test_upgrade_catalog_no_data(self, mock_lock, mock_entities):
        mock_lock.return_value.__enter__.return_value = 'applicant'
        mock_entities.return_value = [('ActionTemplate', ActionTemplate)]

        s = Server('server',
                   last_modified_at=dt.datetime(2019,
                                                4,
                                                1,
                                                tzinfo=dt.timezone.utc))
        g = Gate(server=s, port=80)
        Route(s, cost=0)

        responses.add(
            method='GET',
            url=re.compile('^' + s.url(
                'api_1_0.catalog', data_mark='12345').replace('12345', '')),
            json={"ActionTemplate": []})

        upgrade_catalog_from_server(s)

        atl = [at.to_json() for at in ActionTemplate.query.all()]

        self.assertEqual(0, len(atl))
Beispiel #10
0
    def _update_route_table_from_data(
            self,
            new_routes: t.Dict,
            auth=None) -> t.Dict[Server, RouteContainer]:
        changed_routes = {}
        routes = new_routes.get('route_list', [])
        routes.sort(key=lambda x: x.get('cost') or MAX_COST, reverse=True)
        try:
            likely_proxy_server = self.session.query(Server).get(
                new_routes.get('server_id'))
            if not likely_proxy_server:
                self.logger.warning(
                    f"Server id still '{new_routes.get('server_id')}' not created."
                )
                return changed_routes
            debug_new_routes = []
            for new_route in routes:
                target_server = self.session.query(Server).get(
                    new_route.get('destination_id'))
                proxy_server = self.session.query(Server).get(
                    new_route.get('proxy_server_id'))
                gate = self.session.query(Gate).get(new_route.get('gate_id'))
                dest_name = getattr(target_server, 'name',
                                    new_route.get('destination_id'))
                proxy_name = getattr(proxy_server, 'name',
                                     new_route.get('proxy_server_id'))
                gate_str = str(gate) if gate else new_route.get('gate_id')
                cost = new_route.get('cost')
                if gate_str and proxy_name:
                    gate_str = gate_str + '*' + proxy_name
                debug_new_routes.append(
                    f'{dest_name} -> {gate_str or proxy_name} / {cost}')
                if target_server is None:
                    self.logger.warning(
                        f"Destination server unknown {new_route.get('destination_id')}"
                    )
                    continue
                if target_server.id == self.server.id:
                    # check if server has detected me as a neighbour
                    if new_route.get('cost') == 0:
                        # check if I do not have it as a neighbour yet
                        if likely_proxy_server.route and likely_proxy_server.route.cost != 0:
                            # check if I have a gate to contact with it
                            route = check_gates(likely_proxy_server)
                            if isinstance(route, RouteContainer):
                                changed_routes[likely_proxy_server] = route
                                likely_proxy_server.set_route(route)
                else:
                    # server may be created without route (backward compatibility)
                    if target_server.route is None:
                        target_server.set_route(
                            Route(destination=target_server))
                    # process routes whose proxy_server is not me
                    if self.server.id != new_route.get('proxy_server_id'):
                        # check If I reach destination
                        if target_server.route.cost is not None:
                            if new_route.get('cost') is None:
                                #  likely proxy does not reach but I reach it. It might be shutdown unexpectedly?
                                if target_server.route.cost == 0:
                                    # check if I still have it as a neighbour
                                    route = check_gates(target_server)
                                else:
                                    if target_server.route.proxy_server == likely_proxy_server:
                                        route = RouteContainer(
                                            None, None, None)
                                    else:
                                        # check if I still have access through my proxy
                                        cost, time = ntwrk.ping(
                                            target_server,
                                            retries=1,
                                            timeout=20,
                                            session=self.session)
                                        if cost == target_server.route.cost:
                                            # still a valid route
                                            route = target_server.route
                                        elif cost is None:
                                            route = RouteContainer(
                                                None, None, None)
                                        else:
                                            route = RouteContainer(
                                                target_server.route.
                                                proxy_server, None, cost)

                                if isinstance(route, RouteContainer):
                                    # gate changed
                                    changed_routes[target_server] = route
                                    target_server.set_route(route)
                                elif route is None:
                                    # no route to host. I've lost contact too
                                    changed_routes[
                                        target_server] = RouteContainer(
                                            None, None, None)
                                    target_server.set_route(
                                        changed_routes[target_server])
                                else:
                                    # still a valid route. Send route to likely_proxy_server to tell it I have access
                                    resp = ntwrk.patch(
                                        likely_proxy_server,
                                        'api_1_0.routes',
                                        json=dict(
                                            server_id=str(self.server.id),
                                            route_list=[
                                                target_server.route.to_json()
                                            ]),
                                        auth=get_root_auth(),
                                        timeout=5)
                                    if not resp.ok:
                                        self.logger.info(
                                            f'Unable to send route to {likely_proxy_server}: {resp}'
                                        )
                            elif target_server.route.proxy_server is not None and \
                                    target_server.route.proxy_server == likely_proxy_server:
                                # my proxy is telling me the route has changed
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                            elif new_route.get(
                                    'cost') + 1 < target_server.route.cost:
                                # if new route has less cost than actual route, take it as my new route
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                        else:
                            # me does not reaches target_server
                            # if new route reaches the destination take it as a new one
                            if (new_route.get('cost') == 0
                                    and new_route.get('gate_id') is not None
                                ) or (new_route.get('cost') is not None
                                      and proxy_server is not None):
                                rc = RouteContainer(likely_proxy_server, None,
                                                    new_route.get('cost') + 1)
                            else:
                                # neither my route and the new route has access to the destination
                                rc = RouteContainer(None, None, None)
                            if target_server.route and (
                                    rc.proxy_server !=
                                    target_server.route.proxy_server
                                    or rc.gate != target_server.route.gate
                                    or rc.cost != target_server.route.cost):
                                target_server.set_route(rc)
                                changed_routes.update({target_server: rc})
                    else:
                        # target_server reached through me as a proxy from likely_proxy
                        pass

            query = self.session.query(Route).filter(
                Route.proxy_server_id.in_([
                    s.id for s, r in changed_routes.items() if r.cost is None
                ]))
            rc = RouteContainer(None, None, None)
            for route in query.all():
                route.set_route(rc)
                changed_routes[route.destination] = rc

            self.logger.debug(
                f"New routes processed from {likely_proxy_server.name}: {json.dumps(debug_new_routes, indent=2)}"
            )
            # if changed_routes:
            #     Parameter.set('routing_last_refresh', get_now())
        except errors.InvalidRoute as e:
            debug_new_routes = []
            routes.sort(key=lambda x: x.get('cost') or MAX_COST, reverse=True)
            for new_route in routes:
                target_server = self.session.query(Server).get(
                    new_route.get('destination_id'))
                proxy_server = self.session.query(Server).get(
                    new_route.get('proxy_server_id'))
                gate = self.session.query(Gate).get(new_route.get('gate_id'))
                dest_name = getattr(target_server, 'name',
                                    new_route.get('destination_id'))
                proxy_name = getattr(proxy_server, 'name',
                                     new_route.get('proxy_server_id'))
                gate_str = str(gate) if gate else new_route.get('gate_id')
                cost = new_route.get('cost')
                if gate_str and proxy_name:
                    gate_str = gate_str + '*' + proxy_name
                debug_new_routes.append(
                    f'{dest_name} -> {gate_str or proxy_name} / {cost}')
            self.logger.exception(
                "Error setting routes from following data: " +
                json.dumps(debug_new_routes, indent=4))
        return changed_routes
Beispiel #11
0
    def _route_table_merge(self, data: t.Dict[Server, ntwrk.Response]):
        changed_routes: t.Dict[Server, RouteContainer] = {}
        temp_table_routes: t.Dict[uuid.UUID, t.List[RouteContainer]] = {}
        for s, resp in data.items():
            if resp.code == 200:
                server_id = resp.msg.get(
                    'server_id', None) or resp.msg.get('server').get('id')
                likely_proxy_server_entity = self.session.query(Server).get(
                    server_id)
                for route_json in resp.msg['route_list']:
                    route_json = convert(route_json)
                    if route_json.destination_id != self.server.id \
                            and route_json.proxy_server_id != self.server.id \
                            and route_json.gate_id not in [g.id for g in self.server.gates]:
                        if route_json.destination_id not in temp_table_routes:
                            temp_table_routes.update(
                                {route_json.destination_id: []})
                        if route_json.cost is not None:
                            route_json.cost += 1
                            route_json.proxy_server_id = likely_proxy_server_entity.id
                            route_json.gate_id = None
                            temp_table_routes[
                                route_json.destination_id].append(
                                    RouteContainer(
                                        likely_proxy_server_entity.id, None,
                                        route_json.cost))
                        elif route_json.cost is None:
                            # remove a routing if gateway cannot reach the destination
                            temp_table_routes[
                                route_json.destination_id].append(
                                    RouteContainer(route_json.proxy_server_id,
                                                   None, None))
            else:
                self.logger.error(
                    f"Error while connecting with {s}. Error: {resp}")

        # Select new routes based on neighbour routes
        neighbour_ids = [
            s.id for s in Server.get_neighbours(session=self.session)
        ]
        for destination_id in filter(lambda s: s not in neighbour_ids,
                                     temp_table_routes.keys()):
            route = self.session.query(Route).filter_by(
                destination_id=destination_id).one_or_none()
            if not route:
                server = self.session.query(Server).get(destination_id)
                if not server:
                    continue
                else:
                    route = Route(destination=server)
            temp_table_routes[destination_id].sort(
                key=lambda x: x.cost or MAX_COST)
            if len(temp_table_routes[destination_id]) > 0:
                min_route = temp_table_routes[destination_id][0]
                proxy_server: Server = self.session.query(Server).get(
                    min_route.proxy_server)
                cost = min_route.cost
                if route.proxy_server != proxy_server or route.cost != cost:
                    rc = RouteContainer(proxy_server, None, cost)
                    route.set_route(rc)
                    changed_routes[route.destination] = rc
                    self.session.add(route)

        data = {}
        for server, temp_route in changed_routes.items():
            data.update({
                str(server): {
                    'proxy_server': str(temp_route.proxy_server),
                    'gate': str(temp_route.gate),
                    'cost': str(temp_route.cost)
                }
            })
        return changed_routes
    def test_upgrade_catalog(self, mock_lock, mock_entities, mock_now,
                             mock_tzlocal):
        mock_lock.return_value.__enter__.return_value = 'applicant'
        mock_entities.return_value = [('ActionTemplate', ActionTemplate),
                                      ('Server', Server), ('Gate', Gate)]
        mock_now.return_value = dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc)
        mock_tzlocal.return_value = dt.timezone.utc

        at1 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-56781234aaa1',
                             name='mkdir',
                             version=1,
                             action_type=ActionType.SHELL,
                             code='mkdir {dir}',
                             expected_output=None,
                             expected_rc=None,
                             system_kwargs={})
        db.session.add(at1)
        db.session.commit()

        s = Server(id='aaaaaaaa-1234-5678-1234-56781234bbb1',
                   name='server',
                   last_modified_at=dt.datetime(2019,
                                                4,
                                                1,
                                                tzinfo=dt.timezone.utc))
        s_json = s.to_json()
        g = Gate(server=s,
                 port=80,
                 dns='server',
                 last_modified_at=dt.datetime(2019,
                                              4,
                                              1,
                                              tzinfo=dt.timezone.utc))
        g_json = g.to_json()
        Route(s, cost=0)

        at2 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-56781234aaa2',
                             name='rmdir',
                             version=1,
                             action_type=ActionType.SHELL,
                             code='rmdir {dir}',
                             expected_output=None,
                             expected_rc=None,
                             system_kwargs={},
                             last_modified_at=dt.datetime(
                                 2019, 4, 2, tzinfo=dt.timezone.utc))
        at2_json = at2.to_json()
        del at2

        at1_json = at1.to_json()
        del at1
        at1_json['code'] = 'mkdir -p {dir}'
        responses.add(
            method='GET',
            url=re.compile('^' + s.url(
                'api_1_0.catalog', data_mark='12345').replace('12345', '')),
            json={
                "ActionTemplate": [at1_json, at2_json],
                "Server": [s_json],
                "Gate": [g_json]
            })

        upgrade_catalog_from_server(s)

        db.session.expire_all()
        atl = [at.to_json() for at in ActionTemplate.query.all()]

        self.assertListEqual([at1_json, at2_json], atl)

        c = Catalog.query.get('ActionTemplate')
        self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc),
                         c.last_modified_at)

        at1 = ActionTemplate.query.get('aaaaaaaa-1234-5678-1234-56781234aaa1')
        self.assertEqual('mkdir -p {dir}', at.code)
Beispiel #13
0
    def test_route(self):
        dest = Server('dest', port=80)
        proxy = Server('proxy', port=80)

        r = Route(destination=dest)

        self.assertEqual(dest, r.destination)
        self.assertIsNone(r.proxy_server)
        self.assertIsNone(r.gate)
        self.assertIsNone(r.cost)

        # routes defined with a gate
        ## dest
        r = Route(destination=dest, proxy_server_or_gate=dest.gates[0])
        self.assertEqual(dest, r.destination)
        self.assertIsNone(r.proxy_server)
        self.assertEqual(dest.gates[0], r.gate)
        self.assertEqual(0, r.cost)

        r = Route(destination=dest, proxy_server_or_gate=dest.gates[0], cost=0)
        self.assertEqual(dest, r.destination)
        self.assertIsNone(r.proxy_server)
        self.assertEqual(dest.gates[0], r.gate)
        self.assertEqual(0, r.cost)

        with self.assertRaises(ValueError):
            r = Route(destination=dest,
                      proxy_server_or_gate=dest.gates[0],
                      cost=1)

        ## proxy
        with self.assertRaises(ValueError):
            Route(destination=dest, proxy_server_or_gate=proxy.gates[0])

        with self.assertRaises(ValueError):
            Route(destination=dest,
                  proxy_server_or_gate=proxy.gates[0],
                  cost=0)

        r = Route(destination=dest,
                  proxy_server_or_gate=proxy.gates[0],
                  cost=1)
        self.assertEqual(dest, r.destination)
        self.assertEqual(proxy, r.proxy_server)
        self.assertIsNone(r.gate)
        self.assertEqual(1, r.cost)

        # routes defined with a proxy server
        ## dest
        with self.assertRaises(ValueError):
            r = Route(destination=dest, proxy_server_or_gate=dest)

        with self.assertRaises(ValueError):
            r = Route(destination=dest, proxy_server_or_gate=dest, cost=0)

        with self.assertRaises(ValueError):
            r = Route(destination=dest, proxy_server_or_gate=dest, cost=1)

        ## proxy
        with self.assertRaises(ValueError):
            r = Route(destination=dest, proxy_server_or_gate=proxy)

        with self.assertRaises(ValueError):
            r = Route(destination=dest, proxy_server_or_gate=proxy, cost=0)

        r = Route(destination=dest, proxy_server_or_gate=proxy, cost=1)
        self.assertEqual(dest, r.destination)
        self.assertEqual(proxy, r.proxy_server)
        self.assertIsNone(r.gate)
        self.assertEqual(1, r.cost)