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/juju/client/facade.py b/modules/libjuju/juju/client/facade.py
index 1c7baa0..9e2aabf 100644
--- a/modules/libjuju/juju/client/facade.py
+++ b/modules/libjuju/juju/client/facade.py
@@ -171,13 +171,13 @@
 
 
 def strcast(kind, keep_builtins=False):
-    if issubclass(kind, typing.GenericMeta):
-        return str(kind)[1:]
-    if str(kind).startswith('~'):
-        return str(kind)[1:]
     if (kind in basic_types or
             type(kind) in basic_types) and keep_builtins is False:
         return kind.__name__
+    if str(kind).startswith('~'):
+        return str(kind)[1:]
+    if issubclass(kind, typing.GenericMeta):
+        return str(kind)[1:]
     return kind
 
 
@@ -291,6 +291,13 @@
                     source.append("{}self.{} = {}".format(INDENT * 2,
                                                           arg_name,
                                                           arg_name))
+                elif type(arg_type) is typing.TypeVar:
+                    source.append("{}self.{} = {}.from_json({}) "
+                                  "if {} else None".format(INDENT * 2,
+                                                           arg_name,
+                                                           arg_type_name,
+                                                           arg_name,
+                                                           arg_name))
                 elif issubclass(arg_type, typing.Sequence):
                     value_type = (
                         arg_type_name.__parameters__[0]
@@ -326,13 +333,6 @@
                         source.append("{}self.{} = {}".format(INDENT * 2,
                                                               arg_name,
                                                               arg_name))
-                elif type(arg_type) is typing.TypeVar:
-                    source.append("{}self.{} = {}.from_json({}) "
-                                  "if {} else None".format(INDENT * 2,
-                                                           arg_name,
-                                                           arg_type_name,
-                                                           arg_name,
-                                                           arg_name))
                 else:
                     source.append("{}self.{} = {}".format(INDENT * 2,
                                                           arg_name,
@@ -434,7 +434,7 @@
     return decorator
 
 
-def makeFunc(cls, name, params, result, async=True):
+def makeFunc(cls, name, params, result, _async=True):
     INDENT = "    "
     args = Args(params)
     assignments = []
@@ -448,7 +448,7 @@
     source = """
 
 @ReturnMapping({rettype})
-{async}def {name}(self{argsep}{args}):
+{_async}def {name}(self{argsep}{args}):
     '''
 {docstring}
     Returns -> {res}
@@ -460,12 +460,12 @@
                version={cls.version},
                params=_params)
 {assignments}
-    reply = {await}self.rpc(msg)
+    reply = {_await}self.rpc(msg)
     return reply
 
 """
 
-    fsource = source.format(async="async " if async else "",
+    fsource = source.format(_async="async " if _async else "",
                             name=name,
                             argsep=", " if args else "",
                             args=args,
@@ -474,7 +474,7 @@
                             docstring=textwrap.indent(args.get_doc(), INDENT),
                             cls=cls,
                             assignments=assignments,
-                            await="await " if async else "")
+                            _await="await " if _async else "")
     ns = _getns()
     exec(fsource, ns)
     func = ns[name]