Advertisement
Guest User

Untitled

a guest
Jul 25th, 2018
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.88 KB | None | 0 0
  1. (ns pallet.core
  2. "Core functionality is provided in `lift` and `converge`.
  3.  
  4. - node :: A node in the compute service
  5. - node-spec :: A specification for a node. The node-spec provides an image
  6. hardware, location and network template for starting new
  7. nodes.
  8. - server-spec :: A specification for a server. This is a map of phases and
  9. a default node-spec. A server-spec has the following keys
  10. :phase, :packager and node-spec keys.
  11. - group-spec :: A group of identically configured nodes, represented as a
  12. map with :group-name, :count and server-spec keys.
  13. The group-name is used to link running nodes to their
  14. configuration (via pallet.compute.Node/group-name)
  15. - group :: A group of identically configured nodes, represented as a
  16. group-spec, together with the servers that are running
  17. for that group-spec.
  18. - group name :: The name used to identify a group.
  19. - server :: A map used to descibe the node, image, etc of a single
  20. node running as part of a group. A server has the
  21. following keys :group-name, :node, :node-id and server-spec
  22. keys.
  23. - phase list :: A list of phases to be used
  24. - action plan :: A list of resources that should be run."
  25. {:author "Hugo Duncan"}
  26. (:require
  27. [pallet.action :as action]
  28. [pallet.action-plan :as action-plan]
  29. [pallet.blobstore :as blobstore]
  30. [pallet.compute :as compute]
  31. [pallet.environment :as environment]
  32. [pallet.execute :as execute]
  33. [pallet.futures :as futures]
  34. [pallet.parameter :as parameter]
  35. [pallet.phase :as phase]
  36. [pallet.script :as script]
  37. [pallet.target :as target]
  38. [pallet.thread-expr :as thread-expr]
  39. [pallet.utils :as utils]
  40. [clojure.contrib.condition :as condition]
  41. [clojure.contrib.logging :as logging]
  42. [clojure.contrib.map-utils :as map-utils]
  43. [clojure.set :as set]
  44. [clojure.string :as string])
  45. (:use
  46. [clojure.contrib.core :only [-?>]]))
  47.  
  48. (defn version
  49. "Returns the pallet version."
  50. []
  51. (or
  52. (System/getProperty "pallet.version")
  53. (if-let [version (utils/slurp-resource "pallet-version")]
  54. (string/trim version))))
  55.  
  56. ;; Set the agent string for http requests.
  57. (. System setProperty "http.agent"
  58. (str "Pallet " (version)))
  59.  
  60. (defmacro with-admin-user
  61. "Specify the admin user for running remote commands. The user is specified
  62. either as pallet.utils.User record (see the pallet.utils/make-user
  63. convenience fn) or as an argument list that will be passed to make-user.
  64.  
  65. This is mainly for use at the repl, since the admin user can be specified
  66. functionally using the :user key in a lift or converge call, or in the
  67. environment."
  68. {:arglists
  69. '([user & body]
  70. [[username & {:keys [public-key-path private-key-path passphrase password
  71. sudo-password no-sudo] :as options}] & body])}
  72. [user & exprs]
  73. `(let [user# ~user]
  74. (binding [utils/*admin-user* (if (utils/user? user#)
  75. user#
  76. (apply utils/make-user user#))]
  77. ~@exprs)))
  78.  
  79. (defn admin-user
  80. "Set the root binding for the admin user.
  81. The user arg is a map as returned by make-user, or a username. When passing
  82. a username the options can be specified as in `pallet.utils/make-user`.
  83.  
  84. This is mainly for use at the repl, since the admin user can be specified
  85. functionally using the :user key in a lift or converge call, or in the
  86. environment."
  87. {:arglists
  88. '([user]
  89. [username & {:keys [public-key-path private-key-path passphrase
  90. password sudo-password no-sudo] :as options}])}
  91. [user & options]
  92. (alter-var-root
  93. #'utils/*admin-user*
  94. #(identity %2)
  95. (if (string? user)
  96. (apply utils/make-user user options)
  97. user)))
  98.  
  99. (def ^{:doc "Vector of keywords recognised by node-spec"
  100. :private true}
  101. node-spec-keys [:image :hardware :location :network])
  102.  
  103. (defn node-spec
  104. "Create a node-spec.
  105.  
  106. Defines the compute image and hardware selector template.
  107.  
  108. This is used to filter a cloud provider's image and hardware list to select
  109. an image and hardware for nodes created for this node-spec.
  110.  
  111. :image a map descirbing a predicate for matching an image:
  112. os-family os-name-matches os-version-matches
  113. os-description-matches os-64-bit
  114. image-version-matches image-name-matches
  115. image-description-matches image-id
  116.  
  117. :location a map describing a predicate for matching location:
  118. location-id
  119. :hardware a map describing a predicate for matching harware:
  120. min-cores min-ram smallest fastest biggest architecture
  121. hardware-id
  122. :network a map for network connectivity options:
  123. inbound-ports
  124. :qos a map for quality of service options:
  125. spot-price enable-monitoring"
  126. [& {:keys [image hardware location network qos] :as options}]
  127. {:pre [(or (nil? image) (map? image))]}
  128. options)
  129.  
  130. (defn- merge-specs
  131. "Merge specs, using comp for :phases"
  132. [a b]
  133. (let [phases (merge-with #(comp %2 %1) (:phases a) (:phases b))]
  134. (->
  135. (merge a b)
  136. (thread-expr/when-not->
  137. (empty? phases)
  138. (assoc :phases phases)))))
  139.  
  140. (defn- extend-specs
  141. "Merge in the inherited specs"
  142. [spec inherits]
  143. (if inherits
  144. (merge-specs
  145. (if (map? inherits) inherits (reduce merge-specs inherits))
  146. spec)
  147. spec))
  148.  
  149. (defn server-spec
  150. "Create a server-spec.
  151.  
  152. - :phases a hash-map used to define phases. Standard phases are:
  153. - :bootstrap run on first boot of a new node
  154. - :configure defines the configuration of the node
  155. - :packager override the choice of packager to use
  156. - :node-spec default node-spec for this server-spec
  157. - :extends takes a server-spec, or sequence thereof, and is used to
  158. inherit phases, etc."
  159. [& {:keys [phases packager node-spec extends image hardware location network]
  160. :as options}]
  161. (->
  162. node-spec
  163. (merge options)
  164. (extend-specs extends)
  165. (dissoc :extends :node-spec)))
  166.  
  167. (defn group-spec
  168. "Create a group-spec.
  169.  
  170. `name` is used for the group name, which is set on each node and links a node
  171. to it's node-spec
  172.  
  173. - :extends specify a server-spec, a group-spec, or sequence thereof,
  174. and is used to inherit phases, etc.
  175.  
  176. - :phases used to define phases. Standard phases are:
  177. - :bootstrap run on first boot of a new node
  178. - :configure defines the configuration of the node.
  179.  
  180. - :count specify the target number of nodes for this node-spec
  181. - :packager override the choice of packager to use
  182. - :node-spec default node-spec for this server-spec"
  183. [name
  184. & {:keys [extends count image phases packager node-spec] :as options}]
  185. {:pre [(or (nil? image) (map? image))]}
  186. (->
  187. node-spec
  188. (merge options)
  189. (extend-specs extends)
  190. (dissoc :extends :node-spec)
  191. (assoc :group-name (keyword name))))
  192.  
  193. (defn make-node
  194. "Create a node definition. See defnode."
  195. [name image & {:as phase-map}]
  196. {:pre [(or (nil? image) (map? image))]}
  197. (->
  198. {:group-name (keyword name)
  199. :image image}
  200. (thread-expr/when-not->
  201. (empty? phase-map)
  202. (assoc :phases phase-map))))
  203.  
  204. (defn name-with-attributes
  205. "Modified version, of that found in contrib, to handle the image map."
  206. [name macro-args]
  207. (let [[docstring macro-args] (if (string? (first macro-args))
  208. [(first macro-args) (next macro-args)]
  209. [nil macro-args])
  210. [attr macro-args] (if (and (map? (first macro-args))
  211. (map? (first (next macro-args))))
  212. [(first macro-args) (next macro-args)]
  213. [{} macro-args])
  214. attr (if docstring
  215. (assoc attr :doc docstring)
  216. attr)
  217. attr (if (meta name)
  218. (conj (meta name) attr)
  219. attr)]
  220. [(with-meta name attr) macro-args]))
  221.  
  222. (defmacro defnode
  223. "Define a node type. The name is used for the group name.
  224.  
  225. image defines the image selector template. This is a vector of keyword or
  226. keyword value pairs that are used to filter the image list to select
  227. an image.
  228. Options are used to define phases. Standard phases are:
  229. :bootstrap run on first boot
  230. :configure defines the configuration of the node."
  231. {:arglists ['(tag doc-str? attr-map? image & phasekw-phasefn-pairs)]
  232. :deprecated "0.4.6"}
  233. [group-name & options]
  234. (let [[group-name options] (name-with-attributes group-name options)]
  235. `(def ~group-name (make-node '~(name group-name) ~@options))))
  236.  
  237. (defn- add-request-keys-for-0-4-5-compatibility
  238. "Add target keys for compatibility.
  239. This function adds back deprecated keys"
  240. [request]
  241. (-> request
  242. (assoc :node-type (:group request))
  243. (assoc :target-packager (-> request :server :packager))
  244. (assoc :target-id (-> request :server :node-id))
  245. (assoc :target-node (-> request :server :node))))
  246.  
  247. (defn show-target-keys
  248. "Middleware that is useful in debugging."
  249. [handler]
  250. (fn [request]
  251. (logging/info
  252. (format
  253. "TARGET KEYS :phase %s :node-id %s :group-name %s :packager %s"
  254. (:phase request)
  255. (-> request :server :node-id)
  256. (-> request :server :group-name)
  257. (-> request :server :packager)))
  258. (handler request)))
  259.  
  260.  
  261. ;;; executor
  262.  
  263. (defn- executor [request f action-type location]
  264. (let [exec-fn (get-in request [:executor action-type location])]
  265. (when-not exec-fn
  266. (condition/raise
  267. :type :missing-executor-fn
  268. :fn-for [action-type location]
  269. :message (format
  270. "Missing executor function for %s %s"
  271. action-type location)))
  272. (exec-fn request f)))
  273.  
  274. (let [raise (fn [message]
  275. (fn [_ _]
  276. (condition/raise :type :executor-error :message message)))]
  277. (def ^{:doc "Default executor map"}
  278. default-executors
  279. {:script/bash
  280. {:origin execute/bash-on-origin
  281. :target (raise
  282. (str ":script/bash on :target not implemented.\n"
  283. "Add middleware to enable remote execution."))}
  284. :fn/clojure
  285. {:origin execute/clojure-on-origin
  286. :target (raise ":fn/clojure on :target not supported")}
  287. :transfer/to-local
  288. {:origin (raise
  289. (str ":transfer/to-local on :origin not implemented.\n"
  290. "Add middleware to enable transfers."))
  291. :target (raise ":transfer/to-local on :target not supported")}
  292. :transfer/from-local
  293. {:origin (raise
  294. (str ":transfer/to-local on :origin not implemented.\n"
  295. "Add middleware to enable transfers."))
  296. :target (raise ":transfer/from-local on :target not supported")}}))
  297.  
  298. ;;; bootstrap functions
  299. (defn- bootstrap-script
  300. [request]
  301. {:pre [(get-in request [:group :image :os-family])
  302. (get-in request [:group :packager])]}
  303. (let [error-fn (fn [message]
  304. (fn [_ _]
  305. (condition/raise
  306. :type :booststrap-contains-non-remote-actions
  307. :message message)))
  308. [result request] (->
  309. request
  310. (assoc
  311. :phase :bootstrap
  312. :server (assoc (:group request) :node-id :bootstrap-id))
  313. (assoc-in
  314. [:executor :script/bash :target]
  315. execute/echo-bash)
  316. (assoc-in
  317. [:executor :transfer/to-local :origin]
  318. (error-fn "Bootstrap can not contain transfers"))
  319. (assoc-in
  320. [:executor :transfer/from-local :origin]
  321. (error-fn "Bootstrap can not contain transfers"))
  322. (assoc-in
  323. [:executor :fn/clojure :origin]
  324. (error-fn "Bootstrap can not contain local actions"))
  325. add-request-keys-for-0-4-5-compatibility
  326. action-plan/build-for-target
  327. action-plan/translate-for-target
  328. (action-plan/execute-for-target executor))]
  329. (string/join \newline result)))
  330.  
  331. (defn- create-nodes
  332. "Create count nodes based on the template for the group. The boostrap argument
  333. expects a map with :authorize-public-key and :bootstrap-script keys. The
  334. bootstrap-script value is expected tobe a function that produces a script that
  335. is run with root privileges immediatly after first boot."
  336. [group count request]
  337. {:pre [(map? group)]}
  338. (logging/info
  339. (str "Starting " count " nodes for " (:group-name group)
  340. " os-family " (-> group :image :os-family)))
  341. (let [compute (:compute request)
  342. request (update-in request [:group]
  343. #(compute/ensure-os-family compute %))
  344. request (assoc-in request [:group :packager]
  345. (compute/packager (-> request :group :image)))
  346. init-script (bootstrap-script request)]
  347. (logging/trace
  348. (format "Bootstrap script:\n%s" init-script))
  349. (concat
  350. (map :node (:servers group))
  351. (compute/run-nodes compute group count (:user request) init-script))))
  352.  
  353. (defn- destroy-nodes
  354. "Destroys the specified number of nodes with the given group. Nodes are
  355. selected at random."
  356. [group destroy-count request]
  357. (logging/info
  358. (str "destroying " destroy-count " nodes for " (:group-name group)))
  359. (let [compute (:compute request)
  360. servers (:servers group)]
  361. (if (= destroy-count (count servers))
  362. (do
  363. (compute/destroy-nodes-in-group compute (name (:group-name group)))
  364. nil)
  365. (let [nodes (map :node servers)]
  366. (doseq [node (take destroy-count nodes)]
  367. (compute/destroy-node compute node))
  368. (drop destroy-count nodes)))))
  369.  
  370. (defn- node-count-difference
  371. "Find the difference between the required and actual node counts by group."
  372. [groups]
  373. (->>
  374. groups
  375. (map
  376. (fn [group]
  377. (vector (:group-name group) (- (:count group) (count (:servers group))))))
  378. (into {})))
  379.  
  380. (defn- adjust-node-count
  381. "Adjust the node by delta nodes"
  382. [{:keys [group-name environment servers] :as group} delta request]
  383. (let [request (environment/request-with-environment
  384. (assoc request :group group)
  385. (environment/merge-environments
  386. (:environment request) environment))]
  387. (logging/info (format "adjust-node-count %s %d" group-name delta))
  388. (cond
  389. (pos? delta) (create-nodes group delta request)
  390. (neg? delta) (destroy-nodes group (- delta) request)
  391. :else (map :node servers))))
  392.  
  393. (defn serial-adjust-node-counts
  394. "Start or stop the specified number of nodes."
  395. [delta-map request]
  396. (logging/trace (str "serial-adjust-node-counts" delta-map))
  397. (reduce
  398. concat
  399. (doall
  400. (map
  401. (fn [group]
  402. (adjust-node-count group ((:group-name group) delta-map 0) request))
  403. (:groups request)))))
  404.  
  405. (defn parallel-adjust-node-counts
  406. "Start or stop the specified number of nodes."
  407. [delta-map request]
  408. (logging/trace (str "parallel-adjust-node-counts" delta-map))
  409. (->>
  410. (:groups request)
  411. (map
  412. (fn [group]
  413. (future
  414. (adjust-node-count group ((:group-name group) delta-map 0) request))))
  415. futures/add
  416. doall ;; force generation of all futures
  417. (mapcat #(futures/deref-with-logging % "Adjust node count"))))
  418.  
  419. (defn- converge-node-counts
  420. "Converge the nodes counts, given a compute facility and a reference number of
  421. instances."
  422. [request]
  423. (logging/info "converging nodes")
  424. (assoc request
  425. :all-nodes ((environment/get-for request [:algorithms :converge-fn])
  426. (node-count-difference (:groups request))
  427. request)))
  428.  
  429. ;;; middleware
  430.  
  431. (defn log-request
  432. "Log the request state"
  433. [msg]
  434. (fn [request]
  435. (logging/info (format "%s Request is %s" msg request))
  436. request))
  437.  
  438. (defn log-message
  439. "Log the message"
  440. [msg]
  441. (fn [request]
  442. (logging/info (format "%s" msg))
  443. request))
  444.  
  445. (defn- apply-environment
  446. "Apply the effective environment"
  447. [request]
  448. (environment/request-with-environment
  449. request
  450. (environment/merge-environments
  451. (:environment request)
  452. (-> request :server :environment))))
  453.  
  454. (defn translate-action-plan
  455. [handler]
  456. (fn [request]
  457. (handler (action-plan/translate-for-target request))))
  458.  
  459. (defn middleware-handler
  460. "Build a middleware processing pipeline from the specified middleware.
  461. The result is a middleware."
  462. [handler]
  463. (fn [request]
  464. ((reduce #(%2 %1) handler (:middleware request)) request)))
  465.  
  466. (defn- execute
  467. "Execute the action plan"
  468. [request]
  469. (action-plan/execute-for-target request executor))
  470.  
  471. (defn- apply-phase-to-node
  472. "Apply a phase to a node request"
  473. [request]
  474. {:pre [(:server request) (:phase request)]}
  475. ((middleware-handler execute)
  476. (->
  477. request
  478. apply-environment
  479. add-request-keys-for-0-4-5-compatibility)))
  480.  
  481. (def *middleware*
  482. [translate-action-plan
  483. execute/ssh-user-credentials
  484. execute/execute-with-ssh])
  485.  
  486. (defmacro with-middleware
  487. "Wrap node execution in the given middleware. A middleware is a function of
  488. one argument (a handler function, that is the next middleware to call) and
  489. returns a dunction of one argument (the request map). Middleware can be
  490. composed with the pipe macro."
  491. [f & body]
  492. `(binding [*middleware* ~f]
  493. ~@body))
  494.  
  495. (defn- reduce-node-results
  496. "Combine the node execution results."
  497. [request results]
  498. (reduce
  499. (fn reduce-node-results-fn [request [result req :as arg]]
  500. (let [target-id (-> req :server :node-id)
  501. param-keys [:parameters]]
  502. (->
  503. request
  504. (assoc-in [:results target-id (:phase req)] result)
  505. (update-in
  506. param-keys
  507. (fn merge-params [p]
  508. (map-utils/deep-merge-with
  509. (fn merge-params-fn [x y] (or y x)) p (get-in req param-keys)))))))
  510. request
  511. results))
  512.  
  513. (defn- plan-for-server
  514. "Build an action plan for the specified server."
  515. [request server]
  516. {:pre [(:node server) (:node-id server)]}
  517. (action-plan/build-for-target
  518. (->
  519. request
  520. (assoc :server server)
  521. add-request-keys-for-0-4-5-compatibility
  522. (environment/request-with-environment
  523. (environment/merge-environments
  524. (:environment request)
  525. (-> request :server :environment))))))
  526.  
  527. (defn- plan-for-servers
  528. "Build an action plan for the specified servers."
  529. [request servers]
  530. (reduce plan-for-server request servers))
  531.  
  532. (defn- plan-for-groups
  533. "Build an invocation map for specified node-type map."
  534. [request groups]
  535. (reduce
  536. (fn [request group]
  537. (plan-for-servers (assoc request :group group) (:servers group)))
  538. request groups))
  539.  
  540. (defn- plan-for-phases
  541. "Build an invocation map for specified phases and nodes.
  542. This allows configuration to be accumulated in the request parameters."
  543. [request]
  544. (reduce
  545. (fn [request phase]
  546. (plan-for-groups (assoc request :phase phase) (:groups request)))
  547. request (:phase-list request)))
  548.  
  549. (defn sequential-apply-phase
  550. "Apply a phase to a sequence of nodes"
  551. [request servers]
  552. (logging/info
  553. (format
  554. "apply-phase %s for %s with %d nodes"
  555. (:phase request) (-> request :server :group-name) (count servers)))
  556. (for [server servers]
  557. (apply-phase-to-node (assoc request :server server))))
  558.  
  559. (defn parallel-apply-phase
  560. "Apply a phase to a sequence of nodes"
  561. [request servers]
  562. (logging/info
  563. (format
  564. "apply-phase %s for %s with %d nodes"
  565. (:phase request) (-> request :server :group-name) (count servers)))
  566. (->>
  567. servers
  568. (map (fn [server]
  569. (future (apply-phase-to-node (assoc request :server server)))))
  570. futures/add))
  571.  
  572.  
  573. (defn- add-prefix-to-node-type
  574. [prefix node-type]
  575. (update-in node-type [:tag]
  576. (fn [tag] (keyword (str prefix (name tag))))))
  577.  
  578. (defn- add-prefix-to-node-map [prefix node-map]
  579. (zipmap
  580. (map (partial add-prefix-to-node-type prefix) (keys node-map))
  581. (vals node-map)))
  582.  
  583. (defn- ensure-configure-phase [phases]
  584. (if (some #{:configure} phases)
  585. phases
  586. (concat [:configure] phases)))
  587.  
  588. (defn- identify-anonymous-phases
  589. [request phases]
  590. (reduce #(if (keyword? %2)
  591. [(first %1)
  592. (conj (second %1) %2)]
  593. (let [phase (keyword (name (gensym "phase")))]
  594. [(assoc-in (first %1) [:phases phase] %2)
  595. (conj (second %1) phase)])) [request []] phases))
  596.  
  597. (defn sequential-lift
  598. "Sequential apply the phases."
  599. [request]
  600. (apply
  601. concat
  602. (for [group (:groups request)]
  603. (sequential-apply-phase (assoc request :group group) (:servers group)))))
  604.  
  605. (defn parallel-lift
  606. "Apply the phases in sequence, to nodes in parallel."
  607. [request]
  608. (mapcat
  609. #(map deref %) ; make sure all nodes complete before next phase
  610. (for [group (:groups request)]
  611. (parallel-apply-phase (assoc request :group group) (:servers group)))))
  612.  
  613.  
  614. (defn lift-nodes
  615. "Lift nodes in target-node-map for the specified phases."
  616. [request]
  617. (logging/trace (format "lift-nodes phases %s" (vec (:phase-list request))))
  618. (let [lift-fn (environment/get-for request [:algorithms :lift-fn])
  619. lift-phase (fn [request]
  620. (reduce-node-results
  621. request (lift-fn request)))]
  622. (reduce
  623. (fn [request phase]
  624. (->
  625. request
  626. (assoc :phase phase)
  627. (plan-for-groups (:groups request))
  628. lift-phase))
  629. request
  630. (phase/phase-list-with-implicit-phases (:phase-list request)))))
  631.  
  632.  
  633. (def
  634. ^{:doc
  635. "Flag to control output of warnings about undefined phases in calls to lift
  636. and converge."}
  637. *warn-on-undefined-phase* true)
  638.  
  639. (defn- warn-on-undefined-phase
  640. "Generate a warning for the elements of the request's :phase-list that are not
  641. defined in the request's :groups."
  642. [request]
  643. (when *warn-on-undefined-phase*
  644. (when-let [undefined (seq
  645. (set/difference
  646. (set (filter keyword? (:phase-list request)))
  647. (set
  648. (concat
  649. (->>
  650. (:groups request)
  651. (map (comp keys :phases))
  652. (reduce concat))
  653. (keys (:inline-phases request))))))]
  654. (logging/warn
  655. (format
  656. "Undefined phases: %s"
  657. (string/join ", " (map name undefined))))))
  658. request)
  659.  
  660. (defn- group-with-prefix
  661. [prefix node-spec]
  662. (update-in node-spec [:group-name]
  663. (fn [group-name] (keyword (str prefix (name group-name))))))
  664.  
  665. (defn- node-map-with-prefix [prefix node-map]
  666. (zipmap
  667. (map #(group-with-prefix prefix %) (keys node-map))
  668. (vals node-map)))
  669.  
  670. (defn- phase-list-with-configure
  671. "Ensure that the `phase-list` contains the :configure phase, prepending it if
  672. not."
  673. [phase-list]
  674. (if (some #{:configure} phase-list)
  675. phase-list
  676. (concat [:configure] phase-list)))
  677.  
  678. (defn- phase-list-with-default
  679. "Add the default configure phase if the `phase-list` is empty"
  680. [phase-list]
  681. (if (seq phase-list) phase-list [:configure]))
  682.  
  683. (defn- request-with-configure-phase
  684. "Add the configure phase to the request's :phase-list if not present."
  685. [request]
  686. (update-in request [:phase-list] phase-list-with-configure))
  687.  
  688. (defn- request-with-default-phase
  689. "Add the default phase to the request's :phase-list if none supplied."
  690. [request]
  691. (update-in request [:phase-list] phase-list-with-default))
  692.  
  693. (defn- node-in-types?
  694. "Predicate for matching a node belonging to a set of node types"
  695. [node-types node]
  696. (some #(= (compute/group-name node) (name (% :group-name))) node-types))
  697.  
  698. (defn- nodes-for-group
  699. "Return the nodes that have a group-name that matches one of the node types"
  700. [nodes group]
  701. (let [group-name (name (:group-name group))]
  702. (filter #(compute/node-in-group? group-name %) nodes)))
  703.  
  704. (defn- group-spec?
  705. "Predicate for testing if argument is a node-spec.
  706. This is not exhaustive, and not intended for general use."
  707. [x]
  708. (and (map? x) (:group-name x) (keyword? (:group-name x))))
  709.  
  710. (defn nodes-in-set
  711. "Build a map of node-spec to nodes for the given `node-set`.
  712. A node set can be a node spec, a map from node-spec to a sequence of nodes,
  713. or a sequence of these.
  714.  
  715. The prefix is applied to the group-name of each node-spec in the result.
  716. This allows you to build seperate clusters based on the same node-spec's.
  717.  
  718. The return value is a map of node-spec to node sequence.
  719.  
  720. Example node sets:
  721. node-spec-1
  722. [node-spec1 node-spec-2]
  723. {node-spec #{node1 node2}}
  724. [node-spec1 node-spec-2 {node-spec #{node1 node2}}]"
  725. [node-set prefix nodes]
  726. (letfn [(ensure-set [x] (if (set? x) x #{x}))
  727. (ensure-set-values
  728. [m]
  729. (zipmap (keys m) (map ensure-set (vals m))))]
  730. (cond
  731. (and (map? node-set) (not (group-spec? node-set)))
  732. (ensure-set-values (node-map-with-prefix prefix node-set))
  733. (group-spec? node-set)
  734. (let [group (group-with-prefix prefix node-set)]
  735. {group (set (nodes-for-group nodes group))})
  736. :else (reduce
  737. #(merge-with concat %1 %2) {}
  738. (map #(nodes-in-set % prefix nodes) node-set)))))
  739.  
  740. (defn- server-with-packager
  741. "Add the target packager to the request"
  742. [server]
  743. (update-in server [:packager]
  744. (fn [p] (or p
  745. (-> server :image :packager)
  746. (compute/packager (:image server))))))
  747.  
  748. (defn server
  749. "Take a `group` and a `node`, an `options` map and combine them to produce
  750. a server.
  751.  
  752. The group os-family, os-version, are replaced with the details form the
  753. node. The :node key is set to `node`, and the :node-id and :packager keys
  754. are set.
  755.  
  756. `options` allows adding extra keys on the server."
  757. [group node options]
  758. (->
  759. group
  760. (update-in [:image :os-family] (fn [f] (or (compute/os-family node) f)))
  761. (update-in [:image :os-version] (fn [f] (or (compute/os-version node) f)))
  762. (update-in [:node-id] (fn [id] (or (keyword (compute/id node)) id)))
  763. (assoc :node node)
  764. server-with-packager
  765. (merge options)))
  766.  
  767. (defn groups-with-servers
  768. "Takes a map from node-spec to sequence of nodes, and converts it to a
  769. sequence of group definitions, containing a server for each node in then
  770. :servers key of each group. The server will contain the node-spec,
  771. updated with any information that was available from the node.
  772.  
  773. (groups-with-servers {(node-spec \"spec\" {}) [a b c]})
  774. => [{:group-name \"spec\"
  775. :servers [{:group-name \"spec\" :node a}
  776. {:group-name \"spec\" :node b}
  777. {:group-name \"spec\" :node c}]}]
  778.  
  779. `options` allows adding extra keys to the servers."
  780. [node-map & {:as options}]
  781. (for [[group nodes] node-map]
  782. (assoc group
  783. :servers (map #(server group % options)
  784. (filter compute/running? nodes)))))
  785.  
  786. (defn request-with-groups
  787. "Takes the :all-nodes, :node-set and :prefix keys and compute the groups
  788. for the request, updating the :all-nodes and :groups keys of the request.
  789.  
  790. If the :all-nodes key is not set, then the nodes are retrieved from the
  791. compute service if possible, or are inferred from the :node-set value.
  792.  
  793. The :groups key is set to a sequence of groups, each containing its
  794. list of servers on the :servers key."
  795. [request]
  796. (let [all-nodes (filter
  797. compute/running?
  798. (or (seq (:all-nodes request))
  799. (when-let [compute (environment/get-for
  800. request [:compute] nil)]
  801. (logging/info "retrieving nodes")
  802. (compute/nodes compute))))
  803. targets (nodes-in-set (:node-set request) (:prefix request) all-nodes)
  804. plan-targets (if-let [all-node-set (:all-node-set request)]
  805. (-> (nodes-in-set all-node-set nil all-nodes)
  806. (utils/dissoc-keys (keys targets))))]
  807. (->
  808. request
  809. (assoc :all-nodes (or (seq all-nodes)
  810. (filter
  811. compute/running?
  812. (reduce
  813. concat
  814. (concat (vals targets) (vals plan-targets))))))
  815. (assoc :groups (concat
  816. (groups-with-servers targets)
  817. (groups-with-servers plan-targets :invoke-only true))))))
  818.  
  819. (defn lift*
  820. "Lift the nodes specified in the request :node-set key.
  821. - :node-set - a specification of nodes to lift
  822. - :all-nodes - a sequence of all known nodes
  823. - :all-node-set - a specification of nodes to invoke (but not lift)"
  824. [request]
  825. (logging/debug (format "pallet version: %s" (version)))
  826. (logging/trace (format "lift* phases %s" (vec (:phase-list request))))
  827. (->
  828. request
  829. request-with-groups
  830. request-with-default-phase
  831. warn-on-undefined-phase
  832. lift-nodes))
  833.  
  834. (defn converge*
  835. "Converge the node counts of each node-spec in `:node-set`, executing each of
  836. the configuration phases on all the group-names in `:node-set`. The
  837. phase-functions are also executed, but not applied, for any other nodes in
  838. `:all-node-set`"
  839. [request]
  840. {:pre [(:node-set request)]}
  841. (logging/debug (format "pallet version: %s" (version)))
  842. (logging/trace
  843. (format "converge* %s %s" (:node-set request) (:phase-list request)))
  844. (->
  845. request
  846. request-with-groups
  847. converge-node-counts
  848. lift*))
  849.  
  850. (defmacro or-fn [& args]
  851. `(fn or-args [current#]
  852. (or current# ~@args)))
  853.  
  854. (defn- compute-from-options
  855. [current-value {:keys [compute compute-service]}]
  856. (or current-value
  857. compute
  858. (and compute-service
  859. (compute/compute-service
  860. (:provider compute-service)
  861. :identity (:identity compute-service)
  862. :credential (:credential compute-service)
  863. :extensions (:extensions compute-service)
  864. :node-list (:node-list compute-service)))))
  865.  
  866. (defn- blobstore-from-options
  867. [current-value {:keys [blobstore blobstore-service]}]
  868. (or current-value
  869. blobstore
  870. (and blobstore-service
  871. (blobstore/service
  872. (:provider blobstore-service)
  873. :identity (:identity blobstore-service)
  874. :credential (:credential blobstore-service)
  875. :extensions (:extensions blobstore-service)))))
  876.  
  877. (defn default-environment
  878. "Specify the built-in default environment"
  879. []
  880. {:blobstore nil
  881. :compute nil
  882. :user utils/*admin-user*
  883. :middleware *middleware*
  884. :algorithms {:lift-fn parallel-lift
  885. :converge-fn parallel-adjust-node-counts}})
  886.  
  887. (defn- effective-environment
  888. "Build the effective environment for the request map.
  889. This merges the explicitly passed :environment, with that
  890. defined on the :compute service."
  891. [request]
  892. (assoc
  893. request
  894. :environment
  895. (environment/merge-environments
  896. (default-environment) ; global default
  897. (utils/find-var-with-require 'pallet.config 'environment) ; project default
  898. (-?> request :environment :compute environment/environment) ;service default
  899. (:environment request)))) ; request default
  900.  
  901. (def ^{:doc "args that are really part of the environment"}
  902. environment-args [:compute :blobstore :user :middleware])
  903.  
  904. (defn- request-with-environment
  905. "Build a request map from the given options, combining the service specific
  906. options with those given in the converge or lift invocation."
  907. [{:as options}]
  908. (->
  909. options
  910. (update-in ; ensure backwards compatable
  911. [:environment]
  912. merge (select-keys options environment-args))
  913. (assoc :executor default-executors)
  914. (utils/dissoc-keys environment-args)
  915. (effective-environment)))
  916.  
  917. (def ^{:doc "A set of recognised argument keywords, used for input checking."
  918. :private true}
  919. argument-keywords
  920. #{:compute :blobstore :phase :user :prefix :middleware :all-node-set
  921. :all-nodes :parameters :environment :node-set :phase-list})
  922.  
  923. (defn- check-arguments-map
  924. "Check an arguments map for errors."
  925. [{:as options}]
  926. (let [unknown (remove argument-keywords (keys options))]
  927. (when (and (:phases options) (not (:phase options)))
  928. (condition/raise
  929. :type :invalid-argument
  930. :message (str
  931. "Please pass :phase and not :phases. :phase takes a single "
  932. "phase or a sequence of phases.")
  933. :invalid-keys unknown))
  934. (when (seq unknown)
  935. (condition/raise
  936. :type :invalid-argument
  937. :message (format "Invalid argument keywords %s" (vec unknown))
  938. :invalid-keys unknown)))
  939. options)
  940.  
  941. (defn- identify-anonymous-phases
  942. "For all inline phase defintions in the request's :phase-list,
  943. generate a keyword for the phase, adding an entry to the request's
  944. :inline-phases map containing the phase definition, and replacing the
  945. phase defintion in the :phase-list with the keyword."
  946. [request]
  947. (reduce
  948. (fn [request phase]
  949. (if (keyword? phase)
  950. (update-in request [:phase-list] #(conj (or % []) phase))
  951. (let [phase-kw (keyword (name (gensym "phase")))]
  952. (->
  953. request
  954. (assoc-in [:inline-phases phase-kw] phase)
  955. (update-in [:phase-list] conj phase-kw)))))
  956. (dissoc request :phase-list)
  957. (:phase-list request)))
  958.  
  959. (defn- group-spec-with-count
  960. "Take the given group-spec, and set the :count key to the value specified
  961. by `count`"
  962. [[group-spec count]]
  963. (assoc group-spec :count count))
  964.  
  965. (defn- node-set-for-converge
  966. "Takes the input, and translates it into a sequence of group-spec's.
  967. The input can be a single group-spec, a map from group-spec to node count,
  968. or a sequence of group-spec's"
  969. [group-spec->count]
  970. (cond
  971. ;; a single group-spec
  972. (and
  973. (map? group-spec->count)
  974. (:group-name group-spec->count)) [group-spec->count]
  975. ;; a map from group-spec to count
  976. (map? group-spec->count) (map group-spec-with-count group-spec->count)
  977. :else group-spec->count))
  978.  
  979. (defn converge
  980. "Converge the existing compute resources with the counts specified in
  981. `group-spec->count`. New nodes are started, or nodes are destroyed,
  982. to obtain the specified node counts.
  983.  
  984. `group-spec->count` can be a map from group-spec to node count, or can be a
  985. sequence of group-specs containing a :count key.
  986.  
  987. The compute service may be supplied as an option, otherwise the bound
  988. compute-service is used.
  989.  
  990.  
  991. This applies the bootstrap phase to all new nodes and the configure phase to
  992. all running nodes whose group-name matches a key in the node map. Additional
  993. phases can also be specified in the options, and will be applied to all
  994. matching nodes. The :configure phase is always applied, by default as the
  995. first (post bootstrap) phase. You can change the order in which
  996. the :configure phase is applied by explicitly listing it.
  997.  
  998. An optional group-name prefix may be specified. This will be used to modify
  999. the group-name for each group-spec, allowing you to build multiple discrete
  1000. clusters from a single set of group-specs."
  1001. [group-spec->count & {:keys [compute blobstore user phase prefix middleware
  1002. all-nodes all-node-set environment]
  1003. :as options}]
  1004. (converge*
  1005. (->
  1006. options
  1007. (assoc :node-set (node-set-for-converge group-spec->count)
  1008. :phase-list (if (sequential? phase)
  1009. phase
  1010. (if phase [phase] [:configure])))
  1011. check-arguments-map
  1012. request-with-environment
  1013. identify-anonymous-phases)))
  1014.  
  1015.  
  1016. (defn lift
  1017. "Lift the running nodes in the specified node-set by applying the specified
  1018. phases. The compute service may be supplied as an option, otherwise the
  1019. bound compute-service is used. The configure phase is applied by default
  1020. unless other phases are specified.
  1021.  
  1022. node-set can be a node type, a sequence of node types, or a map
  1023. of node type to nodes. Examples:
  1024. [node-type1 node-type2 {node-type #{node1 node2}}]
  1025. node-type
  1026. {node-type #{node1 node2}}
  1027.  
  1028. options can also be keywords specifying the phases to apply, or an immediate
  1029. phase specified with the phase macro, or a function that will be called with
  1030. each matching node.
  1031.  
  1032. Options:
  1033. :compute a jclouds compute service
  1034. :compute-service a map of :provider, :identity, :credential, and
  1035. optionally :extensions for constructing a jclouds compute
  1036. service.
  1037. :phase a phase keyword, phase function, or sequence of these
  1038. :middleware the middleware to apply to the configuration pipeline
  1039. :prefix a prefix for the group-name names
  1040. :user the admin-user on the nodes"
  1041. [node-set & {:keys [compute phase prefix middleware all-node-set environment]
  1042. :as options}]
  1043. (lift*
  1044. (->
  1045. options
  1046. (assoc :node-set node-set
  1047. :phase-list (if (sequential? phase)
  1048. phase
  1049. (if phase [phase] [:configure])))
  1050. check-arguments-map
  1051. (dissoc :all-node-set :phase)
  1052. request-with-environment
  1053. identify-anonymous-phases)))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement