RackConnect v3.0 load balancer add/remove steps

This adds `IStep` implementations to `otter.convergence` for adding nodes to and removing them from RackConnect v3.0.

Furthermore, it generalized the optimizer step that @radix added to easily be extensible for multiple classes. Maybe one day we'll have to do multi-pass optimization; today is not that day :smile:

There are some `REVIEW` comments inline.

RCv3 supports bulk add/delete steps that allow adding or deleting any combination of nodes and servers in a single request, e.g. the following server, LB pairs can all be added and removed within a single request:

```clojure
[[:a :A]
 [:a :B]
 [:b :B]
 [:c :B]
 [:d :D]]
```

It stands to reason we'd want to use those requests, so that's what the optimizer produces. However, CLB does not support the same "n x n" API. In order to make this future-proof, I've added steps that mimic CLB's API. That way, the convergence code that produces load balancer steps doesn't have to care an awful lot whether it's working with RCv3 or CLB. The optimizer step is repsonsible for turning the intent specified by those steps into the most optimal representation as HTTP requests.
Oh, yes, I missed that point, but I think the same concept can apply -- if there is *only* AddNodesToRCv3LoadBalancers, then you don't need a multi-pass optimizer, and your optimizer function simply merges instances of AddNodesToRCv3LoadBalancers. The converge function that produces them would just need to instantiate a plural instead of a singular, and pass lb_node_pairs=s((lb_id, node_id)) -- only one lb/node pair.