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.
Non-blocking clarification - just wondering about the code in `converge` (or called by `converge`) that generates this.  The code that generates `AddNodeToLoadBalancer` (CLB) steps currently does this one at a time, so I was wondering if the plan was to do per-LB aggregation within the converge function itself, rather than in the optimize step?

If not, and a dummy singular step was desired, would it make sense to either have this take one `lb_id` and one `node_id`, and leave the aggregation in the bulk multi-node multi-lb step?