rust-openstack



Dmitry Tantsur (Principal Software Engineer, Red Hat)

Slides: dtantsur.github.io/talks/berlin-rust-openstack

OpenStack

Free software providing implementing IaaS.

Written mostly in Python, actively reusing other FOSS components.

Consists of many loosely coupled services.

Each provides (mostly) RESTful HTTP API.

rust-openstack

rust-openstack is SDK for OpenStack API.

Provides high-level abstractions for talking to OpenStack.

Wraps HTTP endpoints and JSON objects in a meaninful way.

How it started

A simple recipe:

  1. I know OpenStack (I do it for living)
  2. I care about API design (member of API SIG)
  3. I need to practice Rust

Key technologies

  • reqwest - for HTTP(s) access
  • serde - for handling JSON

Must-have for standard library:

  • log - logging facility
  • fallible-iterator

Wins

Static typing allows to fix my knowledge of how OpenStack API actually works in code.

reqwest feature set.

#[derive(Serialize, Deserialize)] and field attributes.

Macros for generating typical code. Currently 7 of them, around 270 loc totally.

Struggles

Library choice

serde vs rustc-serialize is confusing.

Started with hyper, had to switch away when it became too low-level and asynchronous.

tokyo/futures too hard to use.

Struggles

Futures

In Python Twisted a future is an object similar to Result. You can store it or pass around.

in Rust Future is a trait. Ended up with extensive use of trait objects to avoid dealing with very long generics. From hyper:

Box<Future<Item=Response<Body>, Error=::Error> + Send>

Struggles

Missing in standard library

  • log macros and basics.
  • Types of subnets (ipnet).
  • Iterators that can fail (fallible-iterator).
  • Serialize/Deserialize traits.

Struggles

JSON to static types

Some API use null for missing value, some - empty string. Even for non-string fields.

Some API distinguish between null and absent field.

Some field differ a lot depending on API version.

Some fields are mandatory when reading but not when writing.

Some fields cannot be changed after creating. Some of them are mandatory when creating.

Some fields have different type when reading and writing.

Struggles

serde and JSON API

No way to vary presence and/or type of a field, except for using enums.

No way to (de)serialize based on version, except for using enums for fields.

Currently 7 (de)serialization helpers.

Example

#[serde(deserialize_with = "common::protocol::empty_as_none",
        default, skip_serializing_if = "Option::is_none")]
pub dns_name: Option<String>,

#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fixed_ips: Vec<FixedIp>,

#[serde(skip_serializing)]
pub id: String,

#[serde(skip_serializing_if = "MacAddress::is_nil",
        serialize_with = "common::protocol::ser_mac")]
pub mac_address: MacAddress,

Also: serde rocks!

Macros

Used to generate a lot of boilerplate code, mostly property-style accessors from top-level objects to low-level protocol structures.

Hard to debug when something goes really wrong.

Hygienic macros cannot be used when you need to insert some code into the middle of other code.

Also annoying that one cannot generate function names like fn set_$name(..)

Summary

Writing Rust is lot of fun!

High-quality libraries available, standard library is great, but lacks a few things.

Macros are great, let's have more and more powerful :)

Questions?