Improved Primitive support and better testing

This changeset addresses several issues.

- Improve primitive support so the status and output of an executed
primitive can be retrieved
- Merge latest upstream libjuju (required for new primive features)
- New testing framework
    This is the start of a new testing framework with the ability to
create and configure LXD containers with SSH, to use while testing proxy
charms.
- Add support for using ssh keys with proxy charms
    See Feature 1429. This uses the per-proxy charm/unit ssh keypair

Signed-off-by: Adam Israel <adam.israel@canonical.com>
diff --git a/modules/libjuju/examples/action.py b/modules/libjuju/examples/action.py
index 4a3cc6d..f839f11 100644
--- a/modules/libjuju/examples/action.py
+++ b/modules/libjuju/examples/action.py
@@ -7,7 +7,6 @@
 4. Waits for the action results to come back, then exits.
 
 """
-import asyncio
 import logging
 
 from juju import loop
@@ -27,8 +26,8 @@
 
 async def main():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
-    await model.reset(force=True)
 
     app = await model.deploy(
         'git',
diff --git a/modules/libjuju/examples/add_model.py b/modules/libjuju/examples/add_model.py
index 0e96fa1..88766f1 100644
--- a/modules/libjuju/examples/add_model.py
+++ b/modules/libjuju/examples/add_model.py
@@ -19,6 +19,7 @@
 async def main():
     controller = Controller()
     print("Connecting to controller")
+    # connect to current controller with current user, per Juju CLI
     await controller.connect()
 
     try:
diff --git a/modules/libjuju/examples/config.py b/modules/libjuju/examples/config.py
index bad5b6d..c7580f6 100644
--- a/modules/libjuju/examples/config.py
+++ b/modules/libjuju/examples/config.py
@@ -6,7 +6,6 @@
 3. Deploys a charm and prints its config and constraints
 
 """
-import asyncio
 import logging
 
 from juju.model import Model
@@ -19,8 +18,8 @@
 
 async def main():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
-    await model.reset(force=True)
 
     ubuntu_app = await model.deploy(
         'mysql',
@@ -47,7 +46,7 @@
 
     await model.disconnect()
 
-    
+
 if __name__ == '__main__':
     logging.basicConfig(level=logging.DEBUG)
     ws_logger = logging.getLogger('websockets.protocol')
diff --git a/modules/libjuju/examples/connect_current_model.py b/modules/libjuju/examples/connect_current_model.py
new file mode 100644
index 0000000..b46a09c
--- /dev/null
+++ b/modules/libjuju/examples/connect_current_model.py
@@ -0,0 +1,27 @@
+"""
+This is a very basic example that connects to the currently selected model
+and prints the number of applications deployed to it.
+"""
+import logging
+
+from juju import loop
+from juju.model import Model
+
+log = logging.getLogger(__name__)
+
+
+async def main():
+    model = Model()
+    try:
+        # connect to the current model with the current user, per the Juju CLI
+        await model.connect()
+        print('There are {} applications'.format(len(model.applications)))
+    finally:
+        if model.is_connected():
+            print('Disconnecting from model')
+            await model.disconnect()
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    loop.run(main())
diff --git a/modules/libjuju/examples/controller.py b/modules/libjuju/examples/controller.py
index 3f029ab..b61a6f6 100644
--- a/modules/libjuju/examples/controller.py
+++ b/modules/libjuju/examples/controller.py
@@ -8,7 +8,6 @@
 5. Destroys the model
 
 """
-import asyncio
 import logging
 
 from juju.controller import Controller
@@ -17,6 +16,7 @@
 
 async def main():
     controller = Controller()
+    # connect to current controller with current user, per Juju CLI
     await controller.connect()
     model = await controller.add_model(
         'my-test-model',
diff --git a/modules/libjuju/examples/credential.py b/modules/libjuju/examples/credential.py
new file mode 100644
index 0000000..e653536
--- /dev/null
+++ b/modules/libjuju/examples/credential.py
@@ -0,0 +1,47 @@
+import sys
+from juju import loop
+from juju.controller import Controller
+
+
+async def main(cloud_name, credential_name):
+    controller = Controller()
+    model = None
+    print('Connecting to controller')
+    # connect to current controller with current user, per Juju CLI
+    await controller.connect()
+    try:
+        print('Adding model')
+        model = await controller.add_model(
+            'test',
+            cloud_name=cloud_name,
+            credential_name=credential_name)
+
+        # verify credential
+        print("Verify model's credential: {}".format(
+            model.info.cloud_credential_tag))
+
+        # verify we can deploy
+        print('Deploying ubuntu')
+        app = await model.deploy('ubuntu-10')
+
+        print('Waiting for active')
+        await model.block_until(
+            lambda: app.units and all(unit.workload_status == 'active'
+                                      for unit in app.units))
+
+        print('Removing ubuntu')
+        await app.remove()
+    finally:
+        print('Cleaning up')
+        if model:
+            print('Removing model')
+            model_uuid = model.info.uuid
+            await model.disconnect()
+            await controller.destroy_model(model_uuid)
+        print('Disconnecting')
+        await controller.disconnect()
+
+
+if __name__ == '__main__':
+    assert len(sys.argv) > 2, 'Please provide a cloud and credential name'
+    loop.run(main(sys.argv[1], sys.argv[2]))
diff --git a/modules/libjuju/examples/deploy.py b/modules/libjuju/examples/deploy.py
index b14e4ca..43764d7 100644
--- a/modules/libjuju/examples/deploy.py
+++ b/modules/libjuju/examples/deploy.py
@@ -13,6 +13,7 @@
 async def main():
     model = Model()
     print('Connecting to model')
+    # connect to current model with current user, per Juju CLI
     await model.connect()
 
     try:
diff --git a/modules/libjuju/examples/future.py b/modules/libjuju/examples/future.py
index c93981a..5e974cf 100644
--- a/modules/libjuju/examples/future.py
+++ b/modules/libjuju/examples/future.py
@@ -2,7 +2,6 @@
 This example doesn't work - it demonstrates features that don't exist yet.
 
 """
-import asyncio
 import logging
 
 from juju.model import Model
@@ -11,8 +10,8 @@
 
 async def main():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
-    await model.reset(force=True)
 
     goal_state = Model.from_yaml('bundle-like-thing')
     ubuntu_app = await model.deploy(
diff --git a/modules/libjuju/examples/livemodel.py b/modules/libjuju/examples/livemodel.py
index a15e9f7..1b10ac9 100644
--- a/modules/libjuju/examples/livemodel.py
+++ b/modules/libjuju/examples/livemodel.py
@@ -6,8 +6,6 @@
 3. Runs forever (kill with Ctrl-C)
 
 """
-import asyncio
-
 from juju.model import Model
 from juju import loop
 
@@ -21,6 +19,7 @@
 
 async def watch_model():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
 
     model.add_observer(on_model_change)
diff --git a/modules/libjuju/examples/relate.py b/modules/libjuju/examples/relate.py
index c0ce4c6..347e021 100644
--- a/modules/libjuju/examples/relate.py
+++ b/modules/libjuju/examples/relate.py
@@ -40,6 +40,7 @@
 
 async def main():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
 
     try:
diff --git a/modules/libjuju/examples/unitrun.py b/modules/libjuju/examples/unitrun.py
index b6e2240..805f0ae 100644
--- a/modules/libjuju/examples/unitrun.py
+++ b/modules/libjuju/examples/unitrun.py
@@ -7,7 +7,6 @@
 4. Waits for the action results to come back, then exits.
 
 """
-import asyncio
 import logging
 
 from juju.model import Model
@@ -24,8 +23,8 @@
 
 async def main():
     model = Model()
+    # connect to current model with current user, per Juju CLI
     await model.connect()
-    await model.reset(force=True)
 
     app = await model.deploy(
         'ubuntu-0',