BIGTOP-2516: Add Zeppelin Charm (closes #137) Signed-off-by: Kevin W Monroe <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/bigtop/repo Commit: http://git-wip-us.apache.org/repos/asf/bigtop/commit/f7d471b4 Tree: http://git-wip-us.apache.org/repos/asf/bigtop/tree/f7d471b4 Diff: http://git-wip-us.apache.org/repos/asf/bigtop/diff/f7d471b4 Branch: refs/heads/master Commit: f7d471b4154588b8ac5441657bd23bad60122c09 Parents: f482fd9 Author: Kevin W Monroe <[email protected]> Authored: Mon Jun 6 21:21:51 2016 -0500 Committer: Kevin W Monroe <[email protected]> Committed: Sat Oct 8 15:27:16 2016 -0500 ---------------------------------------------------------------------- .../src/charm/zeppelin/layer-zeppelin/README.md | 133 +++++ .../charm/zeppelin/layer-zeppelin/actions.yaml | 7 + .../zeppelin/layer-zeppelin/actions/restart | 36 ++ .../zeppelin/layer-zeppelin/actions/smoke-test | 93 ++++ .../src/charm/zeppelin/layer-zeppelin/copyright | 16 + .../src/charm/zeppelin/layer-zeppelin/icon.svg | 486 +++++++++++++++++++ .../charm/zeppelin/layer-zeppelin/layer.yaml | 38 ++ .../lib/charms/layer/bigtop_zeppelin.py | 243 ++++++++++ .../charm/zeppelin/layer-zeppelin/metadata.yaml | 16 + .../layer-zeppelin/reactive/zeppelin.py | 160 ++++++ .../resources/flume-tutorial/note.json | 1 + .../resources/hdfs-tutorial/note.json | 340 +++++++++++++ .../layer-zeppelin/tests/01-zeppelin-smoke.py | 56 +++ .../zeppelin/layer-zeppelin/tests/tests.yaml | 3 + .../zeppelin/layer-zeppelin/wheelhouse.txt | 1 + 15 files changed, 1629 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/README.md ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/README.md b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/README.md new file mode 100644 index 0000000..bf37307 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/README.md @@ -0,0 +1,133 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +# Overview + +Apache Zeppelin is a web-based notebook that enables interactive data analytics. +It allows for beautiful data-driven, interactive, and collaborative documents +with SQL, Scala and more. + +As a Multi-purpose Notebook, Apache Zeppelin is the place for interactive: + + * Data Ingestion + * Data Discovery + * Data Analytics + * Data Visualization & Collaboration + +This charm deploys the Zeppelin component of the Apache Bigtop platform. + + +# Deploying + +A working Juju installation is assumed to be present. If Juju is not yet set +up, please follow the +[getting-started](https://jujucharms.com/docs/2.0/getting-started) +instructions prior to deploying this charm. + +This charm is intended to be deployed via one of the +[apache bigtop bundles](https://jujucharms.com/u/bigdata-charmers/#bundles). +For example: + + juju deploy hadoop-processing + +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, use [juju-quickstart](https://launchpad.net/juju-quickstart) with the +following syntax: `juju quickstart hadoop-processing`. + +This will deploy an Apache Bigtop Hadoop cluster. More information about this +deployment can be found in the [bundle readme](https://jujucharms.com/hadoop-processing/). + +Now add Zeppelin and relate it to the cluster via the hadoop-plugin: + + juju deploy zeppelin + juju add-relation zeppelin plugin + +To access the web console, find the `PUBLIC-ADDRESS` of the +zeppelin application and expose it: + + juju status zeppelin + juju expose zeppelin + +The web interface will be available at the following URL: + + http://ZEPPELIN_PUBLIC_IP:9080 + + +# Verifying + +## Status +Apache Bigtop charms provide extended status reporting to indicate when they +are ready: + + juju status + +This is particularly useful when combined with `watch` to track the on-going +progress of the deployment: + + watch -n 0.5 juju status + +The message column will provide information about a given unit's state. +This charm is ready for use once the status message indicates that it is +ready. + +## Smoke Test +This charm provides a `smoke-test` action that can be used to verify the +application is functioning as expected. Run the action as follows: + + juju run-action zeppelin/0 smoke-test + +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action do zeppelin/0 smoke-test`. + +Watch the progress of the smoke test actions with: + + watch -n 0.5 juju show-action-status + +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action status`. + +Eventually, the action should settle to `status: completed`. If it +reports `status: failed`, the application is not working as expected. Get +more information about a specific smoke test with: + + juju show-action-output <action-id> + +> **Note**: The above assumes Juju 2.0 or greater. If using an earlier version +of Juju, the syntax is `juju action fetch <action-id>`. + + +# Network-Restricted Environments + +Charms can be deployed in environments with limited network access. To deploy +in this environment, configure a Juju model with appropriate +proxy and/or mirror options. See +[Configuring Models](https://jujucharms.com/docs/2.0/models-config) for more +information. + + +# Contact Information + +- <[email protected]> + + +# Resources + +- [Apache Bigtop](http://bigtop.apache.org/) home page +- [Apache Bigtop issue tracking](http://bigtop.apache.org/issue-tracking.html) +- [Apache Bigtop mailing lists](http://bigtop.apache.org/mail-lists.html) +- [Juju Bigtop charms](https://jujucharms.com/q/apache/bigtop) +- [Juju mailing list](https://lists.ubuntu.com/mailman/listinfo/juju) +- [Juju community](https://jujucharms.com/community) http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions.yaml ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions.yaml b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions.yaml new file mode 100644 index 0000000..7ce817e --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions.yaml @@ -0,0 +1,7 @@ +smoke-test: + description: > + Verify that Zeppelin is working by running one of + the example notebook paragraphs, using the REST server. +restart: + description: > + Restart Zeppelin. http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/restart ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/restart b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/restart new file mode 100755 index 0000000..bb37376 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/restart @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +sys.path.append('lib') + +from charmhelpers.core import hookenv +from charms.reactive import is_state +from charms.layer.bigtop_zeppelin import Zeppelin + + +def fail(msg): + hookenv.action_fail(msg) + sys.exit() + + +if not is_state('zeppelin.installed'): + fail('Zeppelin service not yet ready') + + +zep = Zeppelin() +zep.restart() http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/smoke-test ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/smoke-test b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/smoke-test new file mode 100755 index 0000000..0339d46 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/actions/smoke-test @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import json +from time import sleep +from urllib.parse import urljoin +from operator import itemgetter + +import requests + +from charmhelpers.core import hookenv +from charms.reactive import is_state + + +def fail(msg): + hookenv.action_fail(msg) + sys.exit() + + +if not is_state('zeppelin.installed'): + fail('Zeppelin not yet ready') + + +notebook_id = '2A94M5J1Z' +zep_addr = 'localhost' +base_url = 'http://{}:9080/api/notebook/'.format(zep_addr) +interp_url = urljoin(base_url, 'interpreter/bind/%s' % notebook_id) +job_url = urljoin(base_url, 'job/%s' % notebook_id) +para_url = urljoin(base_url, '%s/paragraph/' % notebook_id) + +try: + # bind interpreters + resp = requests.get(interp_url, timeout=60) + resp.raise_for_status() + interpreters = resp.json() + interp_ids = list(map(itemgetter('id'), interpreters['body'])) + resp = requests.put(interp_url, data=json.dumps(interp_ids), timeout=60) + resp.raise_for_status() + + # run notebook + resp = requests.post(job_url, timeout=60) + resp.raise_for_status() + for i in range(5): + sleep(10) # sleep first to give the job some time to run + try: + resp = requests.get(job_url, timeout=60) + except requests.exceptions.Timeout: + # sometimes a long-running paragraph will cause the notebook + # job endpoint to timeout, but it may eventually recover + continue + if resp.status_code == 500: + # sometimes a long-running paragraph will cause the notebook + # job endpoint to return 500, but it may eventually recover + continue + statuses = list(map(itemgetter('status'), resp.json()['body'])) + in_progress = {'PENDING', 'RUNNING'} & set(statuses) + if not in_progress: + break + + # check for errors + errors = [] + body = resp.json()['body'] + for result in body: + if result['status'] == 'ERROR': + para_id = result['id'] + resp = requests.get(urljoin(para_url, para_id), timeout=60) + resp.raise_for_status() + para = resp.json()['body'] + if 'errorMessage' in para: + errmsg = para['errorMessage'].splitlines()[0] + elif 'result' in para: + errmsg = para['result']['msg'].splitlines()[0] + else: + errmsg = 'Unable to find error message' + hookenv.action_set('paragraph', resp.text) + fail('Notebook failed: {}'.format(errmsg)) +except requests.exceptions.RequestException as e: + fail('Request failed: {}: {}'.format(e.request.url, e)) http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/copyright ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/copyright b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/copyright new file mode 100644 index 0000000..e900b97 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/copyright @@ -0,0 +1,16 @@ +Format: http://dep.debian.net/deps/dep5/ + +Files: * +Copyright: Copyright 2015, Canonical Ltd., All Rights Reserved. +License: Apache License 2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/icon.svg ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/icon.svg b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/icon.svg new file mode 100644 index 0000000..88e23c9 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/icon.svg @@ -0,0 +1,486 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="96" + height="96" + id="svg6517" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="icon.svg"> + <defs + id="defs6519"> + <linearGradient + inkscape:collect="always" + xlink:href="#Background" + id="linearGradient6461" + gradientUnits="userSpaceOnUse" + x1="0" + y1="970.29498" + x2="144" + y2="970.29498" + gradientTransform="matrix(0,-0.66666669,0.6660448,0,-832.14561,847.86846)" /> + <linearGradient + id="Background"> + <stop + id="stop4178" + offset="0" + style="stop-color:#10b1ef;stop-opacity:1;" /> + <stop + id="stop4180" + offset="1" + style="stop-color:#c9c9c9;stop-opacity:1" /> + </linearGradient> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Inner Shadow" + id="filter1121"> + <feFlood + flood-opacity="0.59999999999999998" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood1123" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="out" + result="composite1" + id="feComposite1125" /> + <feGaussianBlur + in="composite1" + stdDeviation="1" + result="blur" + id="feGaussianBlur1127" /> + <feOffset + dx="0" + dy="2" + result="offset" + id="feOffset1129" /> + <feComposite + in="offset" + in2="SourceGraphic" + operator="atop" + result="composite2" + id="feComposite1131" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter950"> + <feFlood + flood-opacity="0.25" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood952" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite954" /> + <feGaussianBlur + in="composite1" + stdDeviation="1" + result="blur" + id="feGaussianBlur956" /> + <feOffset + dx="0" + dy="1" + result="offset" + id="feOffset958" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite960" /> + </filter> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath873"> + <g + transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)" + id="g875" + inkscape:label="Layer 1" + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"> + <path + style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline" + d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z" + id="path877" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssssss" /> + </g> + </clipPath> + <filter + inkscape:collect="always" + id="filter891" + inkscape:label="Badge Shadow"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.71999962" + id="feGaussianBlur893" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#Background" + id="linearGradient3134" + x1="5.0652875" + y1="50.451253" + x2="90" + y2="51" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(-255.97409,684.13067)" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="4.0745362" + inkscape:cx="18.514671" + inkscape:cy="49.018169" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1031" + inkscape:window-height="1896" + inkscape:window-x="49" + inkscape:window-y="24" + inkscape:window-maximized="1" + showborder="true" + showguides="true" + inkscape:guide-bbox="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid821" /> + <sodipodi:guide + orientation="1,0" + position="16,48" + id="guide823" /> + <sodipodi:guide + orientation="0,1" + position="64,80" + id="guide825" /> + <sodipodi:guide + orientation="1,0" + position="80,40" + id="guide827" /> + <sodipodi:guide + orientation="0,1" + position="64,16" + id="guide829" /> + </sodipodi:namedview> + <metadata + id="metadata6522"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="BACKGROUND" + inkscape:groupmode="layer" + id="layer1" + transform="translate(268,-635.29076)" + style="display:inline"> + <path + style="color:#000000;fill:#a0a0a0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="" + id="path3140" + inkscape:connector-curvature="0" + transform="translate(-268,635.29076)" /> + <path + style="color:#000000;fill:#a1b4bb;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.2454267;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m -248.93557,732.01702 c -5.28927,-0.46936 -9.72782,-1.8431 -12.54221,-3.88183 -2.15434,-1.5606 -4.10069,-4.54273 -4.99322,-7.65046 -0.52933,-1.84308 -1.05542,-5.60148 -1.36264,-9.7347 -0.31315,-4.21296 -0.30401,-43.3237 0.0114,-48.89694 0.38986,-6.88808 1.24042,-11.37707 2.71107,-14.30818 2.79041,-5.56148 7.0575,-7.75361 17.14746,-8.80914 4.40117,-0.46042 51.55611,-0.46042 55.95729,0 11.55122,1.20839 16.00758,4.11773 18.27096,11.92817 0.63745,2.1997 1.36317,6.78141 1.36317,8.60612 0,0.59482 0.0847,1.13383 0.18816,1.19779 0.31305,0.19347 0.374,45.16411 0.0672,49.61159 -0.66327,9.61657 -1.91214,13.63748 -5.2599,16.93499 -2.86188,2.81893 -5.73688,3.93245 -12.36882,4.7906 -3.55249,0.45969 -5.16533,0.48373 -30.3102,0.45177 -14.60693,-0.0186 -27.60284,-0.12646 -28.87979,-0.23978 l 0,0 z" + id="path3172" + inkscape:connector-curvature="0" /> + <image + y="656.29077" + x="-263" + id="image3188" + xlink:href="data:image/png;base64,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 +RK5CYII= +" + height="55.000042" + width="83.820099" /> + <path + style="fill:#a1b4bb;fill-opacity:1" + d="m 5.0652875,74.182812 c 0,-1.644894 0.059004,-1.933067 0.3712655,-1.813242 0.2041961,0.07836 0.5079117,0.03407 0.6749235,-0.09841 0.1670118,-0.132486 0.7454259,-0.319654 1.2853647,-0.415927 0.5399388,-0.09627 1.3130329,-0.325824 1.7179869,-0.510112 0.7800115,-0.354976 4.1759739,-1.096834 5.0209009,-1.096834 0.27566,0 0.5012,-0.110442 0.5012,-0.245427 0,-0.134985 0.165663,-0.245427 0.36814,-0.245427 0.459824,0 0.477938,0.09776 -0.246454,-1.330085 -0.336896,-0.664053 -0.612539,-1.354316 -0.612539,-1.533917 0,-0.1796 -0.15015,-0.326545 -0.333667,-0.326545 -0.40361,0 -3.339856,1.471741 -3.794761,1.902057 -0.1783727,0.168731 -0.4651897,0.306784 -0.637371,0.306784 -0.1721813,0 -0.5677382,0.220884 -0.8790156,0.490853 -0.3112774,0.269969 -0.7147231,0.490853 -0.8965462,0.490853 -0.1818229,0 -0.5253754,0.193274 -0.7634499,0.429497 -0.2380744,0.236223 -0.7350635,0.565181 -1.1044202,0.731018 l -0.6715576,0.301522 0,-25.028262 0,-25.028263 28.6535675,0.03677 c 15.759463,0.02022 28.3222 42,0.07368 27.917288,0.118796 -2.510585,0.279686 -4.943318,0.664366 -5.153961,0.81498 -0.134984,0.09652 -0.908078,0.316283 -1.717986,0.488369 -0.809909,0.172085 -1.941942,0.52442 -2.515632,0.782966 -0.573687,0.258546 -1.374392,0.536349 -1.779343,0.617339 -0.404949,0.08099 -0.736273,0.243891 -0.736273,0.362003 0,0.118112 -0.156491,0.214748 -0.347757,0.214748 -0.432162,0 -3.519027,1.622883 -3.998097,2.101953 -0.193772,0.193773 -0.513555,0.352314 -0.710626,0.352314 -0.197072,0 -0.420271,0.161459 -0.495997,0.358798 -0.07572,0.197338 -0.328894,0.419486 -0.562599,0.49366 -0.233703,0.07418 -0.637658,0.326873 -0.897675,0.561553 -0.26002,0.234679 -0.950842,0.751454 -1.535159,1.148389 -0.584317,0.396934 -1.509114,1.142418 -2.055105,1.65663 -0.545989,0.514212 -1.071244,0.934931 -1.167233,0.934931 -0.09599,0 -0.463093,0.303716 -0.815786,0.674924 -0.352695,0.371208 -1.036856,1.006249 -1.520362,1.411203 -2.714291,2.27333 -5.42323,4.745103 -9.988905,9.11438 -4.044712,3.870725 -8.055597,8.556004 -8 .055597,9.410075 0,0.137878 -0.09367,0.250688 -0.208153,0.250688 -0.114484,0 -0.402637,0.469379 -0.640339,1.043064 -0.237702,0.573685 -0.6287,1.484831 -0.868884,2.02477 -1.194238,2.684674 -1.505876,7.84213 -0.630268,10.430635 0.251135,0.742416 0.535356,1.405068 0.631601,1.47256 0.09625,0.06749 0.348306,0.46116 0.560133,0.874817 0.556959,1.087625 3.658447,4.032763 4.247547,4.033418 0.148077,1.65e-4 0.523912,0.221184 0.83519,0.491153 0.311277,0.269969 0.687112,0.491533 0.835189,0.492365 0.148077,7.36e-4 0.56227,0.154406 0.920429,0.341276 0.358159,0.186873 1.076032,0.469992 1.595274,0.629156 0.519241,0.159164 1.164959,0.394445 1.434929,0.522845 0.269969,0.1284 0.987842,0.32949 1.595273,0.446863 0.695999,0.134489 -3.320148,0.223353 -10.860132,0.240297 l -11.9645515,0.02689 0,-1.955712 z M 23.618175,44.198096 c 0.797164,-0.694149 1.712944,-1.522464 2.035067,-1.8407 0.322122,-0.318236 0.669243,-0.578611 0.771378,-0.578611 0.102136,0 0.252788,-0.174823 0.334782,-0.388496 0.181106,-0.471955 -1.37429,-2.069686 -2.003086,-2.057606 -1.159466,0.02227 -2.689852,2.471904 -2.920011,4.673949 -0.09356,0.895154 -0.05888,1.453553 0.09028,1.453553 0.133212,0 0.894428,-0.56794 1.691593,-1.262089 l 0,0 z" + id="path3191" + inkscape:connector-curvature="0" + transform="translate(-268,635.29076)" /> + <path + style="fill:#a1b4bb;fill-opacity:1" + d="m 40.161307,75.75772 c 4.119556,-0.456634 4.092711,-0.445791 4.069198,-1.643454 l -0.01966,-1.001272 1.595274,-0.356919 c 0.8774,-0.196305 1.650747,-0.444181 1.718549,-0.550836 0.0678,-0.106655 0.5426,-0.272583 1.055106,-0.368729 0.512507,-0.09615 1.064464,-0.301541 1.226571,-0.456432 0.162108,-0.154889 0.929783,-0.478553 1.705945,-0.719248 0.776162,-0.240697 1.411203,-0.533653 1.411203,-0.651014 0,-0.117363 0.338789,-0.287797 0.752864,-0.378742 0.414075,-0.09095 0.809145,-0.312023 0.877933,-0.491283 0.141606,-0.369019 1.820455,-1.100349 2.525972,-1.100349 0.255989,0 0.50985,-0.133248 0.564136,-0.296106 0.07158,-0.214742 1.217678,-0.299041 4.170932,-0.306784 3.978475,-0.01043 4.086855,0.003 4.707569,0.584315 0.349438,0.327245 0.635343,0.741403 0.635343,0.920349 0,0.178948 0.220884,0.32536 0.490854,0.32536 0.269969,0 0.490853,-0.09027 0.490853,-0.200607 0,-0.110334 0.607431,-0.392082 1.349847,-0.626108 0.742416,-0.234025 1.349847,-0.516494 1.349847,-0.627708 0,-0.111214 0.3 18014,-0.26581 0.706696,-0.343547 0.388685,-0.07774 0.858064,-0.285082 1.043064,-0.460767 0.185002,-0.175685 0.771008,-0.540045 1.302236,-0.809689 0.531229,-0.269644 1.152288,-0.655924 1.380135,-0.858401 0.227847,-0.202477 0.786475,-0.644245 1.241396,-0.981707 1.085222,-0.805019 3.652688,-3.360902 3.652688,-3.63621 0,-0.118426 0.358936,-0.602475 0.797636,-1.075664 1.125913,-1.214429 1.620433,-1.893239 2.199412,-3.019067 0.277675,-0.539939 0.585634,-1.036928 0.684352,-1.104421 0.09872,-0.06749 0.430044,-0.619702 0.73628,-1.227133 0.306236,-0.607431 0.63165,-1.159641 0.72314,-1.227134 0.09149,-0.06749 0.424834,-0.785365 0.740757,-1.595273 0.315923,-0.809908 0.827,-2.04109 1.135724,-2.735959 0.308725,-0.694869 0.700519,-1.937341 0.870654,-2.76105 0.170132,-0.823709 0.39768,-1.497652 0.505658,-1.497652 0.107975,0 0.196321,7.12351 0.196321,15.830022 l 0,15.830023 -25.708447,-0.03398 C 40.77683,76.075113 37.716333,76.028732 40.161307,75.75772 z m 20.689674,-0.510714 c 1.91667,-0.08859 3.7 09853,-0.243895 3.984852,-0.345129 0.399176,-0.146946 0.563379,-0.561281 0.814338,-2.054827 0.172891,-1.028922 0.47181,-2.172621 0.664269,-2.541551 0.483106,-0.926078 0.450064,-1.110006 -0.321973,-1.792301 -0.644294,-0.569401 -0.821654,-0.595747 -3.90828,-0.580556 -1.910105,0.0094 -3.366499,0.123596 -3.558688,0.279034 -0.178931,0.144717 -0.794129,0.340439 -1.367107,0.434939 -0.572978,0.0945 -1.10507,0.274228 -1.182429,0.399396 -0.07736,0.12517 -0.595613,0.459684 -1.151677,0.743366 -0.556065,0.283683 -1.163725,0.668485 -1.350357,0.855118 -0.186632,0.186632 -0.607713,0.406691 -0.935736,0.48902 -0.328022,0.08233 -0.596405,0.234593 -0.596405,0.33837 0,0.103773 -0.773094,0.389195 -1.717987,0.634268 -0.944892,0.245076 -1.717986,0.519652 -1.717986,0.61017 0,0.09052 -0.690263,0.310644 -1.533917,0.489165 -1.732683,0.366641 -2.252917,0.74165 -2.02501,1.459722 0.132449,0.417311 0.385353,0.496125 1.673408,0.521507 0.834558,0.01644 3.450114,0.106552 5.812346,0.200241 2.362232,0.09369 4.438633,0. 136767 4.614225,0.09573 0.175591,-0.04103 1.887443,-0.147091 3.804114,-0.235681 z" + id="path3193" + inkscape:connector-curvature="0" + transform="translate(-268,635.29076)" /> + <path + style="fill:#a1b4bb;fill-opacity:1" + d="m 88.428559,34.988646 c -0.08999,-0.08999 -0.163618,-0.386447 -0.163618,-0.658793 0,-0.635276 -0.969876,-2.676072 -1.393673,-2.932542 -0.178373,-0.107946 -0.324314,-0.344751 -0.324314,-0.526234 0,-0.337656 -3.14023,-3.631128 -4.653855,-4.880962 -1.118614,-0.923664 -5.006105,-2.973691 -6.390347,-3.36988 -0.607431,-0.173855 -1.214862,-0.394911 -1.349847,-0.491236 -0.230232,-0.164293 -2.185515,-0.609134 -3.558687,-0.809627 -0.337462,-0.04927 3.610841,-0.104625 8.774005,-0.123008 l 9.387571,-0.03342 0,6.994662 c 0,3.847063 -0.03681,6.994661 -0.08181,6.994661 -0.045,0 -0.155438,-0.07363 -0.245427,-0.163618 z" + id="path3195" + inkscape:connector-curvature="0" + transform="translate(-268,635.29076)" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="PLACE YOUR PICTOGRAM HERE" + style="display:inline" /> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="BADGE" + style="display:none" + sodipodi:insensitive="true"> + <g + style="display:inline" + transform="translate(-340.00001,-581)" + id="g4394" + clip-path="none"> + <g + id="g855"> + <g + inkscape:groupmode="maskhelper" + id="g870" + clip-path="url(#clipPath873)" + style="opacity:0.6;filter:url(#filter891)"> + <path + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)" + d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" + sodipodi:ry="12" + sodipodi:rx="12" + sodipodi:cy="552.36218" + sodipodi:cx="252" + id="path844" + style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + sodipodi:type="arc" /> + </g> + <g + id="g862"> + <path + sodipodi:type="arc" + style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="path4398" + sodipodi:cx="252" + sodipodi:cy="552.36218" + sodipodi:rx="12" + sodipodi:ry="12" + d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" + transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" /> + <path + transform="matrix(1.25,0,0,1.25,33,-100.45273)" + d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" + sodipodi:ry="12" + sodipodi:rx="12" + sodipodi:cy="552.36218" + sodipodi:cx="252" + id="path4400" + style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + sodipodi:type="arc" /> + <path + sodipodi:type="star" + style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="path4459" + sodipodi:sides="5" + sodipodi:cx="666.19574" + sodipodi:cy="589.50385" + sodipodi:r1="7.2431178" + sodipodi:r2="4.3458705" + sodipodi:arg1="1.0471976" + sodipodi:arg2="1.6755161" + inkscape:flatsided="false" + inkscape:rounded="0.1" + inkscape:randomized="0" + d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z" + transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" /> + </g> + </g> + </g> + </g> +</svg> http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/layer.yaml ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/layer.yaml b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/layer.yaml new file mode 100644 index 0000000..19626b4 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/layer.yaml @@ -0,0 +1,38 @@ +repo: [email protected]:juju-solutions/bigtop.git +includes: + - 'layer:apache-bigtop-base' + - 'layer:hadoop-client' + - 'interface:hive' + - 'interface:spark' + - 'interface:zeppelin' +options: + basic: + packages: + - 'unzip' + apache-bigtop-base: + groups: + - 'hadoop' + - 'zeppelin' + users: + ubuntu: + groups: ['hadoop', 'zeppelin'] + dirs: + zeppelin: + path: '/usr/lib/zeppelin' + zeppelin_conf: + path: '/etc/zeppelin/conf' + zeppelin_logs: + path: '/var/log/zeppelin' + zeppelin_notebooks: + path: '/var/lib/zeppelin/notebook' + ports: + # Ports that need to be exposed, overridden, or manually specified. + # Only expose ports serving a UI or external API (i.e., namenode and + # resourcemanager). Communication among units within the cluster does + # not need ports to be explicitly opened. + zeppelin: + port: 9080 + exposed_on: 'zeppelin' + zeppelin_web: + port: 9081 + exposed_on: 'zeppelin' http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/lib/charms/layer/bigtop_zeppelin.py ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/lib/charms/layer/bigtop_zeppelin.py b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/lib/charms/layer/bigtop_zeppelin.py new file mode 100644 index 0000000..b600f4b --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/lib/charms/layer/bigtop_zeppelin.py @@ -0,0 +1,243 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import json +import time +import socket +from urllib.parse import urljoin + +import requests +from path import Path + +from jujubigdata import utils +from charmhelpers.core import hookenv, host, unitdata +from charms import layer +from charms.layer.apache_bigtop_base import Bigtop + + +class Zeppelin(object): + """ + This class manages Zeppelin. + """ + def __init__(self): + self.dist_config = utils.DistConfig( + data=layer.options('apache-bigtop-base')) + + def _add_override(self, name, value): + unitdata.kv().update({ + name: value, + }, prefix='zeppelin.bigtop.overrides.') + + def install(self): + ''' + Trigger the Bigtop puppet recipe that handles the Zepplin service. + ''' + # Dirs are handled by the bigtop deb, so no need to call out to + # dist_config to do that work. However, we want to adjust the + # groups for the `ubuntu` user for better interaction with Juju. + self.dist_config.add_users() + self._add_override('zeppelin::server::server_port', + self.dist_config.port('zeppelin')) + self._add_override('zeppelin::server::web_socket_port', + self.dist_config.port('zeppelin_web')) + self.trigger_bigtop() + + def trigger_bigtop(self): + bigtop = Bigtop() + overrides = unitdata.kv().getrange('zeppelin.bigtop.overrides.', + strip=True) + bigtop.render_site_yaml( + roles=[ + 'zeppelin-server', + ], + overrides=overrides, + ) + bigtop.trigger_puppet() + self.wait_for_api(30) + + def setup_etc_env(self): + ''' + Write some niceties to /etc/environment + ''' + # Configure system-wide bits + zeppelin_bin = self.dist_config.path('zeppelin') / 'bin' + zeppelin_conf = self.dist_config.path('zeppelin_conf') + with utils.environment_edit_in_place('/etc/environment') as env: + if zeppelin_bin not in env['PATH']: + env['PATH'] = ':'.join([env['PATH'], zeppelin_bin]) + env['ZEPPELIN_CONF_DIR'] = zeppelin_conf + + def reconfigure_zeppelin(self): + ''' + Configure zeppelin based on current environment + ''' + raise NotImplementedError() + # NB (kwm): this method is not currently called because Bigtop spark + # doesn't expose these settings. Leaving this here just in case + # we update the bigtop charms to provide these bits in the future. + etc_env = utils.read_etc_env() + hadoop_extra_classpath = etc_env.get('HADOOP_EXTRA_CLASSPATH', '') + spark_driver_mem = etc_env.get('SPARK_DRIVER_MEMORY', '1g') + spark_exe_mode = os.environ.get('MASTER', 'yarn-client') + spark_executor_mem = etc_env.get('SPARK_EXECUTOR_MEMORY', '1g') + zeppelin_env = self.dist_config.path('zeppelin_conf') / 'zeppelin-env.sh' + with open(zeppelin_env, "a") as f: + f.write('export ZEPPELIN_CLASSPATH_OVERRIDES={}\n'.format(hadoop_extra_classpath)) + f.write('export ZEPPELIN_JAVA_OPTS="-Dspark.driver.memory={} -Dspark.executor.memory={}"\n'.format( + spark_driver_mem, + spark_executor_mem)) + f.write('export SPARK_SUBMIT_OPTIONS="--driver-memory {} --executor-memory {}"\n'.format( + spark_driver_mem, + spark_executor_mem)) + f.write('export MASTER={}\n'.format(spark_exe_mode)) + + def configure_hadoop(self): + # create hdfs storage space + utils.run_as('hdfs', 'hdfs', 'dfs', '-mkdir', '-p', '/user/zeppelin') + utils.run_as('hdfs', 'hdfs', 'dfs', '-chown', 'zeppelin', '/user/zeppelin') + + def configure_spark(self, master_url): + ''' + Configure the zeppelin spark interpreter + ''' + # TODO: Need Puppet params created to set Spark driver and executor memory + self._add_override('zeppelin::server::spark_master_url', master_url) + self.trigger_bigtop() + + def configure_hive(self, hive_url): + ''' + Configure the zeppelin hive interpreter + ''' + self._add_override('zeppelin::server::hiveserver2_url', hive_url) + self.trigger_bigtop() + + def restart(self): + self.stop() + self.start() + + def start(self): + host.service_start('zeppelin') + + def check_connect(self, addr, port): + try: + with socket.create_connection((addr, port), timeout=10): + return True + except OSError: + return False + + def wait_for_api(self, timeout): + start = time.time() + while time.time() - start < timeout: + if self.check_connect('localhost', self.dist_config.port('zeppelin')): + return True + time.sleep(2) + raise utils.TimeoutError('Timed-out waiting for connection to Zeppelin') + + def stop(self): + host.service_stop('zeppelin') + + def open_ports(self): + for port in self.dist_config.exposed_ports('zeppelin'): + hookenv.open_port(port) + + def close_ports(self): + for port in self.dist_config.exposed_ports('zeppelin'): + hookenv.close_port(port) + + def register_notebook(self, local_id, contents): + api = ZeppelinAPI() + kv = unitdata.kv() + notebook_ids = kv.get('zeppelin.notebooks.ids', {}) + if local_id in notebook_ids: + hookenv.log('Replacing notebook {} registered as {}'.format( + local_id, notebook_ids[local_id])) + api.delete_notebook(notebook_ids[local_id]) + zeppelin_id = api.import_notebook(contents) + if zeppelin_id: + notebook_ids[local_id] = zeppelin_id + hookenv.log('Registered notebook {} as {}'.format(local_id, + zeppelin_id)) + return True + else: + hookenv.log('Unable to register notebook: {}'.format(local_id), + hookenv.ERROR) + return False + kv.set('zeppelin.notebooks.ids', notebook_ids) + + def remove_notebook(self, local_id): + api = ZeppelinAPI() + kv = unitdata.kv() + notebook_ids = kv.get('zeppelin.notebooks.ids', {}) + if local_id in notebook_ids: + api.delete_notebook(notebook_ids[local_id]) + del notebook_ids[local_id] + else: + hookenv.log('Notebook not registered: {}'.format(local_id), + hookenv.ERROR) + kv.set('zeppelin.notebooks.ids', notebook_ids) + + def register_hadoop_notebooks(self): + for notebook in ('hdfs-tutorial', 'flume-tutorial'): + contents = (Path('resources') / notebook / 'note.json').text() + self.register_notebook(notebook, contents) + + def remove_hadoop_notebooks(self): + for notebook in ('hdfs-tutorial', 'flume-tutorial'): + self.remove_notebook(notebook) + + +class ZeppelinAPI(object): + """ + Helper for interacting with the Appache Zeppelin REST API. + """ + def _url(self, *parts): + dc = utils.DistConfig( + data=layer.options('apache-bigtop-base')) + url = 'http://localhost:{}/api/'.format(dc.port('zeppelin')) + for part in parts: + url = urljoin(url, part) + return url + + def import_notebook(self, contents): + response = requests.post(self._url('notebook'), data=contents) + if response.status_code != 201: + return None + return response.json()['body'] + + def delete_notebook(self, notebook_id): + requests.delete(self._url('notebook/', notebook_id)) + + def modify_interpreter(self, interpreter_name, properties): + response = requests.get(self._url('interpreter/', 'setting')) + try: + body = response.json()['body'] + except json.JSONDecodeError: + hookenv.log('Invalid response from API server: {} {}'.format( + response, response.text), hookenv.ERROR) + raise + for interpreter_data in body: + if interpreter_data['name'] == interpreter_name: + break + else: + raise ValueError('Interpreter not found: {}'.format( + interpreter_name)) + interpreter_data['properties'].update(properties) + response = requests.put(self._url('interpreter/', 'setting/', + interpreter_data['id']), + data=json.dumps(interpreter_data)) + if response.status_code != 200: + raise ValueError('Unable to update interpreter: {}'.format( + response.text)) http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/metadata.yaml ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/metadata.yaml b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/metadata.yaml new file mode 100644 index 0000000..f6f4be6 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/metadata.yaml @@ -0,0 +1,16 @@ +name: zeppelin +summary: A web-based notebook that enables interactive data analytics. +maintainer: Juju Big Data <[email protected]> +description: | + Apache Zeppelin is a web-based notebook that enables interactive data + analytics. You can make beautiful data-driven, interactive, and collaborative + documents with SQL, Scala and more. +tags: ["analytics"] +provides: + client: + interface: zeppelin +requires: + hive: + interface: hive + spark: + interface: spark http://git-wip-us.apache.org/repos/asf/bigtop/blob/f7d471b4/bigtop-packages/src/charm/zeppelin/layer-zeppelin/reactive/zeppelin.py ---------------------------------------------------------------------- diff --git a/bigtop-packages/src/charm/zeppelin/layer-zeppelin/reactive/zeppelin.py b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/reactive/zeppelin.py new file mode 100644 index 0000000..740e1e0 --- /dev/null +++ b/bigtop-packages/src/charm/zeppelin/layer-zeppelin/reactive/zeppelin.py @@ -0,0 +1,160 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import hashlib + +from charms.reactive import when, when_not +from charms.reactive import is_state, set_state, remove_state +from charmhelpers.core import hookenv +from charms.layer.bigtop_zeppelin import Zeppelin +from charms.reactive.helpers import data_changed + + +@when('zeppelin.installed') +def update_status(): + hadoop_joined = is_state('hadoop.joined') + hadoop_ready = is_state('hadoop.ready') + hive_joined = is_state('hive.connected') + hive_ready = is_state('hive.available') + spark_joined = is_state('spark.joined') + spark_ready = is_state('spark.ready') + + waiting_apps = [] + ready_apps = [] + # Check status of the hadoop plugin + if hadoop_joined and not hadoop_ready: + waiting_apps.append('hadoop') + elif hadoop_ready: + ready_apps.append('hadoop') + + # Check status of Hive + if hive_joined and not hive_ready: + waiting_apps.append('hive') + elif hive_ready: + ready_apps.append('hive') + + # Check status of Spark + if spark_joined and not spark_ready: + waiting_apps.append('spark') + elif spark_ready: + ready_apps.append('spark') + + # Set appropriate status based on the apps we checked above + if waiting_apps: + hookenv.status_set('waiting', + 'waiting for: {}'.format(' & '.join(waiting_apps))) + elif ready_apps: + hookenv.status_set('active', + 'ready with: {}'.format(' & '.join(ready_apps))) + else: + hookenv.status_set('active', 'ready') + + +@when('bigtop.available') +@when_not('zeppelin.installed') +def initial_setup(): + hookenv.status_set('maintenance', 'installing zeppelin') + zeppelin = Zeppelin() + zeppelin.install() + zeppelin.setup_etc_env() + zeppelin.open_ports() + set_state('zeppelin.installed') + update_status() + + +@when('zeppelin.installed') +@when('hadoop.ready') +@when_not('zeppelin.hadoop.configured') +def configure_hadoop(hadoop): + zeppelin = Zeppelin() + zeppelin.configure_hadoop() + zeppelin.register_hadoop_notebooks() + set_state('zeppelin.hadoop.configured') + + +@when('zeppelin.installed') +@when_not('hadoop.ready') +@when('zeppelin.hadoop.configured') +def unconfigure_hadoop(): + zeppelin = Zeppelin() + zeppelin.remove_hadoop_notebooks() + remove_state('zeppelin.hadoop.configured') + + +@when('zeppelin.installed', 'hive.ready') +def configure_hive(hive): + hive_ip = hive.get_private_ip() + hive_port = hive.get_port() + hive_url = 'jdbc:hive2://%s:%s' % (hive_ip, hive_port) + if data_changed('hive.connect', hive_url): + hookenv.status_set('maintenance', 'configuring hive') + zeppelin = Zeppelin() + zeppelin.configure_hive(hive_url) + set_state('zeppelin.hive.configured') + update_status() + + +@when('zeppelin.installed', 'zeppelin.hive.configured') +@when_not('hive.ready') +def unconfigure_hive(): + hookenv.status_set('maintenance', 'removing hive relation') + zeppelin = Zeppelin() + zeppelin.configure_hive('jdbc:hive2://:') + remove_state('zeppelin.hive.configured') + update_status() + + +@when('zeppelin.installed', 'spark.ready') +def configure_spark(spark): + master_url = spark.get_master_url() + if data_changed('spark.master', master_url): + hookenv.status_set('maintenance', 'configuring spark') + zeppelin = Zeppelin() + zeppelin.configure_spark(master_url) + set_state('zeppelin.spark.configured') + update_status() + + +@when('zeppelin.installed', 'zeppelin.spark.configured') +@when_not('spark.ready') +def unconfigure_spark(): + hookenv.status_set('maintenance', 'removing spark relation') + zeppelin = Zeppelin() + # Yarn / Hadoop may not actually be available, but that is the default + # value and nothing else would reasonably work here either without Spark. + zeppelin.configure_spark('yarn-client') + data_changed('spark.master', 'yarn-client') # ensure updated if re-added + remove_state('zeppelin.spark.configured') + update_status() + + +@when('zeppelin.started', 'client.notebook.registered') +def register_notebook(client): + zeppelin = Zeppelin() + for notebook in client.unregistered_notebooks(): + notebook_md5 = hashlib.md5(notebook.encode('utf8')).hexdigest() + if zeppelin.register_notebook(notebook_md5, notebook): + client.accept_notebook(notebook) + else: + client.reject_notebook(notebook) + + +@when('zeppelin.started', 'client.notebook.removed') +def remove_notebook(client): + zeppelin = Zeppelin() + for notebook in client.unremoved_notebooks(): + notebook_md5 = hashlib.md5(notebook.encode('utf8')).hexdigest() + zeppelin.remove_notebook(notebook_md5) + client.remove_notebook(notebook)
