Iris through Go... API v1

Bringing simplicity back to distributed services

Péter Szilágyi

European Institute for Innovation and Technology

Eötvös Loránd University, Budapest, Hungary

Babeş-Bolyai University, Cluj-Napoca, Romania

Note, these are the offline slides of the presentation. For executable codes, please check playground availability at http://iris.karalabe.com/talks.

What the heck is Iris?

Decentralized cloud messaging

I.e. If starting a batch of VMs on Google Compute Engine is a one liner,

gcutil addinstance vm-1 vm-2 ... vm-N

Assembling them into a distributed system should be a one liner too!

iris -net <service name> -rsa <private key>

What can it do?

It communicates, of course!

What makes it special? Simplicity!

Instances consolidated based on responsibility

Clusters are the smallest logical units

⊕ Harder to abuse, fewer moving components, self organization

Meaningful and non-dynamic addressing

Routing based on semantic addressing

⊕ Implicit failovers, automatic load balancing, simpler client code

Implicit security while trusting the trustworthy

Security at service level

⊕ Decoupled system, optimized encryption, out of the box

Show me the code!

Challenge #0 – Boilerplate

Assemble a network of micro services:

Solution #0 – Register

// Callback handler for inbound events
type servicedemo struct{}

// Handler initialization, invoked after successful registration
func (s *servicedemo) Init(conn *iris.Connection) error { return nil }

func main() {
    // Register a micro-service instance into the network
    service, err := iris.Register(55555, "Gopher Service", new(servicedemo), nil)
    if err != nil {
        fmt.Println("Failed to register service:", err); return
    }
    defer service.Unregister()
}

// Remaining callbacks methods, not used in this demo
func (s *servicedemo) HandleBroadcast(msg []byte)               { panic("Not implemented!") }
func (s *servicedemo) HandleRequest(msg []byte) ([]byte, error) { panic("Not implemented!") }
func (s *servicedemo) HandleTunnel(tunn *iris.Tunnel)           { panic("Not implemented!") }
func (s *servicedemo) HandleDrop(reason error)                  { panic("Not implemented!") }

Note: Connecting as a simple client is also supported.

Register highlights

Register(port int, cluster string, handler ServiceHandler, limits ServiceLimits) → Service
func (iris.ServiceHandler) Init (connection *iris.Connection)

Challenge #1 – Web requests

Simulate a system for handling web requests:

Solution #1 – Browser

// Connect to the network as a simple client
connection, err := iris.Connect(55555)
if err != nil {
    fmt.Println("Failed to connect:", err); return
}
defer connection.Close()

// Issue a dummy request every second
for i := 1; i <= 60; i++ {
    request := []byte(fmt.Sprint("Request #", i))

    reply, err := connection.Request("webserver", request, time.Second)
    if err != nil {
        fmt.Println("Request failed:", err)
    } else {
        fmt.Println("Web reply:", string(reply))
    }
    time.Sleep(time.Second)
}

Hint: Start some webservers and check back 😉

Solution #1 – Web server

// Format each request a bit and return as the reply
func (w *webserver) HandleRequest(request []byte) ([]byte, error) {
    return []byte(fmt.Sprint("go-www-", w.id, ": ", string(request))), nil
}

func main() {
    // Register a webserver micro-service into the network
    service, err := iris.Register(55555, "webserver", &webserver{id: rand.Intn(100)}, nil)
    if err != nil {
        fmt.Println("Failed to register micro-service:", err); return
    }
    defer service.Unregister()

    fmt.Println("Waiting for inbound requests...")
    time.Sleep(60 * time.Second)
}

The presentation supports only one active demo process per window. Open new tab?

Request / Reply highlights

func (iris.Connection) Request(cluster string, request []byte, timeout time.Duration) → []byte
func (iris.ServiceHandler) HandleRequest(request []byte) → []byte

Challenge #2 – Aperture Science Enrichment Center 😈

Implement the comlink for Aperture Laboratories¹:

¹ http://en.wikipedia.org/wiki/Portal_(video_game)

Solution #2 – GLaDOS

// Connect to the Iris network as GLaDOS
connection, err := iris.Connect(55555)
if err != nil {
    fmt.Println("Failed to connect:", err); return
}
defer connection.Close()

fmt.Println("GLaDOS is online, sending wishes...")
for {
    // Pick a random wish from hidden 'wishes' array
    wish := wishes[rand.Intn(len(wishes))]

    connection.Publish("official", []byte("GLaDOS: " + wish))
    time.Sleep(5 * time.Second)
}

Hint: Boot GLaDOS and let the experiment begin 😉

Solution #2 – Chell

// Topic subscription handler processing inbound events
type chell struct{}
func (c *chell) HandleEvent(event []byte) {
    fmt.Printf("%s\n\n", string(event))
}

func main() {
    // Connect to the Iris network as Chell
    connection, err := iris.Connect(55555)
    if err != nil {
        fmt.Printf("Failed to connect: %v\n", err); return
    }
    defer connection.Close()

    fmt.Println("Tuning in to Aperture channels...")
    connection.Subscribe("official", new(chell), nil)

    time.Sleep(time.Minute)
}

Hint: Maybe there is an "unofficial" channel? 😉

Publish / Subscribe highlights

func (iris.Connection) Subscribe(topic string, handler TopicHandler, limits TopicLimits)
func (iris.Connection) Publish(topic string, event []byte)
func (iris.TopicHandler) HandleEvent(event []byte)

Challenge #3 – Data repository

Implement a data distribution system:

Solution #3 – Client

// Open an outbound tunnel to a data store
tunnel, err := connection.Tunnel("repository", time.Second)
if err != nil {
    fmt.Println("Tunneling failed:", err); return
}
defer tunnel.Close()

// Request a file and retrieve the multi-part response
tunnel.Send([]byte("some file"), time.Second)
for {
    msg, err := tunnel.Recv(time.Second)
    if err != nil {
        break
    }
    fmt.Println(string(msg))
}

Hint: Start some data repositories 😉

Solution #3 – Data store

// ServiceHandler callback, invoked when a tunnel is inbound
func (d *datastore) HandleTunnel(tunnel *iris.Tunnel) {
    // Make sure tunnel is cleaned up
    defer tunnel.Close()

    // Fetch the file name
    name, _ := tunnel.Recv(time.Second)

    // Simulate sending some multi-part data stream
    for i := 1; i <= 10; i++ {
        part := fmt.Sprintf("Go repo #%d: <%s> part #%d", d.id, string(name), i)
        tunnel.Send([]byte(part), time.Second)
    }
}

The presentation supports only one active demo process per window. Open new tab?

Tunnel highlights

func (iris.Connection) Tunnel(cluster string, timeout time.Duration) → iris.Tunnel
func (iris.Tunnel) Send(message []byte, timeout time.Duration)
func (iris.Tunnel) Recv(timeout time.Duration) → []byte

How does this all work?

Sneak behind the scenes

Iris nodes do the heavy lifting (one/host):

Thin clients bathe in the glory:

Where to go next?

Iris resources

Iris community:

Thank you

Péter Szilágyi

European Institute for Innovation and Technology

Eötvös Loránd University, Budapest, Hungary

Babeş-Bolyai University, Cluj-Napoca, Romania