├── .gitignore ├── .travis.yml ├── README.md ├── project.clj ├── src └── clj_mesos │ ├── executor.clj │ ├── marshalling.clj │ └── scheduler.clj └── test └── clj_mesos └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-mesos 2 | 3 | A Clojure library that provides integration with Mesos via the Java API. 4 | 5 | [![Build Status](https://travis-ci.org/dgrnbrg/clj-mesos.svg)](https://travis-ci.org/dgrnbrg/clj-mesos) 6 | 7 | ## Usage 8 | 9 | This library has the exact same functions and callbacks as the [Java Mesos API](http://mesos.apache.org/api/latest/java/), 10 | except for the Log API. You can declare an instance of a Scheduler or an Executor with a proxy-like interface: 11 | 12 | ```clojure 13 | (def myscheduler 14 | (clj-mesos.scheduler/scheduler (registered [driver fid mi] 15 | (println "registered" fid mi)) 16 | (resourceOffers [driver offers] 17 | (clojure.pprint/pprint offers)))) 18 | ``` 19 | 20 | Any unimplemented callbacks will just be noops. 21 | 22 | To use the Scheduler or Executor, you can create a driver and use the functions to activate it: 23 | 24 | ```clojure 25 | ;; Create a driver 26 | (def overdriver 27 | (clj-mesos.scheduler/driver 28 | myscheduler {:user "" :name "testframework"} "localhost:5050")) 29 | 30 | ;; Call a function on the driver 31 | (clj-mesos.scheduler/start overdriver) 32 | (clj-mesos.scheduler/stop overdriver)) 33 | (clj-mesos.scheduler/revive-offers) 34 | ``` 35 | 36 | Note that the functions you call on the driver are using normal Clojure case rules: all lowercase, 37 | with `-`s separating the words. On the other hand, the callbacks use the Java camelCase standard. 38 | 39 | ## Stability 40 | 41 | clj-mesos has been used to support a major production workload since June 2014. 42 | 43 | ## License 44 | 45 | Copyright © 2013 David Greenberg 46 | 47 | Distributed under the Eclipse Public License, the same as Clojure. 48 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-mesos "0.22.3-SNAPSHOT" 2 | :description "A fully-featured Mesos binding for Clojure" 3 | :url "http://github.com/dgrnbrg/clj-mesos" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [com.google.protobuf/protobuf-java "2.5.0"] 8 | [org.clojure/tools.logging "0.2.6"] 9 | [org.apache.mesos/mesos "0.22.1"]] 10 | :global-vars {*warn-on-reflection* true} 11 | :jvm-opts ["-XX:-OmitStackTraceInFastThrow" "-Xcheck:jni"] 12 | :deploy-repositories [["releases" :clojars]] 13 | :repositories {"apache-releases" "http://repository.apache.org/content/repositories/releases/"}) 14 | -------------------------------------------------------------------------------- /src/clj_mesos/executor.clj: -------------------------------------------------------------------------------- 1 | (ns clj-mesos.executor 2 | (:require [clojure.reflect :as reflect] 3 | [clojure.string :as str]) 4 | (:use clj-mesos.marshalling)) 5 | 6 | (defdriver org.apache.mesos.ExecutorDriver) 7 | 8 | (defmacro executor 9 | [& fns] 10 | (make-proxy-body 'org.apache.mesos.Executor fns)) 11 | 12 | (alter-meta! #'executor assoc :doc (str "methods: " (str/join " " (map :name (:members (reflect/reflect org.apache.mesos.Executor)))))) 13 | 14 | (defn driver 15 | [executor] 16 | (org.apache.mesos.MesosExecutorDriver. executor)) 17 | -------------------------------------------------------------------------------- /src/clj_mesos/marshalling.clj: -------------------------------------------------------------------------------- 1 | (ns clj-mesos.marshalling 2 | (:require [clojure.string :as str] 3 | [clojure.tools.logging :as log] 4 | [clojure.reflect :as reflect]) 5 | (:import [org.apache.mesos 6 | Scheduler])) 7 | 8 | (defn clojurify-name 9 | "Converts from camelCase or TITLE_CASE to clojure-case" 10 | [name] 11 | (-> 12 | (if (every? #(Character/isUpperCase %) (filter #(Character/isLetter %) name)) 13 | ;;Handle TITLE_CASE 14 | (str/lower-case name) 15 | ;;Handle camelCase 16 | (loop [[first :as name] name result ""] 17 | (if (not= name "") 18 | (recur (.substring name 1) 19 | (str result 20 | (if (Character/isUpperCase first) 21 | (str \- (Character/toLowerCase first)) 22 | first))) 23 | result))) 24 | (str/replace "_" "-"))) 25 | 26 | (declare proto->map) 27 | 28 | (defn- handle-value-type 29 | "Takes a value-type protocol buffer and extracts the value." 30 | [proto] 31 | (condp = (.getType proto) 32 | org.apache.mesos.Protos$Value$Type/SCALAR 33 | (.. proto getScalar getValue) 34 | org.apache.mesos.Protos$Value$Type/SET 35 | (set (.. proto getSet getItemList)) 36 | org.apache.mesos.Protos$Value$Type/TEXT 37 | (.. proto getText getValue) 38 | org.apache.mesos.Protos$Value$Type/RANGES 39 | (mapv proto->map (.. proto getRanges getRangeList)) 40 | (throw (ex-info "Unknown value" {})))) 41 | 42 | (defn proto->map 43 | "Takes a protocol buffer and converts it to a map." 44 | [proto] 45 | (cond 46 | ;; Handle enums, since they're not composite 47 | (.. proto getClass isEnum) 48 | (-> proto .name clojurify-name keyword) 49 | 50 | (instance? com.google.protobuf.Descriptors$EnumValueDescriptor proto) 51 | (-> proto .getName clojurify-name keyword) 52 | 53 | (.. proto getClass isPrimitive) 54 | proto 55 | 56 | (#{Boolean Long Short Character Double Float} (.getClass proto)) 57 | proto 58 | 59 | :else 60 | (let [fields (seq (.getAllFields proto))] 61 | (cond 62 | ;; Mesos tagged values are special 63 | (= "mesos.Value" (.. proto getDescriptorForType getFullName)) 64 | (handle-value-type proto) 65 | (= "mesos.Attribute" (.. proto getDescriptorForType getFullName)) 66 | {:name (.getName proto) :value (handle-value-type proto)} 67 | (= "mesos.Resource" (.. proto getDescriptorForType getFullName)) 68 | {:name (.getName proto) :value (handle-value-type proto)} 69 | ;; Hack for nice environment variable handling 70 | (= "mesos.Environment" (.. proto getDescriptorForType getFullName)) 71 | (into {} (map (fn [v] [(.getName v) (.getValue v)]) (.getValue (first fields)))) 72 | ;; Some Mesos values are just a single "value", which we'll treat specially 73 | (= 1 (count fields)) 74 | (let [[[_ v]] fields] v) 75 | ;; Everything else is a message, which is just a struct 76 | :else 77 | (let [processed (for [[desc v] fields 78 | :let [name (.getName desc) 79 | v (cond 80 | (or (string? v) (integer? v) (float? v)) v 81 | (instance? com.google.protobuf.ByteString v) (.toByteArray v) 82 | (and (.isRepeated desc) 83 | (#{"mesos.Resource" 84 | "mesos.Attribute" 85 | "mesos.Environment.Variable"} 86 | (.. desc getMessageType getFullName))) 87 | (->> v 88 | (map (fn [map-entry] 89 | (let [{:keys [name value]} (proto->map map-entry)] 90 | [(keyword (clojurify-name name)) value]))) 91 | (into {})) 92 | (.isRepeated desc) (mapv proto->map v) 93 | :else (proto->map v))]] 94 | [(keyword (clojurify-name name)) v])] 95 | (into {} processed)))))) 96 | 97 | (defn javaify-enum-name 98 | [n] 99 | (-> n 100 | name 101 | (str/replace "-" "_") 102 | (str/upper-case))) 103 | 104 | (defn get-descriptor 105 | [proto] 106 | (clojure.lang.Reflector/invokeStaticMethod proto "getDescriptor" (into-array []))) 107 | 108 | (defn new-builder 109 | [proto] 110 | (clojure.lang.Reflector/invokeStaticMethod proto "newBuilder" (into-array []))) 111 | 112 | (defn fieldName->class 113 | "Maps a protobuf fieldName to className. 114 | 115 | Example: 116 | (= (fieldName->className \"mesos.Status\") org.apache.mesos.Proto$Status)" 117 | [fieldName] 118 | (let [packageName "org.apache.mesos.Protos" 119 | classes (-> fieldName 120 | (str/split #"\.") 121 | (rest)) 122 | className (str/join "$" (conj classes packageName))] 123 | (Class/forName className))) 124 | 125 | (defn recursive-build 126 | "Takes a protobuf builder and a map, and recursively builds the protobuf." 127 | [builder m] 128 | (let [desc (.getDescriptorForType builder) 129 | fields (.getFields desc)] 130 | (cond 131 | (and (= 1 (count fields)) (not (.isRepeated (first fields)))) 132 | (.setField builder (first fields) m) 133 | ;; This clause allows us to use a map directly for the environment vars 134 | ;; When we want to build an environment, we need to follow the :else clause 135 | ;; But that clause requires the :variables repeated field to be set to a map (since the fix up will handle that correctly) 136 | ;; So this clause just pushes a k/v env var map into its subfield, :variables 137 | (and (= (.getFullName desc) "mesos.Environment") (= ::no (get m :variables ::no))) 138 | (recursive-build builder {:variables m}) 139 | :else 140 | (reduce (fn [builder field] 141 | (let [name (clojurify-name (.getName field)) 142 | value (get m (keyword name) ::missing) 143 | message? (= (.getType field) com.google.protobuf.Descriptors$FieldDescriptor$Type/MESSAGE) 144 | enum? (= (.getType field) com.google.protobuf.Descriptors$FieldDescriptor$Type/ENUM) 145 | ;; Fix ups for simplicity 146 | value (cond 147 | ;; Fix up Resources and Attributes and Env Vars 148 | (and message? 149 | (#{"mesos.Attribute" "mesos.Resource" "mesos.Environment.Variable"} (.. field getMessageType getFullName))) 150 | (mapv (fn [[k v]] 151 | (let [type (cond 152 | (set? v) :set 153 | (float? v) :scalar 154 | (string? v) :value 155 | (every? #(and (contains? % :begin) (contains? % :end)) v) :ranges)] 156 | (merge 157 | {:name (clojure.core/name k) 158 | type v} 159 | (when (not= type :value) ;; Attribute or Resource 160 | {:type type})))) 161 | (if (= value ::missing) [] value)) 162 | enum? 163 | (if (= value ::missing) 164 | value 165 | (.getValueDescriptor 166 | (java.lang.Enum/valueOf 167 | (fieldName->class (.. field getEnumType getFullName)) 168 | (javaify-enum-name value)))) 169 | 170 | :else 171 | value) 172 | include 173 | (cond 174 | (.isRepeated field) 175 | (fn [value-processor] 176 | (doseq [v value] 177 | (try 178 | (.addRepeatedField builder field (value-processor v)) 179 | (catch Exception e 180 | (throw (ex-info "Could not marshall repeated field" {:field field :value v :desc desc} e))))) 181 | builder) 182 | 183 | :else 184 | (fn [value-processor] 185 | (try 186 | (.setField builder field (value-processor value)) 187 | (catch Exception e 188 | (throw (ex-info "Could not marshall field" {:field field :value value :desc desc} e))))))] 189 | (when (= value ::missing) 190 | (assert (not (.isRequired field)) (str "Missing required field " (.getName field) " in message " (.getFullName desc)))) 191 | (cond 192 | (= value ::missing) 193 | builder 194 | 195 | message? 196 | (include 197 | #(.build (recursive-build (.newBuilderForField builder field) %))) 198 | 199 | (= (.getType field ) com.google.protobuf.Descriptors$FieldDescriptor$Type/BYTES) 200 | (include #(com.google.protobuf.ByteString/copyFrom %)) 201 | 202 | :else 203 | (include identity)))) 204 | builder 205 | fields)))) 206 | 207 | (defn map->proto 208 | "Takes a protocol buffer class and a map, and converts the map into the appropriate type." 209 | [proto m] 210 | (let [desc (get-descriptor proto)] 211 | (cond 212 | (instance? com.google.protobuf.Descriptors$EnumDescriptor desc) 213 | (clojure.lang.Reflector/invokeStaticMethod proto "valueOf" (into-array [(javaify-enum-name m)])) 214 | (instance? com.google.protobuf.Descriptors$Descriptor desc) 215 | (.build (recursive-build (new-builder proto) m))))) 216 | 217 | (defn class-to-type 218 | [class-symbol] 219 | (let [name (name class-symbol) 220 | class (or ({"int" java.lang.Integer 221 | "byte<>" (Class/forName "[B") 222 | "boolean" java.lang.Boolean} name) 223 | (Class/forName name))] 224 | class)) 225 | 226 | (defn make-proxy-body 227 | [class fns] 228 | (let [impls (->> fns 229 | (map (fn [[fname & fntail]] 230 | [fname fntail])) 231 | (into {})) 232 | body (map (fn [{:keys [name parameter-types] :as signature}] 233 | (let [params parameter-types 234 | marshalling-fns 235 | (map (fn [param] 236 | (let [supers (supers (class-to-type param))] 237 | ;; TODO: refactor to use contains? to avoid the nil? special case 238 | (cond 239 | (nil? supers) 240 | ::skip 241 | (supers com.google.protobuf.AbstractMessage) 242 | `proto->map 243 | (supers java.util.Collection) 244 | `(fn [l#] (mapv proto->map l#)) 245 | :else 246 | ::skip))) 247 | params) 248 | args (or (first (get impls name)) 249 | (repeat (count marshalling-fns) '_)) 250 | marshalled-let 251 | `(let [~@(mapcat (fn [sym f] 252 | (if (= f ::skip) 253 | nil 254 | [sym (list f sym)])) 255 | args marshalling-fns)] 256 | ~@(rest (get impls name)))] 257 | `(~name [~@args] 258 | (log/debug "In callback: " ~(clojure.core/name name) "with args" ~@args) 259 | (~'try 260 | ~(if (contains? impls name) 261 | marshalled-let 262 | nil) 263 | (~'catch Throwable t# 264 | (log/error t# ~(str "Error in " (clojure.core/name name)))))))) 265 | (:members (reflect/reflect (class-to-type class))))] 266 | `(proxy [~class] [] 267 | ~@body))) 268 | 269 | (defn distinct-by 270 | [f coll] 271 | (loop [result [] 272 | seen #{} 273 | [h & t] coll] 274 | (if h 275 | (if (contains? seen (f h)) 276 | (recur result seen t) 277 | (recur (conj result h) (conj seen (f h)) t)) 278 | result))) 279 | 280 | (defn marshall-one-param 281 | "Generate the binding to marshall the given sym to the given param-type" 282 | [sym param-type erased-type] 283 | (let [type (class-to-type param-type) 284 | supers (conj (supers type) type)] 285 | (cond 286 | (contains? supers com.google.protobuf.AbstractMessage) 287 | [sym (list `map->proto param-type sym)] 288 | (contains? supers java.util.Collection) 289 | [sym `(map (partial map->proto ~erased-type) ~sym)] 290 | :else 291 | []))) 292 | 293 | (defn marshall-params 294 | "Generate a binding that marshalls the given sym to either the scalar 295 | or collection type depending on whether the symbol is a scalar or collection 296 | at runtime" 297 | [sym scalar-type collection-type] 298 | (let [[_ marshall-scalar] (marshall-one-param 299 | sym scalar-type nil) 300 | [_ marshall-collection] (marshall-one-param 301 | sym 'java.util.Collection collection-type)] 302 | [sym `(if (or (vector? ~sym) 303 | (seq? ~sym) 304 | (nil? ~sym)) 305 | ~marshall-collection 306 | ~marshall-scalar)])) 307 | 308 | (defn make-reflective-param-marshalling 309 | [name driver-type arities erased-types] 310 | (for [[arity-size arity-types] (group-by count arities)] 311 | ;; We must check that for each position, there's only one non-collection arity 312 | (do (assert (->> (apply map vector arity-types) 313 | (map (fn [ts] (remove #(= 'java.util.Collection %) ts))) 314 | (every? #(<= (count (distinct %)) 1))) 315 | "Each position must have only one distinct non-collection arity") 316 | (let [params (repeatedly arity-size gensym) 317 | driver-sym (with-meta (gensym "driver") {:tag driver-type}) 318 | param-marshalling (mapcat (fn [sym param-types collection-type] 319 | (if (= 1 (count (distinct param-types))) 320 | (marshall-one-param sym (first param-types) collection-type) 321 | ;; TODO: handle multi case 322 | (marshall-params sym (some #(and (not= 'java.util.Collection %) %) param-types) collection-type))) 323 | params (apply map vector arity-types) (concat erased-types (repeat nil))) 324 | invocation (list* `. driver-sym name params)] 325 | `([~driver-sym ~@params] 326 | (log/debug "In driver fn" ~(clojure.core/name name)) 327 | (let [~@param-marshalling] 328 | (proto->map ~invocation))))))) 329 | 330 | (defn make-reflective-fn 331 | "Takes a data structure from clojure.reflect/reflect's members and returns 332 | the syntax for a fn that invokes the function, marshalling protobufs automatically. 333 | 334 | erased-types contains the types to use for collection arguments in that 335 | argument position. At runtime, we'll check to see if the argument is a collection 336 | or not (where collection means seq, vector, or nil). If it's a collection, it'll 337 | marshall it according to the specified type. Otherwise, it'll assume that there's 338 | a single arity that we can use to marshall to. 339 | " 340 | [name driver-type arities return-type erased-types] 341 | (when (= (count (distinct (map count arities))) (count arities)) (str name " has difficult arities: " (pr-str arities))) 342 | `(defn ~(symbol (clojurify-name (clojure.core/name name))) 343 | ~(str/join "\n " (map #(str "type signature: "(str/join " " %)) arities)) 344 | ~@(make-reflective-param-marshalling name driver-type arities erased-types))) 345 | 346 | (defmacro defdriver 347 | [driver & handlers] 348 | (let [methods (->> (reflect/reflect (class-to-type driver)) :members seq (group-by :name)) 349 | handlers (apply hash-map handlers)] 350 | (cons `do 351 | (mapv (fn [[method-name methods]] 352 | (make-reflective-fn 353 | method-name 354 | driver 355 | (map :parameter-types methods) 356 | (-> methods first :return-type) 357 | (handlers method-name) 358 | #_(fn [sym] 359 | `(mapv (partial map->proto ~(handlers method-name)) ~sym)))) 360 | methods)))) 361 | -------------------------------------------------------------------------------- /src/clj_mesos/scheduler.clj: -------------------------------------------------------------------------------- 1 | (ns clj-mesos.scheduler 2 | (:require [clojure.reflect :as reflect] 3 | [clojure.string :as str]) 4 | (:use clj-mesos.marshalling)) 5 | 6 | (defdriver org.apache.mesos.SchedulerDriver 7 | launchTasks [org.apache.mesos.Protos$OfferID org.apache.mesos.Protos$TaskInfo] 8 | requestResources [org.apache.mesos.Protos$Request] 9 | reconcileTasks [org.apache.mesos.Protos$TaskStatus]) 10 | 11 | (defmacro scheduler 12 | [& fns] 13 | (make-proxy-body 'org.apache.mesos.Scheduler fns)) 14 | 15 | (alter-meta! #'scheduler assoc :doc (str "methods: " (str/join " " (map :name (:members (reflect/reflect org.apache.mesos.Scheduler)))))) 16 | 17 | (defn driver 18 | ([scheduler framework-info master] 19 | (org.apache.mesos.MesosSchedulerDriver. 20 | scheduler 21 | (map->proto org.apache.mesos.Protos$FrameworkInfo framework-info) 22 | master)) 23 | ([scheduler framework-info master credential] 24 | (org.apache.mesos.MesosSchedulerDriver. 25 | scheduler 26 | (map->proto org.apache.mesos.Protos$FrameworkInfo framework-info) 27 | master 28 | (map->proto org.apache.mesos.Protos$Credential credential)))) 29 | 30 | (comment 31 | (def myscheduler (scheduler (registered [driver fid mi] 32 | (println "registered" fid mi)) 33 | (resourceOffers [driver offers] (clojure.pprint/pprint offers)))) 34 | (def overdriver 35 | (driver 36 | myscheduler {:user "" :name "testframework"} "localhost:5050")) 37 | (start overdriver) 38 | (stop overdriver)) 39 | -------------------------------------------------------------------------------- /test/clj_mesos/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-mesos.core-test 2 | (:require [clojure.test :refer :all] 3 | [clj-mesos.marshalling :refer :all])) 4 | 5 | (deftest clojure-case 6 | (is (= "hello-world" (clojurify-name "helloWorld"))) 7 | (is (= "hello-world" (clojurify-name "HELLO_WORLD")))) 8 | 9 | (deftest pb-conversions 10 | (let [offer-id (.. (org.apache.mesos.Protos$OfferID/newBuilder) 11 | (setValue "hello") 12 | build) 13 | driver-abort org.apache.mesos.Protos$Status/DRIVER_ABORTED 14 | framework-id (.. (org.apache.mesos.Protos$FrameworkID/newBuilder) 15 | (setValue "goodbye") 16 | build) 17 | slave-id (.. (org.apache.mesos.Protos$SlaveID/newBuilder) 18 | (setValue "foo") 19 | build) 20 | offer 21 | (.. (org.apache.mesos.Protos$Offer/newBuilder) 22 | (setId offer-id) 23 | (setFrameworkId framework-id) 24 | (setSlaveId slave-id) 25 | (setHostname "localhost") 26 | (addResources (.. (org.apache.mesos.Protos$Resource/newBuilder) 27 | (setName "cpu") 28 | (setType org.apache.mesos.Protos$Value$Type/SCALAR) 29 | (setScalar (.. (org.apache.mesos.Protos$Value$Scalar/newBuilder) 30 | (setValue 12.0) 31 | build)) 32 | build)) 33 | (addResources (.. (org.apache.mesos.Protos$Resource/newBuilder) 34 | (setName "mem") 35 | (setType org.apache.mesos.Protos$Value$Type/SCALAR) 36 | (setScalar (.. (org.apache.mesos.Protos$Value$Scalar/newBuilder) 37 | (setValue 1024.0) 38 | build)) 39 | build)) 40 | build)] 41 | (is (= (proto->map offer-id) "hello")) 42 | (is (= (proto->map driver-abort) :driver-aborted)) 43 | (is (= (proto->map offer) 44 | {:id "hello", 45 | :framework-id "goodbye", 46 | :slave-id "foo", 47 | :hostname "localhost" 48 | :resources {:cpu 12.0 49 | :mem 1024.0}})) 50 | (is (= 22.22 (proto->map (.. (org.apache.mesos.Protos$Value/newBuilder) 51 | (setType org.apache.mesos.Protos$Value$Type/SCALAR) 52 | (setScalar (.. (org.apache.mesos.Protos$Value$Scalar/newBuilder) 53 | (setValue 22.22) 54 | build)) 55 | build)))) 56 | (is (= #{"hello" "world"} (proto->map (.. (org.apache.mesos.Protos$Value/newBuilder) 57 | (setType org.apache.mesos.Protos$Value$Type/SET) 58 | (setSet (.. (org.apache.mesos.Protos$Value$Set/newBuilder) 59 | (addItem "hello") 60 | (addItem "world") 61 | build)) 62 | build)))) 63 | (is (= "hello world" (proto->map (.. (org.apache.mesos.Protos$Value/newBuilder) 64 | (setType org.apache.mesos.Protos$Value$Type/TEXT) 65 | (setText (.. (org.apache.mesos.Protos$Value$Text/newBuilder) 66 | (setValue "hello world") 67 | build)) 68 | build)))) 69 | (is (= [{:begin 0 :end 5} {:begin 8 :end 10}] 70 | (proto->map (.. (org.apache.mesos.Protos$Value/newBuilder) 71 | (setType org.apache.mesos.Protos$Value$Type/RANGES) 72 | (setRanges (.. (org.apache.mesos.Protos$Value$Ranges/newBuilder) 73 | (addRange (.. (org.apache.mesos.Protos$Value$Range/newBuilder) 74 | (setBegin 0) 75 | (setEnd 5) 76 | build)) 77 | (addRange (.. (org.apache.mesos.Protos$Value$Range/newBuilder) 78 | (setBegin 8) 79 | (setEnd 10) 80 | build)) 81 | build)) 82 | build)))) 83 | (is (= {:name "cpu" :value 12.0} (proto->map (.. (org.apache.mesos.Protos$Resource/newBuilder) 84 | (setName "cpu") 85 | (setType org.apache.mesos.Protos$Value$Type/SCALAR) 86 | (setScalar (.. (org.apache.mesos.Protos$Value$Scalar/newBuilder) 87 | (setValue 12.0) 88 | build)) 89 | build)))))) 90 | 91 | (defn roundtrip-via 92 | [proto data] 93 | (is (->> data 94 | (map->proto proto) 95 | (proto->map) 96 | (= data)))) 97 | 98 | (deftest pb-conversions-back 99 | (is (= (map->proto org.apache.mesos.Protos$Value$Type :ranges) org.apache.mesos.Protos$Value$Type/RANGES)) 100 | (roundtrip-via org.apache.mesos.Protos$Offer {:id "hello", 101 | :framework-id "goodbye", 102 | :slave-id "foo", 103 | :hostname "localhost"}) 104 | (roundtrip-via org.apache.mesos.Protos$Offer {:id "hello", 105 | :framework-id "goodbye", 106 | :slave-id "foo", 107 | :hostname "localhost" 108 | :resources {:cpu 12.0 109 | :mem 1024.0} 110 | :executor-ids ["one" "two" "three"]}) 111 | (roundtrip-via org.apache.mesos.Protos$TaskInfo {:slave-id "20150911-202419-16842879-5050-1247-S0", 112 | :name "cookjob_vagrant_702213fc-4f57-4fcd-9437-d99aea7976be", 113 | :task-id "702213fc-4f57-4fcd-9437-d99aea7976be", 114 | :resources {:cpus 1.5, :mem 1000.0} 115 | :command {:value "echo hello my friend", 116 | :uris [{:value "http://google.com/index.html" 117 | :extract false}] 118 | :environment {"lol" "lol2", 119 | "foo" "bar", 120 | "baz" "quuz"}, 121 | :user "vagrant"}}) 122 | (roundtrip-via org.apache.mesos.Protos$FrameworkID "hello")) 123 | --------------------------------------------------------------------------------