Import of core OSM charm layers

Signed-off-by: Adam Israel <adam.israel@canonical.com>
Change-Id: Iff2c30e3b952d52891c190ec2d7a31a650e5ad53
diff --git a/charms/layers/vnfproxy/README.md b/charms/layers/vnfproxy/README.md
new file mode 100644
index 0000000..8379e50
--- /dev/null
+++ b/charms/layers/vnfproxy/README.md
@@ -0,0 +1,172 @@
+# vnfproxy
+
+## Overview
+
+This charm layer is intended for use by vendors who wish to integrate with
+OSM. The current release of OSM only supports a lightweight version of Juju
+charms, which we will refer to as "proxy charms". Consider the diagram below:
+
+```
++---------------------+    +---------------------+
+|                     <----+                     |
+|  Resource           |    |  Service            |
+|  Orchestrator (RO)  +---->  Orchestrator (SO)  |
+|                     |    |                     |
++------------------+--+    +-------+----^--------+
+                   |               |    |
+                   |               |    |
+                   |               |    |
+             +-----v-----+       +-v----+--+
+             |           <-------+         |
+             |  Virtual  |       |  Proxy  |
+             |  Machine  |       |  Charm  |
+             |           +------->         |
+             +-----------+       +---------+
+```
+
+The Virtual Machine (VM) is created by the Resource Orchestrator (RO), at the
+request of the Service Orchestrator (SO). Once the VM has been created, a
+"proxy charm" is deployed in order to facilitate operations between the SO and
+your service running within the VM.
+
+As such, a proxy charm will expose a number of "actions" that are run via the
+SO. By default, the following actions are exposed:
+
+```bash
+actions
+├── reboot
+├── restart
+├── run
+├── start
+└── stop
+```
+
+Some actions, such as `run` and `reboot`, do not require any additional configuration. `start`, `stop` and `restart`, however, will require you to
+implement the command(s) required to interact with your service.
+
+## Usage
+
+Create the framework for your proxy charm:
+
+```bash
+$ charm create pingpong
+$ cd pingpong
+```
+
+Modify `layer.yaml` to the following:
+```yaml
+includes:
+    - layer:basic
+    - layer:vnfproxy
+```
+
+The `metadata.yaml` describes your service. It should look similar to the following:
+
+```yaml
+name: vnfproxy
+summary: A layer for developing OSM "proxy" charms.
+maintainer: Adam Israel <adam.israel@canonical.com>
+description: |
+  VNF "proxy" charms are a lightweight version of a charm that, rather than
+  installing software on the same machine, execute commands over an ssh channel.
+series:
+  - trusty
+  - xenial
+tags:
+  - osm
+  - vnf
+subordinate: false
+```
+
+Implement the default action(s) you wish to support by adding the following code to reactive/pingpong.py and fill in the cmd to be run:
+
+```python
+@when('actions.start')
+def start():
+    err = ''
+    try:
+        cmd = ""
+        result, err = charms.sshproxy._run(cmd)
+    except:
+        action_fail('command failed:' + err)
+    else:
+        action_set({'outout': result})
+    finally:
+        remove_flag('actions.start')
+
+
+@when('actions.stop')
+def stop():
+    err = ''
+    try:
+        # Enter the command to stop your service(s)
+        cmd = "service myname stop"
+        result, err = charms.sshproxy._run(cmd)
+    except:
+        action_fail('command failed:' + err)
+    else:
+        action_set({'outout': result})
+    finally:
+        remove_flag('actions.stop')
+
+
+@when('actions.restart')
+def restart():
+    err = ''
+    try:
+        # Enter the command to restart your service(s)
+        cmd = "service myname restart"
+        result, err = charms.sshproxy._run(cmd)
+    except:
+        action_fail('command failed:' + err)
+    else:
+        action_set({'outout': result})
+    finally:
+        remove_flag('actions.restart')
+```
+
+Rename `README.ex` to `README.md` and describe your application and its usage.
+
+-- fix this. there are cases where the config is useful -- Delete `config.yaml`, since the charm's configuration will be driven by the SO.
+
+Create the `actions.yaml` file; this will describe the additional operations you would like to perform on or against your service.
+
+```yaml
+set-server:
+    description: "Set the target IP address and port"
+    params:
+        server-ip:
+            description: "IP on which the target service is listening."
+            type: string
+            default: ""
+        server-port:
+            description: "Port on which the target service is listening."
+            type: integer
+            default: 5555
+    required:
+        - server-ip
+set-rate:
+    description: "Set the rate of packet generation."
+    params:
+        rate:
+            description: "Packet rate."
+            type: integer
+            default: 5
+get-stats:
+    description: "Get the stats."
+get-state:
+    description: "Get the admin state of the target service."
+get-rate:
+    description: "Get the rate set on the target service."
+get-server:
+    description: "Get the target server and IP set"
+```
+
+
+Once you've implemented your actions, you need to compile the various charm layers:
+```bash
+$ charm build
+
+```
+
+## Contact
diff --git a/charms/layers/vnfproxy/actions.yaml b/charms/layers/vnfproxy/actions.yaml
new file mode 100644
index 0000000..a9ee2ba
--- /dev/null
+++ b/charms/layers/vnfproxy/actions.yaml
@@ -0,0 +1,25 @@
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+start:
+  description: "Stop the service"
+stop:
+  description: "Stop the service"
+restart:
+  description: "Stop the service"
+reboot:
+  description: "Reboot the machine"
diff --git a/charms/layers/vnfproxy/actions/reboot b/charms/layers/vnfproxy/actions/reboot
new file mode 100755
index 0000000..751c88c
--- /dev/null
+++ b/charms/layers/vnfproxy/actions/reboot
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`set_state` only works here because it's flushed to disk inside the `main()`
+loop. remove_state will need to be called inside the action method.
+"""
+set_state('actions.reboot')
+
+try:
+    main()
+except Exception as e:
+    action_fail(repr(e))
diff --git a/charms/layers/vnfproxy/actions/restart b/charms/layers/vnfproxy/actions/restart
new file mode 100755
index 0000000..f62929b
--- /dev/null
+++ b/charms/layers/vnfproxy/actions/restart
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`set_state` only works here because it's flushed to disk inside the `main()`
+loop. remove_state will need to be called inside the action method.
+"""
+set_state('actions.restart')
+
+try:
+    main()
+except Exception as e:
+    action_fail(repr(e))
diff --git a/charms/layers/vnfproxy/actions/start b/charms/layers/vnfproxy/actions/start
new file mode 100755
index 0000000..a9db3d0
--- /dev/null
+++ b/charms/layers/vnfproxy/actions/start
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`set_state` only works here because it's flushed to disk inside the `main()`
+loop. remove_state will need to be called inside the action method.
+"""
+set_state('actions.start')
+
+try:
+    main()
+except Exception as e:
+    action_fail(repr(e))
diff --git a/charms/layers/vnfproxy/actions/stop b/charms/layers/vnfproxy/actions/stop
new file mode 100755
index 0000000..3890486
--- /dev/null
+++ b/charms/layers/vnfproxy/actions/stop
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`set_state` only works here because it's flushed to disk inside the `main()`
+loop. remove_state will need to be called inside the action method.
+"""
+set_state('actions.stop')
+
+try:
+    main()
+except Exception as e:
+    action_fail(repr(e))
diff --git a/charms/layers/vnfproxy/icon.svg b/charms/layers/vnfproxy/icon.svg
new file mode 100644
index 0000000..e092eef
--- /dev/null
+++ b/charms/layers/vnfproxy/icon.svg
@@ -0,0 +1,279 @@
+<?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+devel r12274"

+   sodipodi:docname="Juju_charm_icon_template.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,-866.25992,731.29077)" />

+    <linearGradient

+       id="Background">

+      <stop

+         id="stop4178"

+         offset="0"

+         style="stop-color:#b8b8b8;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>

+  </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="1920"

+     inkscape:window-height="1029"

+     inkscape:window-x="0"

+     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="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"

+       d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"

+       id="path6455"

+       inkscape:connector-curvature="0"

+       sodipodi:nodetypes="sssssssss" />

+  </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 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 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 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 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 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 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 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z"

+             transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />

+        </g>

+      </g>

+    </g>

+  </g>

+</svg>

diff --git a/charms/layers/vnfproxy/layer.yaml b/charms/layers/vnfproxy/layer.yaml
new file mode 100644
index 0000000..89bf245
--- /dev/null
+++ b/charms/layers/vnfproxy/layer.yaml
@@ -0,0 +1,20 @@
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+includes:
+    - layer:basic
+    - layer:sshproxy
diff --git a/charms/layers/vnfproxy/metadata.yaml b/charms/layers/vnfproxy/metadata.yaml
new file mode 100644
index 0000000..909643d
--- /dev/null
+++ b/charms/layers/vnfproxy/metadata.yaml
@@ -0,0 +1,30 @@
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+name: vnfproxy
+summary: A layer for developing OSM "proxy" charms.
+maintainer: Adam Israel <adam.israel@canonical.com>
+description: |
+  VNF "proxy" charms are a lightweight version of a charm that, rather than
+  installing software on the same machine, execute commands over an ssh channel.
+series:
+  - trusty
+  - xenial
+tags:
+  - osm
+  - vnf
+subordinate: false
diff --git a/charms/layers/vnfproxy/reactive/vnfproxy.py b/charms/layers/vnfproxy/reactive/vnfproxy.py
new file mode 100644
index 0000000..2b26212
--- /dev/null
+++ b/charms/layers/vnfproxy/reactive/vnfproxy.py
@@ -0,0 +1,75 @@
+from charmhelpers.core.hookenv import (
+    action_fail,
+    action_set,
+)
+
+from charms.reactive import (
+    when,
+    remove_state as remove_flag,
+)
+import charms.sshproxy
+
+
+@when('actions.reboot')
+def reboot():
+    err = ''
+    try:
+        result, err = charms.sshproxy._run("reboot")
+    except:
+        action_fail('command failed:' + err)
+    else:
+        action_set({'outout': result})
+    finally:
+        remove_flag('actions.reboot')
+
+
+###############################################################################
+# Below is an example implementation of the start/stop/restart actions.       #
+# To use this, copy the below code into your layer and add the appropriate    #
+# command(s) necessary to perform the action.                                 #
+###############################################################################
+
+# @when('actions.start')
+# def start():
+#     err = ''
+#     try:
+#         cmd = "service myname start"
+#         result, err = charms.sshproxy._run(cmd)
+#     except:
+#         action_fail('command failed:' + err)
+#     else:
+#         action_set({'outout': result})
+#     finally:
+#         remove_flag('actions.start')
+#
+#
+# @when('actions.stop')
+# def stop():
+#     err = ''
+#     try:
+#         # Enter the command to stop your service(s)
+#         cmd = "service myname stop"
+#         result, err = charms.sshproxy._run(cmd)
+#     except:
+#         action_fail('command failed:' + err)
+#     else:
+#         action_set({'outout': result})
+#     finally:
+#         remove_flag('actions.stop')
+#
+#
+# @when('actions.restart')
+# def restart():
+#     err = ''
+#     try:
+#         # Enter the command to restart your service(s)
+#         cmd = "service myname restart"
+#         result, err = charms.sshproxy._run(cmd)
+#     except:
+#         action_fail('command failed:' + err)
+#     else:
+#         action_set({'outout': result})
+#     finally:
+#         remove_flag('actions.restart')
+#
+#
diff --git a/charms/layers/vnfproxy/tests/00-setup b/charms/layers/vnfproxy/tests/00-setup
new file mode 100755
index 0000000..1e5f91b
--- /dev/null
+++ b/charms/layers/vnfproxy/tests/00-setup
@@ -0,0 +1,22 @@
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+#!/bin/bash
+
+sudo add-apt-repository ppa:juju/stable -y
+sudo apt-get update
+sudo apt-get install amulet python-requests -y
diff --git a/charms/layers/vnfproxy/tests/10-deploy b/charms/layers/vnfproxy/tests/10-deploy
new file mode 100755
index 0000000..5bb9044
--- /dev/null
+++ b/charms/layers/vnfproxy/tests/10-deploy
@@ -0,0 +1,52 @@
+##
+# Copyright 2016 Canonical Ltd.
+# All rights reserved.
+#
+# 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.
+##
+
+#!/usr/bin/python3
+
+import amulet
+import requests
+import unittest
+
+
+class TestCharm(unittest.TestCase):
+    def setUp(self):
+        self.d = amulet.Deployment()
+
+        self.d.add('vnfproxy')
+        self.d.expose('vnfproxy')
+
+        self.d.setup(timeout=900)
+        self.d.sentry.wait()
+
+        self.unit = self.d.sentry['vnfproxy'][0]
+
+    def test_service(self):
+        # test we can access over http
+        page = requests.get('http://{}'.format(self.unit.info['public-address']))
+        self.assertEqual(page.status_code, 200)
+        # Now you can use self.d.sentry[SERVICE][UNIT] to address each of the units and perform
+        # more in-depth steps. Each self.d.sentry[SERVICE][UNIT] has the following methods:
+        # - .info - An array of the information of that unit from Juju
+        # - .file(PATH) - Get the details of a file on that unit
+        # - .file_contents(PATH) - Get plain text output of PATH file from that unit
+        # - .directory(PATH) - Get details of directory
+        # - .directory_contents(PATH) - List files and folders in PATH on that unit
+        # - .relation(relation, service:rel) - Get relation data from return service
+
+
+if __name__ == '__main__':
+    unittest.main()