jobs

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 30, 2024 License: Apache-2.0 Imports: 30 Imported by: 0

README

Computing in the NuNet Network

Table of Contents

Ensemble Orchestration

In NuNet, compute workloads are structured as compute ensembles. Here, we discuss how an ensemble can be created, deployed, and supervised in the NuNet network.

Compute Ensembles

An ensemble is a collection of logical nodes and allocations. Nodes represent the hardware where the compute workloads run. Allocations are the individual compute jobs that comprise the workload. Each allocation is assigned to a node, and a node can have multiple allocations assigned to it.

All allocations in the ensemble are assigned a private IP address in the 10/8 range and are connected with a virtual private network, implemented using IP over libp2p. All allocations can reach each other through the VPN. Allocation IP addresses can be discovered internally in the ensemble using DNS: each allocation has a name and a DNS name, which by default is just the allocation name in the .internal domain.

Allocation and Node names within an ensemble must be unique. The ensemble as a whole has a globally unique ID (a randomn UUID).

Ensemble Specification

In order to deploy an ensemble, the user must specify its structure and constraints; this is done with a YAML file encoding the ensemble configuration data structure; the fields of the configuration structure are described in detail in this reference.

Fundamentally the ensemble configuration has the following structure:

  • A map of allocations, mapping allocation names to configuration for individual allocations.
  • A map of nodes, mapping node names to configuration for individual nodes.
  • A list of edges between nodes, encoding specific logical edge constraints.
  • There are additional fields in the data structure which allows us to include ssh keys and scripts in the configuration, as well as supervision strategies policies.

An allocation's configuration has the following structure:

  • The name of the allocation executor; this is the environment in which the actual compute job is executed. We currently support docker and firecracker VMs, but we plan to also support WASM and generally any sandbox/VM that makes sense for users.
  • The resources required to run the allocation, such as memory, cpu cores, gpus, and so on.
  • The execution details, which encodes the executor specific configuration of the allocation.
  • The DNS name for internal name resolution of the allocation. This can be omitted, in which case the allocation's name becomes the DNS name.
  • The list of ssh keys ton drop in the allocation, so that administrators can ssh into the allocation.
  • The list of scripts to execute during provisioning, in execution order.
  • Finally, the user can also specify the application specific health check to be performede by the supervisor, so that the health of the application can be ascertained and failures detected.

A node's configuration has the following structure:

  • The list of allocations that are assigned to the node
  • The configuration of mapping public ports to ports in allocations
  • The Location constraints for the node
  • An optional field for explicitly specifying the peer on which the node should be assigned, allowing users and organizations to bring their own nodes into the mix, for instance for hosting sensitive data.

In the near future, we also plan to support directly parsing kubernetes job description files. We also plan to provide a declarative format for specifying large ensembles so that it is possible to succinctly describe a 10k GPU ensemble for training an LLM and so on.

Ensemble Constraints

It is worth reiterating that ensembles carry with the constraints, as specified by the user. This allows the user to have finegrained control of their ensemble deployment and ensure that certain requirements are met.

In DMS v0.5 we support the following constraints:

  • Resources for an allocation, such as memory, core count, gpu details, and so on.
  • Location for nodes; the user can specify the region, city, etc all the way to choosing a particular ISP. Location constraints can also be negative, so that a node will not be deployed in certain locations e.g. because of regulatory considerations such as GPDR.
  • Edge Constraints, which specify the relationship between nodes in the allocation in terms of available bandwidth and round trip time.

In subsequent releases we plan to add additional constraints (e.g. existence of a contract, price range, explicit datacenter placement, energy sources and so on) and generalize the constraint expression language as graphs.

Ensemble Deployment

Given an ensemble specification, the core functionality of the NuNet network is to find and assign peers to nodes that satisfies the constraints of the ensemble. The system treats the deployment as a constraint satisfaction problem over permutations of available peers (compute nodes) on which the user is authorized to deploy. The process of deploying an ensemble is called orchestration. In the following we summarize how deployment orchestration is performed.

Ensemble Deployment Sequence Diagram

Ensemble deployment is initiated with a user invoking the /dms/node/deployment/new behavior on the node which is willing to run an orchestrator for them; this can be just the user's private DMS running on his laptop. The node accepting the invocation creates the orchestrator actor inside its process space, initiates the deployment orchestration, and return to the user the ensemble identifier. The user can use this identifier to poll the status of the deployment and control of the ensemble through the orchestrator actor. The user also specifies a timeout on how long the deployment process should take before declaring failure. This is simply the expiration on the message that invokes /dms/node/deployment/new.

The orchestrator then proceeds to request bids for each node in the ensemble. This is accomplished by broadcasting a message to the /dms/deployment/request behavior in the /nunet/deployment broadcast topic. The deployment request contains a mapping of node names in the ensemble, together with their aggregate (for all allocations to be assigned in the node) resource constraints, together with location and other constraints that can restrict the search space.

In order for this to proceed, the orchestrator must have the appropriate capabilities; only provider nodes that accept the user's capabilities will respond to the broadcast message. The response to the bid request is a bid for a node in the ensemble, by sending a message to the /dms/deployment/bid behavior in the orchestrator. This also implies that the nodes that submit such bids must have appropriate capabilities accepted by the orchestrator.

Given the appropriate capabilities, the orchestrator collects bids until it has a sufficient number of bids or a timeout that ensures prompt progress in the deployment. If the orchestrator doesn't have bids for all nodes, then it rebroadcasts its bid request, excluding peers that have already submitted a bid. This continues until there are bids for all nodes or the deployment times out, at which point a deployment failure is declared.

Note that in the case of node pinning, where a specific peer is assigned to an ensemble node in advance (ie when a user brings their own nodes into the ensemble), bid requests are not broadcast but rather directly invoked on the peer.

Next, the orchestrator generates permutations of assignments of peers to nodes and evaluates the constraints. Some constraints can be directly rejected without measurement, for instance round trip latency constraints can be rejected by using speed of light calculations that provide a lower bound on physically realizable latency. We plan to do the same with bandwidth constraints, given the node measured link capacity and the throughput bound equation that governs TCP's behavior given bottleneck bandwidth and RTT.

Once a candidate assignment is deemed viable, the orchestrator proceeds to measure specific constraints for satisfiability. This involves measuring round trip time and bandwidth between node pairs, and is accomplished by invoking the /dms/deployment/constraint/edge behavior.

If a candidate assignment satisfies the constraints, the orchestrator proceeds with committing and provisioning the deployment. This is done with a two phase commit process: first the orchestrator sends a commit message to all peers to ensure that the resources are still available (nodes don't lock resources when submitting a bid), by invoking the /dms/deployment/commit behavior. If any node fails to commit, the candidate deployment is reverted and the orchestrator starts anew; revert happens with the /dms/deployment/revert behavior.

If all nodes successfully commit, the orchestrator proceeds to provision the deployment by sending allocation details to the relevant nodes and creating the VPN. This is initiated by invoking the /dms/deployment/allocate behavior on the provider nodes, which creates a new allocation actor. Subsequently, the orchestrator assigns IP addresses to allocations and creates the VPN (what we call the subnet) by invoking the appropriate behaviors on the allocation actors, and then starts the allocations. Once all nodes provision, the deployment is now considered running and enters supervision.

The deployment will keep running until the user shuts it down, as long as the user's agreement with the provider is active; in the near future we will also support explicitly specifying durations for running ensembles, and the ability to modify running ensembles in order to support mechanisms like auto scaling.

Ensemble Supervision

TODO

Deploying in the NuNet Network

In order to discuss authorization flow for deployment in the NuNet network, we need to distinguish certain actors in the system in the course of an ensembles lifetime.

Specifically, we introduce the following notation:

  • Let's call U, the user as an actor.
  • Let's call O the orchestrator, which is an actor living inside a DMS instance (node) for which the user is authorized to initiate a deployment. We call the node where the orchestrator runs N_o. Note that the DID of the orchestrator actor will be the same as the DID of the node on which it runs, but it will have an ephemeral actor ID.
  • Let's call P_i the set of compute providers that are willing to accept deployment requests from U.
  • Let's call N_{P_i,j} the DMS nodes controlled by the providers that are willing to accept deployments from users.
  • And finally let's call A_i the allocation actor for each running allocation. The DID of each allocation actor will be the same as the DID of the node on which the allocation is running, but it will have an ephemeral actor ID.

Also note that we have certain identifiers pertaining to these actors; let's define the following notation:

  • DID(x) is the DID of actor x; in general this is the DID that identifies the node on which the actor is running.
  • ID(x) is the ID of actor x; this is generally ephemeral, except for node root actors which have persistent identities matching their DID.
  • Peer(x) is the peer ID of a node/actor x.
  • Root(x) is the DID of the root anchor of trust for the node/actor x.
Behaviors and Capabilities

Using the notation above we can enumerate the behavior namespaces and requisite capabilities for deployment of an ensemble:

  • Invocations from U to N_o are in the /dms/node/deployment namespace
  • Invocations from O to N_{P_i,j} for deployment bids:
    • broadcast /dms/deployment/request via the /nunet/deployment topic
    • unicast /dms/deployment/request for pinned ensemble nodes
  • Messages from N_{P_i,j} to O:
    • /dms/deployment/bid as the reply to a bid request
  • Invocations from O to N_{P_i,j} for deployment control are in the /dms/deployment namespace.
  • Invocations from O to A_i are in the /dms/allocation namespace and are dynamically granted programmatically.
  • Invocations from O to N_{P_i,j} for allocation control are in the dynamic /dms/ensemble/<ensemble-id> namespace and are dynamically granted programatically.

This creates the following structure:

  • U must be authorized with /dms/node/deployment capability in N_o
  • N_o must be authorized with /dms/deployment capability in N_{P_i,j} so that the orchestrator can make the appropriate invocations.
  • N_{P_i,j} must be authorized with /dms/deployment/bid capability on N_o so that it can submit bids to the orchestrator.

Note that the decentralized structure and fine grained capability model of the NuActor system allows for very tight access control. This ensures that:

  • Orchestrators can only run on DMS instances where the user is authorized to initiate deployment.
  • Bid requests will only be accepted by provider DMS instances where the user is authorized to deploy.
  • Bids will only be accepted by provider DMS instances whom the user has authorized.

In the following we examine common functional scenarios on how to set up the system so that deployments are properly authorized.

Deploying in a Private Network

TODO

Deploying in a Restricted Network

TODO

Authorizing a Third Party to Vet Users

TODO

Distributing and Revoking Capability Tokens

TODO

Public Deployment

TODO

Documentation

Overview

Copyright 2024, Nunet Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

View Source
const (
	EnsembleNamespace   = "/dms/ensemble/%s"
	AllocationNamespace = "/dms/allocation"
	NodeNamespace       = "/dms/node"
)
View Source
const (
	BidRequestTopic    = "/nunet/deployment"
	BidRequestBehavior = "/dms/deployment/request"
	BidRequestTimeout  = 5 * time.Second
	BidReplyBehavior   = "/dms/deployment/bid"

	VerifyEdgeConstraintBehavior = "/dms/deployment/constraint/edge"
	VerifyEdgeConstraintTimeout  = 5 * time.Second

	CommitDeploymentBehavior     = "/dms/deployment/commit"
	CommitDeploymentTimeout      = 3 * time.Second
	AllocationDeploymentBehavior = "/dms/deployment/allocate"
	AllocationDeploymentTimeout  = 5 * time.Second
	RevertDeploymentBehavior     = "/dms/deployment/revert"

	AllocationStartBehavior   = "/dms/allocation/start"
	AllocationRestartBehavior = "/dms/allocation/restart"
	AllocationStartTimeout    = 5 * time.Second
	AllocationShutdownTimeout = 5 * time.Second

	MinEnsembleDeploymentTime = 15 * time.Second

	MaxBidMultiplier = 8

	SubnetCreateTimeout  = 2 * time.Minute
	SubnetDestroyTimeout = 30 * time.Second
)
View Source
const (
	DeploymentStatusPreparing    = job_types.DeploymentStatusPreparing
	DeploymentStatusGenerating   = job_types.DeploymentStatusGenerating
	DeploymentStatusCommitting   = job_types.DeploymentStatusCommitting
	DeploymentStatusProvisioning = job_types.DeploymentStatusProvisioning
	DeploymentStatusRunning      = job_types.DeploymentStatusRunning
	DeploymentStatusFailed       = job_types.DeploymentStatusFailed
	DeploymentStatusShuttingDown = job_types.DeploymentStatusShuttingDown
	DeploymentStatusCompleted    = job_types.DeploymentStatusCompleted
)
View Source
const MaxPermutations = 1_000_000

Variables

View Source
var (
	SubnetCreateBehavior = types.Behavior{
		DynamicTemplate: EnsembleNamespace + "/node/subnet/create",
		Static:          NodeNamespace + "/subnet/create",
	}
	SubnetDestroyBehavior = types.Behavior{
		DynamicTemplate: EnsembleNamespace + "/node/subnet/destroy",
		Static:          NodeNamespace + "/subnet/destroy",
	}

	RegisterHealthcheckBehavior = "/dms/actor/healthcheck/register"

	SubnetAddPeerBehavior         = AllocationNamespace + "/subnet/add-peer"
	SubnetRemovePeerBehavior      = AllocationNamespace + "/subnet/remove-peer"
	SubnetAcceptPeerBehavior      = AllocationNamespace + "/subnet/accept-peer"
	SubnetMapPortBehavior         = AllocationNamespace + "/subnet/map-port"
	SubnetUnmapPortBehavior       = AllocationNamespace + "/subnet/unmap-port"
	SubnetDNSAddRecordsBehavior   = AllocationNamespace + "/subnet/dns/add-records"
	SubnetDNSRemoveRecordBehavior = AllocationNamespace + "/subnet/dns/remove-record"

	AllocationLogsBehavior     = EnsembleNamespace + "/allocation/logs"
	AllocationShutdownBehavior = EnsembleNamespace + "/allocation/shutdown"
)
View Source
var (
	ErrProvisioningFailed = errors.New("failed to provision the ensemble")
	ErrDeploymentFailed   = errors.New("failed to create deployment")
	ErrTODO               = errors.New("TODO")
)

Functions

func EnsembleIDFromBehavior

func EnsembleIDFromBehavior(b string) (string, error)

Types

type Allocation

type Allocation struct {
	ID string

	Actor actor.Actor

	Job Job
	// contains filtered or unexported fields
}

Allocation represents an allocation

func NewAllocation

func NewAllocation(
	id string,
	fs afero.Afero,
	dmsConfig config.Config,
	actor actor.Actor,
	details AllocationDetails,
	network network.Network,
) (*Allocation, error)

NewAllocation creates a new allocation given the actor.

func (*Allocation) Cleanup

func (a *Allocation) Cleanup() error

func (*Allocation) ExecutionID

func (a *Allocation) ExecutionID() string

func (*Allocation) GetPortMapping

func (a *Allocation) GetPortMapping() map[int]int

GetPortMapping returns allocation's port mapping

func (*Allocation) Restart

func (a *Allocation) Restart(ctx context.Context) error

func (*Allocation) Run

func (a *Allocation) Run(ctx context.Context, subnetIP string, gatewayIP string, portMapping map[int]int) error

Run creates the executor based on th e execution engine configuration.

func (*Allocation) SetHealthCheck

func (a *Allocation) SetHealthCheck(f func() error)

func (*Allocation) Start

func (a *Allocation) Start() error

Start the actor of the allocation.

func (*Allocation) Status

func (a *Allocation) Status(_ context.Context) Status

Status returns information about the allocated/usage of resources and execution status of workload.

func (*Allocation) Stop

func (a *Allocation) Stop(ctx context.Context) error

Stop stops the running executor and the allocation actor

func (*Allocation) Terminate

func (a *Allocation) Terminate(ctx context.Context) error

Terminate stops the allocation and cleans up after

type AllocationDeploymentConfig

type AllocationDeploymentConfig struct {
	Executor         AllocationExecutor
	Resources        types.Resources
	Execution        types.SpecConfig
	ProvisionScripts map[string][]byte
}

type AllocationDeploymentRequest

type AllocationDeploymentRequest struct {
	EnsembleID  string
	NodeID      string
	Allocations map[string]AllocationDeploymentConfig
}

type AllocationDeploymentResponse

type AllocationDeploymentResponse struct {
	OK          bool
	Error       string
	Allocations map[string]actor.Handle
}

type AllocationDetails

type AllocationDetails struct {
	Job      Job
	NodeID   string
	SourceID string
}

AllocationDetails encapsulates the dependencies to the constructor.

type AllocationExecutor

type AllocationExecutor = job_types.AllocationExecutor

type AllocationLogsRequest

type AllocationLogsRequest struct {
	AllocName string
}

type AllocationLogsResponse

type AllocationLogsResponse struct {
	Stdout []byte
	Stderr []byte
	Error  string
}

type AllocationManifest

type AllocationManifest = job_types.AllocationManifest

type AllocationRestartResponse

type AllocationRestartResponse struct {
	OK    bool
	Error string
}

type AllocationShutdownRequest

type AllocationShutdownRequest struct {
	AllocationID string
}

type AllocationShutdownResponse

type AllocationShutdownResponse struct {
	OK    bool
	Error string
}

type AllocationStartRequest

type AllocationStartRequest struct {
	SubnetIP    string
	GatewayIP   string
	PortMapping map[int]int
}

type AllocationStartResponse

type AllocationStartResponse struct {
	OK    bool
	Error string
}

type AllocationStatus

type AllocationStatus string

AllocationStatus is a representation of the execution status

const (
	Pending    AllocationStatus = "pending"
	Running    AllocationStatus = "running"
	Stopped    AllocationStatus = "stopped"
	Failed     AllocationStatus = "failed"
	Completed  AllocationStatus = "completed"
	Terminated AllocationStatus = "terminated"
)

type AllocationStopRequest

type AllocationStopRequest struct {
	AllocationID string
}

type AllocationStopResponse

type AllocationStopResponse struct {
	OK    bool
	Error string
}

type Bid

type Bid = job_types.Bid

type BidRequest

type BidRequest = job_types.BidRequest

type CommitDeploymentRequest

type CommitDeploymentRequest struct {
	EnsembleID     string
	AllocationName string
	NodeID         string
	Resources      types.Resources
	PortMapping    map[int]int
}

type CommitDeploymentResponse

type CommitDeploymentResponse struct {
	OK    bool
	Error string
}

type Coordinate

type Coordinate struct {
	// contains filtered or unexported fields
}

func (*Coordinate) Empty

func (c *Coordinate) Empty() bool

type DeploymentSnapshot

type DeploymentSnapshot = job_types.DeploymentSnapshot

type DeploymentStatus

type DeploymentStatus = job_types.DeploymentStatus

type EdgeConstraint

type EdgeConstraint = job_types.EdgeConstraint

type EnsembleBidRequest

type EnsembleBidRequest = job_types.EnsembleBidRequest

type EnsembleConfig

type EnsembleConfig = job_types.EnsembleConfig

type EnsembleManifest

type EnsembleManifest = job_types.EnsembleManifest

type GeoLocator

type GeoLocator struct {
	// contains filtered or unexported fields
}

func NewGeoLocator

func NewGeoLocator() (*GeoLocator, error)

func (*GeoLocator) Coordinate

func (geo *GeoLocator) Coordinate(loc Location) (Coordinate, error)

type HealthCheckResponse

type HealthCheckResponse struct {
	OK    bool
	Error string
}

type Job

type Job struct {
	Resources        types.Resources
	Execution        types.SpecConfig
	ProvisionScripts map[string][]byte
}

type Location

type Location = job_types.Location

type LocationConstraints

type LocationConstraints = job_types.LocationConstraints

type NodeConfig

type NodeConfig = job_types.NodeConfig

type NodeManifest

type NodeManifest = job_types.NodeManifest

type Orchestrator

type Orchestrator struct {
	// contains filtered or unexported fields
}

func NewOrchestrator

func NewOrchestrator(
	ctx context.Context,
	id string,
	actor actor.Actor,
	network network.Network,
	cfg EnsembleConfig,
) (*Orchestrator, error)

func RestoreDeployment

func RestoreDeployment(
	actr actor.Actor, net network.Network, id string,
	cfg EnsembleConfig, manifest EnsembleManifest,
	status DeploymentStatus, restoreInfo DeploymentSnapshot,
) (*Orchestrator, error)

Restore restores deployments where the status is either provisioning, committing or running

func (*Orchestrator) ActorPrivateKey

func (o *Orchestrator) ActorPrivateKey() crypto.PrivKey

func (*Orchestrator) Config

func (o *Orchestrator) Config() EnsembleConfig

func (*Orchestrator) Deploy

func (o *Orchestrator) Deploy(expiry time.Time) error

func (*Orchestrator) DeploymentSnapshot

func (o *Orchestrator) DeploymentSnapshot() DeploymentSnapshot

func (*Orchestrator) GetAllocationLogs

func (o *Orchestrator) GetAllocationLogs(name string) (AllocationLogsResponse, error)

func (*Orchestrator) ID

func (o *Orchestrator) ID() string

func (*Orchestrator) Manifest

func (o *Orchestrator) Manifest() EnsembleManifest

func (*Orchestrator) Shutdown

func (o *Orchestrator) Shutdown()

func (*Orchestrator) Status

func (o *Orchestrator) Status() DeploymentStatus

func (*Orchestrator) Stop

func (o *Orchestrator) Stop()

type OrchestratorView

type OrchestratorView = job_types.OrchestratorView

type RegisterHealthcheckRequest

type RegisterHealthcheckRequest struct {
	EnsembleID  string
	HealthCheck types.HealthCheckManifest
}

type RegisterHealthcheckResponse

type RegisterHealthcheckResponse struct {
	OK    bool
	Error string
}

type RevertDeploymentMessage

type RevertDeploymentMessage struct {
	EnsembleID   string
	AllocsByName []string
}

type Status

type Status struct {
	JobResources types.Resources
	Status       AllocationStatus
}

Status holds the status of an allocation.

type SubnetAcceptPeerRequest

type SubnetAcceptPeerRequest struct {
	SubnetID string
	PeerID   string
	IP       string
}

type SubnetAcceptPeerResponse

type SubnetAcceptPeerResponse struct {
	OK    bool
	Error string
}

type SubnetAddPeerRequest

type SubnetAddPeerRequest struct {
	SubnetID string
	PeerID   string
	IP       string
}

type SubnetAddPeerResponse

type SubnetAddPeerResponse struct {
	OK    bool
	Error string
}

type SubnetCreateRequest

type SubnetCreateRequest struct {
	SubnetID     string
	IP           string
	RoutingTable map[string]string
}

type SubnetCreateResponse

type SubnetCreateResponse struct {
	OK    bool
	Error string
}

type SubnetDNSAddRecordsRequest

type SubnetDNSAddRecordsRequest struct {
	SubnetID string
	// map of domain name:ip
	Records map[string]string
}

type SubnetDNSAddRecordsResponse

type SubnetDNSAddRecordsResponse struct {
	OK    bool
	Error string
}

type SubnetDNSRemoveRecordRequest

type SubnetDNSRemoveRecordRequest struct {
	SubnetID   string
	DomainName string
}

type SubnetDNSRemoveRecordResponse

type SubnetDNSRemoveRecordResponse struct {
	OK    bool
	Error string
}

type SubnetDestroyRequest

type SubnetDestroyRequest struct {
	SubnetID string
}

type SubnetDestroyResponse

type SubnetDestroyResponse struct {
	OK    bool
	Error string
}

type SubnetMapPortRequest

type SubnetMapPortRequest struct {
	SubnetID   string
	Protocol   string
	SourceIP   string
	SourcePort string
	DestIP     string
	DestPort   string
}

type SubnetMapPortResponse

type SubnetMapPortResponse struct {
	OK    bool
	Error string
}

type SubnetRemovePeerRequest

type SubnetRemovePeerRequest struct {
	IP       string
	SubnetID string
	PeerID   string
}

type SubnetRemovePeerResponse

type SubnetRemovePeerResponse struct {
	OK    bool
	Error string
}

type SubnetUnmapPortRequest

type SubnetUnmapPortRequest struct {
	SubnetID   string
	Protocol   string
	SourceIP   string
	SourcePort string
	DestIP     string
	DestPort   string
}

type SubnetUnmapPortResponse

type SubnetUnmapPortResponse struct {
	OK    bool
	Error string
}

type VerifyEdgeConstraintRequest

type VerifyEdgeConstraintRequest struct {
	EnsembleID string // the ensemble identifier
	S, T       string // the peer IDs of the edge S->T
	RTT        uint   //  maximum RTT in ms (if > 0)
	BW         uint   // minim BW in Kbps
}

type VerifyEdgeConstraintResponse

type VerifyEdgeConstraintResponse struct {
	OK    bool
	Error string
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳