@defer.inlineCallbacks
    def reserve(self, header, connection_id, global_reservation_id, description, criteria, request_info=None):

        log.msg('', system=LOG_SYSTEM)
        log.msg('Reserve request from %s. Trace: %s' % (header.requester_nsa, header.connection_trace), system=LOG_SYSTEM)

        # rethink with modify
        if connection_id != None:
            connection_exists = yield database.ServiceConnection.exists(['connection_id = ?', connection_id])
            if connection_exists:
                raise error.ConnectionExistsError('Connection with id %s already exists' % connection_id)
            raise NotImplementedError('Cannot handly modification of existing connections yet')

        yield self.plugin.connectionRequest(header, connection_id, global_reservation_id, description, criteria)

        if cnt.REQUIRE_TRACE in self.policies:
            if not header.connection_trace:
                log.msg('Rejecting reserve request without connection trace')
                raise error.SecurityError('This NSA (%s) requires a connection trace in the header to create a reservation.' % self.nsa_.urn() )

        if cnt.REQUIRE_USER in self.policies:
            user_attrs  = [ sa for sa in header.security_attributes if sa.type_ == 'user'  ]
            if not user_attrs:
                log.msg('Rejecting reserve request without user security attribute', system=LOG_SYSTEM)
                raise error.SecurityError('This NSA (%s) requires a user attribute in the header to create a reservation.' % self.nsa_.urn() )

        sd = criteria.service_def
        source_stp = sd.source_stp
        dest_stp   = sd.dest_stp

        # policy check: one endpoint must be in local network
        if not (source_stp.network == self.network or dest_stp.network == self.network):
            raise error.ConnectionCreateError('None of the endpoints terminate in the network, rejecting request (network: %s + %s, nsa network %s)' %
                (source_stp.network, dest_stp.network, self.network))

        if (source_stp.label is None and dest_stp.label) or (source_stp.label and dest_stp.label is None):
            raise error.ConnectionCreateError('Cannot create connection with label only defined in one end (maybe possible in the future)')

        # check that we have path vectors to topologies
        if source_stp.network != self.network and self.route_vectors.vector(source_stp.network) is None:
            raise error.ConnectionCreateError('No known routes to network %s' % source_stp.network)
        if dest_stp.network != self.network and self.route_vectors.vector(dest_stp.network) is None:
            raise error.ConnectionCreateError('No known routes to network %s' % dest_stp.network)

        # if the link terminates at our network, check that ports exists
        if source_stp.network == self.network:
            self.network_topology.getPort(source_stp.network + ':' + source_stp.port)
        if dest_stp.network == self.network:
            self.network_topology.getPort(dest_stp.network + ':' + dest_stp.port)

        connection_id = yield self.plugin.createConnectionId()

        conn = database.ServiceConnection(connection_id=connection_id, revision=0, global_reservation_id=global_reservation_id, description=description,
                            requester_nsa=header.requester_nsa, requester_url=header.reply_to, reserve_time=datetime.datetime.utcnow(),
                            reservation_state=state.RESERVE_START, provision_state=state.RELEASED, lifecycle_state=state.CREATED,
                            source_network=source_stp.network, source_port=source_stp.port, source_label=source_stp.label,
                            dest_network=dest_stp.network, dest_port=dest_stp.port, dest_label=dest_stp.label,
                            start_time=criteria.schedule.start_time, end_time=criteria.schedule.end_time,
                            symmetrical=sd.symmetric, directionality=sd.directionality, bandwidth=sd.capacity,
                            security_attributes=header.security_attributes, connection_trace=header.connection_trace)
        yield conn.save()

        # Here we should return / callback and spawn off the path creation

        # Note: At his point STP Labels are candidates and they will need to be changed later

    #    def reserveRequestsDone(results):
    #        successes = [ r[0] for r in results ]
    #        if all(successes):
    #            state.reserved(conn)
    #            log.msg('Connection %s: Reserve succeeded' % self.connection_id, system=LOG_SYSTEM)
    #            self.scheduler.scheduleTransition(self.service_parameters.start_time, scheduled, state.RELEASED)
    #            return self
    #
    #        else:
    #            # terminate non-failed connections
    #            # currently we don't try and be too clever about cleaning, just do it, and switch state
    #            defs = []
    #            reserved_connections = [ conn for success,conn in results if success ]
    #            for rc in reserved_connections:
    #                d = rc.terminate()
    #                d.addCallbacks(
    #                    lambda c : log.msg('Succesfully terminated sub connection after partial reservation failure %s %s' % (c.curator(), connPath(c)) , system=LOG_SYSTEM),
    #                    lambda f : log.msg('Error terminating connection after partial-reservation failure: %s' % str(f), system=LOG_SYSTEM)
    #                )
    #                defs.append(d)
    #            dl = defer.DeferredList(defs)
    #            dl.addCallback( self.state.terminatedFailed )
    #
    #            err = self._createAggregateException(results, 'reservations', error.ConnectionCreateError)
    #            raise err

        yield state.reserveChecking(conn) # this also acts a lock

        if conn.source_network == self.network and conn.dest_network == self.network:
            # no hairpin connections
            if conn.source_port == conn.dest_port:
                raise error.ServiceError('Hairpin connections not supported. Go away and fix your path finder')
            # setup path
            path_info = ( conn.connection_id, self.network, conn.source_port, shortLabel(conn.source_label), conn.dest_port, shortLabel(conn.dest_label) )
            log.msg('Connection %s: Local link creation: %s %s?%s == %s?%s' % path_info, system=LOG_SYSTEM)
            paths = [ [ nsa.Link( nsa.STP(self.network, conn.source_port, conn.source_label),
                                  nsa.STP(self.network, conn.dest_port,   conn.dest_label))  ] ]

            # we should probably specify the connection id to the backend,
            # to make it seem like the aggregator isn't here

        else:
            # log about creation and the connection type
            log.msg('Connection %s: Aggregate path creation: %s -> %s' % (conn.connection_id, str(source_stp), str(dest_stp)), system=LOG_SYSTEM)
            # making the connection is the same for all though :-)

            # how to this with path vector
            # 1. find topology to use from vector
            # 2. create abstracted path: local link + rest

            if source_stp.network == self.network:
                local_stp      = source_stp
                remote_stp     = dest_stp
            else:
                local_stp      = dest_stp
                remote_stp     = source_stp

            # we should really find multiple port/link vectors to the remote network, but right now we don't
            vector_port = self.route_vectors.vector(remote_stp.network)
            if vector_port is None:
                raise error.STPResolutionError('No vector to network %s, cannot create circuit' % remote_stp.network)

            log.msg('Vector to %s via port %s' % (remote_stp.network, vector_port), system=LOG_SYSTEM)

            # this really shouldn't fail, so we don't need to check
            demarc_ports = [ self.network_topology.getPort( self.network + ':' + vector_port ) ]

            ldp = demarc_ports[0] # most of the time we will only have one anyway, should iterate and build multiple paths

            local_demarc_port  = ldp.id_.rsplit(':', 1)[1]
            remote_demarc_network, remote_demarc_port = ldp.remote_port.rsplit(':', 1) # [1] # this is wrong in the new naming scheme

            local_link  = nsa.Link( local_stp, nsa.STP(local_stp.network, local_demarc_port, ldp.label()) )
            remote_link = nsa.Link( nsa.STP(remote_demarc_network, remote_demarc_port, ldp.label()), remote_stp) # # the ldp label isn't quite correct

            paths = [ [ local_link, remote_link ] ]
            paths = yield self.plugin.prunePaths(paths)


        selected_path = paths[0] # shortest path (legacy structure)
        log_path = ' -> '.join( [ str(p) for p in selected_path ] )
        log.msg('Attempting to create path %s' % log_path, system=LOG_SYSTEM)

        for link in selected_path:
            if link.src_stp.network == self.network:
                continue # we got this..
            p = self.provider_registry.getProviderByNetwork(link.src_stp.network)
            if p is None:
                raise error.ConnectionCreateError('No provider for network %s. Cannot create link.' % link.src_stp.network)

        conn_trace = (header.connection_trace or []) + [ self.nsa_.urn() + ':' + conn.connection_id ]
        conn_info = []

        for idx, link in enumerate(selected_path):

            sub_connection_id = None

            if link.src_stp.network == self.network:
                provider_urn = self.nsa_.urn()
                sub_connection_id = connection_id
            else:
                provider_urn = self.provider_registry.getProviderByNetwork(link.src_stp.network)

            c_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, security_attributes=header.security_attributes, connection_trace=conn_trace)

            sd = nsa.Point2PointService(link.src_stp, link.dst_stp, conn.bandwidth, sd.directionality, sd.symmetric)

            # save info for db saving
            self.reservations[c_header.correlation_id] = {
                                                        'provider_nsa'  : provider_urn,
                                                        'service_connection_id' : conn.id,
                                                        'order_id'       : idx,
                                                        'source_network' : link.src_stp.network,
                                                        'source_port'    : link.src_stp.port,
                                                        'dest_network'   : link.dst_stp.network,
                                                        'dest_port'      : link.dst_stp.port }

            crt = nsa.Criteria(criteria.revision, criteria.schedule, sd)

            provider = self.getProvider(provider_urn)
            # note: request info will only be passed to local backends, remote requester will just ignore it
            d = provider.reserve(c_header, sub_connection_id, conn.global_reservation_id, conn.description, crt, request_info)
            d.addErrback(_logErrorResponse, connection_id, provider_urn, 'reserve')

            conn_info.append( (d, provider_urn) )

            # Don't bother trying to save connection here, wait for reserveConfirmed


        results = yield defer.DeferredList( [ c[0] for c in conn_info ], consumeErrors=True) # doesn't errback
        successes = [ r[0] for r in results ]

        if all(successes):
            log.msg('Connection %s: Reserve acked' % conn.connection_id, system=LOG_SYSTEM)
            defer.returnValue(connection_id)

        else:
            # I think this is out of spec, the aggregator shouldn't do anything here...
            # terminate non-failed connections
            # currently we don't try and be too clever about cleaning, just do it, and switch state
            yield state.terminating(conn)
            defs = []
            reserved_connections = [ (sc_id, provider_urn) for (success,sc_id),(_,provider_urn) in zip(results, conn_info) if success ]
            for (sc_id, provider_urn) in reserved_connections:

                provider = self.getProvider(provider_urn)
                t_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, security_attributes=header.security_attributes)

                d = provider.terminate(t_header, sc_id)
                d.addCallbacks(
                    lambda c : log.msg('Succesfully terminated sub connection %s at %s after partial reservation failure.' % (sc_id, provider_urn) , system=LOG_SYSTEM),
                    lambda f : log.msg('Error terminating connection after partial-reservation failure: %s' % str(f), system=LOG_SYSTEM)
                )
                defs.append(d)
            dl = defer.DeferredList(defs)
            yield dl
            yield state.terminated(conn)

            # construct provider nsa urns, so we can produce a good error message
            provider_urns = [ ci[1] for ci in conn_info ]
            err = _createAggregateException(connection_id, 'reservations', results, provider_urns, error.ConnectionCreateError)
            raise err
dataset/ETHPy150Open NORDUnet/opennsa/opennsa/aggregator.py/Aggregator.reserve/original