diff --git a/Makefile b/Makefile
index ba368a16..ebdf0b04 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ DPKG               ?= $(shell which dpkg 2> /dev/null)
 MAKE               ?= make
 PEP8_LINT          ?= pep8
 PEP8_LINT_ARGS     ?= --max-line-length=132
-PYTHON             ?= python
+PYTHON             ?= python3
 PYTHON_VERSION     ?= $(shell $(PYTHON) -c "import sys; sys.stdout.write(sys.version[:3])")
 SUDO               ?= sudo
 
@@ -31,9 +31,11 @@ SUDO               ?= sudo
 
 DPKG_PREREQUISITES := \
     bridge-utils      \
-    python-lockfile   \
-    python-psutil     \
-    python-setuptools \
+    net-tools         \
+    python3-lockfile   \
+    python3-pip \
+    python3-psutil     \
+    python3-setuptools \
     $(NULL)
 
 # check-dpkg-prequisite <package>
@@ -146,11 +148,13 @@ endif
 	@echo "Remember to update PYTHONPATH for every environment that will use this package, thus run"
 	@echo "export PYTHONPATH=$$PYTHONPATH:"$(HAPPY_PATH)/lib/python$(PYTHON_VERSION)/site-packages/""
 	@echo
+	$(MAKE) link
 
 uninstall-path:
 ifeq ($(HAPPY_PATH),)
 	@echo Variable HAPPY_PATH not set. && false
 endif
+	$(MAKE) unlink
 	rm -rf $(HAPPY_PATH)
 
 distribution-build: clean
diff --git a/bin/Makefile b/bin/Makefile
index a2b2d341..6f0f803e 100644
--- a/bin/Makefile
+++ b/bin/Makefile
@@ -15,12 +15,22 @@
 #    limitations under the License.
 #
 
-DEST=/usr/local/bin
-SUDO=sudo
+
+ifeq ($(HAPPY_PATH),)
+	DESTROOT=/usr/local
+	SUDO?=sudo
+else
+	DESTROOT=$(HAPPY_PATH)
+	SUDO?=
+endif
+
+DEST=$(DESTROOT)/bin
+
 PEP8_LINT ?= pep8
 PEP8_LINT_ARGS ?= --max-line-length=132
 
 link:
+	mkdir -p $(DEST)
 	for f in happy-*.py; do \
 		$(SUDO) cp $$f $(DEST)/"$${f%.*}"; \
 		$(SUDO) chmod o+rx $(DEST)/"$${f%.*}"; \
diff --git a/bin/happy-configuration.py b/bin/happy-configuration.py
index d69d3732..3c302179 100755
--- a/bin/happy-configuration.py
+++ b/bin/happy-configuration.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyConfiguration class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "quiet", "config-type=", "delete=", "key=", "value="])
 
     except getopt.GetoptError as err:
-        print happy.HappyConfiguration.HappyConfiguration.__doc__
-        print hred(str(err))
+        print(happy.HappyConfiguration.HappyConfiguration.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyConfiguration.HappyConfiguration.__doc__
+            print(happy.HappyConfiguration.HappyConfiguration.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-dns.py b/bin/happy-dns.py
index 3fc40414..2fdef1f0 100755
--- a/bin/happy-dns.py
+++ b/bin/happy-dns.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyDNS class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "add", "delete"])
 
     except getopt.GetoptError as err:
-        print happy.HappyDNS.HappyDNS.__doc__
-        print hred(str(err))
+        print(happy.HappyDNS.HappyDNS.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyDNS.HappyDNS.__doc__
+            print(happy.HappyDNS.HappyDNS.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-internet.py b/bin/happy-internet.py
index 5db81d09..d29cebf3 100755
--- a/bin/happy-internet.py
+++ b/bin/happy-internet.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyInternet class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "node=", "quiet", "add", "delete", "interface=", "isp=", "seed="])
 
     except getopt.GetoptError as err:
-        print happy.HappyInternet.HappyInternet.__doc__
-        print hred(str(err))
+        print(happy.HappyInternet.HappyInternet.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyInternet.HappyInternet.__doc__
+            print(happy.HappyInternet.HappyInternet.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-link-add.py b/bin/happy-link-add.py
index e3a1fd3d..0968f766 100755
--- a/bin/happy-link-add.py
+++ b/bin/happy-link-add.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyLinkAdd class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "type=", "quiet", "tap"])
 
     except getopt.GetoptError as err:
-        print happy.HappyLinkAdd.HappyLinkAdd.__doc__
-        print hred(str(err))
+        print(happy.HappyLinkAdd.HappyLinkAdd.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyLinkAdd.HappyLinkAdd.__doc__
+            print(happy.HappyLinkAdd.HappyLinkAdd.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-link-delete.py b/bin/happy-link-delete.py
index 01b586ef..e5677c84 100755
--- a/bin/happy-link-delete.py
+++ b/bin/happy-link-delete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyLinkDelete class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyLinkDelete.HappyLinkDelete.__doc__
-        print hred(str(err))
+        print(happy.HappyLinkDelete.HappyLinkDelete.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyLinkDelete.HappyLinkDelete.__doc__
+            print(happy.HappyLinkDelete.HappyLinkDelete.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-link-list.py b/bin/happy-link-list.py
index 620ff38e..03d1e347 100755
--- a/bin/happy-link-list.py
+++ b/bin/happy-link-list.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyLinkList class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyLinkList.HappyLinkList.__doc__
-        print hred(str(err))
+        print(happy.HappyLinkList.HappyLinkList.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyLinkList.HappyLinkList.__doc__
+            print(happy.HappyLinkList.HappyLinkList.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-add.py b/bin/happy-network-add.py
index 315cf13f..def5149b 100755
--- a/bin/happy-network-add.py
+++ b/bin/happy-network-add.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkAdd class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "type="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkAdd.HappyNetworkAdd.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkAdd.HappyNetworkAdd.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkAdd.HappyNetworkAdd.__doc__
+            print(happy.HappyNetworkAdd.HappyNetworkAdd.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-address.py b/bin/happy-network-address.py
index f3a310d0..6081e5bf 100755
--- a/bin/happy-network-address.py
+++ b/bin/happy-network-address.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkAddress class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "add", "delete"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkAddress.HappyNetworkAddress.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkAddress.HappyNetworkAddress.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkAddress.HappyNetworkAddress.__doc__
+            print(happy.HappyNetworkAddress.HappyNetworkAddress.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-delete.py b/bin/happy-network-delete.py
index 3c73445c..235ab96c 100755
--- a/bin/happy-network-delete.py
+++ b/bin/happy-network-delete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkDelete class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkDelete.HappyNetworkDelete.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkDelete.HappyNetworkDelete.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkDelete.HappyNetworkDelete.__doc__
+            print(happy.HappyNetworkDelete.HappyNetworkDelete.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-list.py b/bin/happy-network-list.py
index 77defd4b..1942eab6 100755
--- a/bin/happy-network-list.py
+++ b/bin/happy-network-list.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkList class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkList.HappyNetworkList.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkList.HappyNetworkList.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkList.HappyNetworkList.__doc__
+            print(happy.HappyNetworkList.HappyNetworkList.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-route.py b/bin/happy-network-route.py
index e5025d34..077d98d3 100755
--- a/bin/happy-network-route.py
+++ b/bin/happy-network-route.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkRoute class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "add", "delete", "to=", "via=", "prefix=", "isp=", "seed="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkRoute.HappyNetworkRoute.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkRoute.HappyNetworkRoute.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkRoute.HappyNetworkRoute.__doc__
+            print(happy.HappyNetworkRoute.HappyNetworkRoute.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-state.py b/bin/happy-network-state.py
index ebf82025..7b091499 100755
--- a/bin/happy-network-state.py
+++ b/bin/happy-network-state.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkState class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "up", "down"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkState.HappyNetworkState.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkState.HappyNetworkState.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkState.HappyNetworkState.__doc__
+            print(happy.HappyNetworkState.HappyNetworkState.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-network-status.py b/bin/happy-network-status.py
index 3a036c8a..a362c4e5 100755
--- a/bin/happy-network-status.py
+++ b/bin/happy-network-status.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNetworkStatus class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNetworkStatus.HappyNetworkStatus.__doc__
-        print hred(str(err))
+        print(happy.HappyNetworkStatus.HappyNetworkStatus.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNetworkStatus.HappyNetworkStatus.__doc__
+            print(happy.HappyNetworkStatus.HappyNetworkStatus.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-add.py b/bin/happy-node-add.py
index 3550b71f..c923a58a 100755
--- a/bin/happy-node-add.py
+++ b/bin/happy-node-add.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeAdd class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "ap", "service", "local"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeAdd.HappyNodeAdd.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeAdd.HappyNodeAdd.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeAdd.HappyNodeAdd.__doc__
+            print(happy.HappyNodeAdd.HappyNodeAdd.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-address.py b/bin/happy-node-address.py
index 07c52804..b0c44c42 100755
--- a/bin/happy-node-address.py
+++ b/bin/happy-node-address.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeAddress class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "interface=", "add", "delete"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeAddress.HappyNodeAddress.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeAddress.HappyNodeAddress.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeAddress.HappyNodeAddress.__doc__
+            print(happy.HappyNodeAddress.HappyNodeAddress.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-delete.py b/bin/happy-node-delete.py
index 0ca62c30..9946eef5 100755
--- a/bin/happy-node-delete.py
+++ b/bin/happy-node-delete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeDelete class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeDelete.HappyNodeDelete.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeDelete.HappyNodeDelete.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeDelete.HappyNodeDelete.__doc__
+            print(happy.HappyNodeDelete.HappyNodeDelete.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-edit.py b/bin/happy-node-edit.py
new file mode 100755
index 00000000..ebe28e70
--- /dev/null
+++ b/bin/happy-node-edit.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+
+#
+#    Copyright (c) 2021 Google LLC.
+#    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.
+#
+
+##
+#    @file
+#       A Happy command line utility to modify an existing virtual node.
+#
+#       The command is executed by instantiating and running HappyNodeEdit class.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import sys
+
+import happy.HappyNodeEdit
+from happy.Utils import *
+
+if __name__ == "__main__":
+    options = happy.HappyNodeEdit.option()
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hi:n:qasl",
+                                   ["help", "id=", "new=", "quiet"])
+
+    except getopt.GetoptError as err:
+        print(happy.HappyNodeEdit.HappyNodeEdit.__doc__)
+        print(hred(str(err)))
+        sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
+
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            print(happy.HappyNodeEdit.HappyNodeEdit.__doc__)
+            sys.exit(0)
+
+        elif o in ("-q", "--quiet"):
+            options["quiet"] = True
+
+        elif o in ("-i", "--id"):
+            options["node_id"] = a
+
+        elif o in ("-n", "--new"):
+            options["new_node_id"] = a
+
+        else:
+            assert False, "unhandled option"
+
+    cmd = happy.HappyNodeEdit.HappyNodeEdit(options)
+    cmd.start()
diff --git a/bin/happy-node-join.py b/bin/happy-node-join.py
index e982ed72..174341ff 100755
--- a/bin/happy-node-join.py
+++ b/bin/happy-node-join.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeJoin class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "network=", "mac=", "tap", "customizedeui64="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeJoin.HappyNodeJoin.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeJoin.HappyNodeJoin.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeJoin.HappyNodeJoin.__doc__
+            print(happy.HappyNodeJoin.HappyNodeJoin.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-leave.py b/bin/happy-node-leave.py
index c4890743..c2a8ea25 100755
--- a/bin/happy-node-leave.py
+++ b/bin/happy-node-leave.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeLeave class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "network="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeLeave.HappyNodeLeave.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeLeave.HappyNodeLeave.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeLeave.HappyNodeLeave.__doc__
+            print(happy.HappyNodeLeave.HappyNodeLeave.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-list.py b/bin/happy-node-list.py
index 4eeefa03..deb0326d 100755
--- a/bin/happy-node-list.py
+++ b/bin/happy-node-list.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeList class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeList.HappyNodeList.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeList.HappyNodeList.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeList.HappyNodeList.__doc__
+            print(happy.HappyNodeList.HappyNodeList.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-route.py b/bin/happy-node-route.py
index 0a021b37..dc4d4930 100755
--- a/bin/happy-node-route.py
+++ b/bin/happy-node-route.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeRoute class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -34,17 +36,17 @@
     options = happy.HappyNodeRoute.option()
 
     try:
-        opts, args = getopt.getopt(sys.argv[1:], "hi:qadt:v:p:s:e:",
-                                   ["help", "id=", "quiet", "add", "delete", "to=", "via=", "prefix=", "isp=", "seed="])
+        opts, args = getopt.getopt(sys.argv[1:], "hi:qadt:v:p:s:e:y:",
+                                   ["help", "id=", "quiet", "add", "delete", "to=", "via=", "prefix=", "isp=", "seed=", "type="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeRoute.HappyNodeRoute.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeRoute.HappyNodeRoute.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeRoute.HappyNodeRoute.__doc__
+            print(happy.HappyNodeRoute.HappyNodeRoute.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
@@ -74,6 +76,9 @@
         elif o in ("-e", "--seed"):
             options["seed"] = True
 
+        elif o in ("-y", "--type"):
+            options["route_type"] = a
+
         else:
             assert False, "unhandled option"
 
diff --git a/bin/happy-node-status.py b/bin/happy-node-status.py
index b3549ffb..bf12648d 100755
--- a/bin/happy-node-status.py
+++ b/bin/happy-node-status.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeAdd class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeStatus.HappyNodeStatus.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeStatus.HappyNodeStatus.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeStatus.HappyNodeStatus.__doc__
+            print(happy.HappyNodeStatus.HappyNodeStatus.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-tcp-reset.py b/bin/happy-node-tcp-reset.py
index 62f5206b..df845627 100755
--- a/bin/happy-node-tcp-reset.py
+++ b/bin/happy-node-tcp-reset.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeTcpReset class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "interface=", "isp=", "dstPort=", "start=", "duration="])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeTcpReset.HappyNodeTcpReset.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeTcpReset.HappyNodeTcpReset.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeTcpReset.HappyNodeTcpReset.__doc__
+            print(happy.HappyNodeTcpReset.HappyNodeTcpReset.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-node-tmux.py b/bin/happy-node-tmux.py
index 56a61adc..be43410e 100755
--- a/bin/happy-node-tmux.py
+++ b/bin/happy-node-tmux.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyNodeTmux class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "user=", "session=", "delete", "attach", "noattach"])
 
     except getopt.GetoptError as err:
-        print happy.HappyNodeTmux.HappyNodeTmux.__doc__
-        print hred(str(err))
+        print(happy.HappyNodeTmux.HappyNodeTmux.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyNodeTmux.HappyNodeTmux.__doc__
+            print(happy.HappyNodeTmux.HappyNodeTmux.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-ping.py b/bin/happy-ping.py
index 23b02026..c3d7cb5c 100755
--- a/bin/happy-ping.py
+++ b/bin/happy-ping.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running Ping class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -39,13 +41,13 @@
                                    ["help", "id=", "quiet", "destination=", "size=", "count="])
 
     except getopt.GetoptError as err:
-        print happy.Ping.Ping.__doc__
-        print hred(str(err))
+        print(happy.Ping.Ping.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.Ping.Ping.__doc__
+            print(happy.Ping.Ping.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-process-output.py b/bin/happy-process-output.py
index 5d6982cd..4bb89fc0 100755
--- a/bin/happy-process-output.py
+++ b/bin/happy-process-output.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyProcessOutput class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "tag="])
 
     except getopt.GetoptError as err:
-        print happy.HappyProcessOutput.HappyProcessOutput.__doc__
-        print hred(str(err))
+        print(happy.HappyProcessOutput.HappyProcessOutput.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyProcessOutput.HappyProcessOutput.__doc__
+            print(happy.HappyProcessOutput.HappyProcessOutput.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-process-start.py b/bin/happy-process-start.py
index 7d32917d..25fabb18 100755
--- a/bin/happy-process-start.py
+++ b/bin/happy-process-start.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyProcessStart class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "tag=", "strace", "env="])
 
     except getopt.GetoptError as err:
-        print happy.HappyProcessStart.HappyProcessStart.__doc__
-        print hred(str(err))
+        print(happy.HappyProcessStart.HappyProcessStart.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyProcessStart.HappyProcessStart.__doc__
+            print(happy.HappyProcessStart.HappyProcessStart.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-process-stop.py b/bin/happy-process-stop.py
index f9162baf..34e57ae2 100755
--- a/bin/happy-process-stop.py
+++ b/bin/happy-process-stop.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyProcessStop class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "tag="])
 
     except getopt.GetoptError as err:
-        print happy.HappyProcessStop.HappyProcessStop.__doc__
-        print hred(str(err))
+        print(happy.HappyProcessStop.HappyProcessStop.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyProcessStop.HappyProcessStop.__doc__
+            print(happy.HappyProcessStop.HappyProcessStop.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-process-strace.py b/bin/happy-process-strace.py
index 2416582d..303e6477 100755
--- a/bin/happy-process-strace.py
+++ b/bin/happy-process-strace.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyProcessStrace class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "tag="])
 
     except getopt.GetoptError as err:
-        print happy.HappyProcessStrace.HappyProcessStrace.__doc__
-        print hred(str(err))
+        print(happy.HappyProcessStrace.HappyProcessStrace.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyProcessStrace.HappyProcessStrace.__doc__
+            print(happy.HappyProcessStrace.HappyProcessStrace.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-process-wait.py b/bin/happy-process-wait.py
index deb2b9f8..861f89df 100755
--- a/bin/happy-process-wait.py
+++ b/bin/happy-process-wait.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyProcessWait class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "tag="])
 
     except getopt.GetoptError as err:
-        print happy.HappyProcessWait.HappyProcessWait.__doc__
-        print hred(str(err))
+        print(happy.HappyProcessWait.HappyProcessWait.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyProcessWait.HappyProcessWait.__doc__
+            print(happy.HappyProcessWait.HappyProcessWait.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-shell.py b/bin/happy-shell.py
index bcf46369..2aceb538 100755
--- a/bin/happy-shell.py
+++ b/bin/happy-shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyShell class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "id=", "quiet", "user", "command="])
 
     except getopt.GetoptError as err:
-        print happy.HappyShell.HappyShell.__doc__
-        print hred(str(err))
+        print(happy.HappyShell.HappyShell.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyShell.HappyShell.__doc__
+            print(happy.HappyShell.HappyShell.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-state-delete.py b/bin/happy-state-delete.py
index 121cf1f5..b011ad19 100755
--- a/bin/happy-state-delete.py
+++ b/bin/happy-state-delete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyStatus class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "quiet", "force", "all"])
 
     except getopt.GetoptError as err:
-        print happy.HappyStateDelete.HappyStateDelete.__doc__
-        print hred(str(err))
+        print(happy.HappyStateDelete.HappyStateDelete.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyStateDelete.HappyStateDelete.__doc__
+            print(happy.HappyStateDelete.HappyStateDelete.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-state-load.py b/bin/happy-state-load.py
index 44891a2b..2c096b89 100755
--- a/bin/happy-state-load.py
+++ b/bin/happy-state-load.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyStateLoad class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "file=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyStateLoad.HappyStateLoad.__doc__
-        print hred(str(err))
+        print(happy.HappyStateLoad.HappyStateLoad.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyStateLoad.HappyStateLoad.__doc__
+            print(happy.HappyStateLoad.HappyStateLoad.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-state-unload.py b/bin/happy-state-unload.py
index 6b7b29ed..37656041 100755
--- a/bin/happy-state-unload.py
+++ b/bin/happy-state-unload.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyStateUnload class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -38,13 +40,13 @@
                                    ["help", "file=", "quiet"])
 
     except getopt.GetoptError as err:
-        print happy.HappyStateUnload.HappyStateUnload.__doc__
-        print hred(str(err))
+        print(happy.HappyStateUnload.HappyStateUnload.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyStateUnload.HappyStateUnload.__doc__
+            print(happy.HappyStateUnload.HappyStateUnload.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/bin/happy-state.py b/bin/happy-state.py
index 42b64887..d664f60a 100755
--- a/bin/happy-state.py
+++ b/bin/happy-state.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running HappyStatus class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -34,17 +36,17 @@
     options = happy.HappyState.option()
 
     try:
-        opts, args = getopt.getopt(sys.argv[1:], "hqs:gljuia",
-                                   ["help", "quiet", "save=", "graph", "log", "json", "unlock", "id", "all"])
+        opts, args = getopt.getopt(sys.argv[1:], "hqs:gljuian:e:",
+                                   ["help", "quiet", "save=", "graph", "log", "json", "unlock", "id", "all", "node=", "extension="])
 
     except getopt.GetoptError as err:
-        print happy.HappyState.HappyState.__doc__
-        print hred(str(err))
+        print(happy.HappyState.HappyState.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.HappyState.HappyState.__doc__
+            print(happy.HappyState.HappyState.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
@@ -71,8 +73,17 @@
         elif o in ("-a", "--all"):
             options["all"] = True
 
+        elif o in ("-n", "--node"):
+            options["node"] = a
+
+        elif o in ("-e", "--extension"):
+            options["extension"] = a
+
         else:
             assert False, "unhandled option"
 
+    if len(args) == 1:
+        options["node"] = args[0]
+
     cmd = happy.HappyState.HappyState(options)
     cmd.start()
diff --git a/bin/happy-traceroute.py b/bin/happy-traceroute.py
index cca119e8..2334fbbf 100755
--- a/bin/happy-traceroute.py
+++ b/bin/happy-traceroute.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       The command is executed by instantiating and running Traceroute class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import sys
 
@@ -39,13 +41,13 @@
                                    ["help", "id=", "quiet", "destination="])
 
     except getopt.GetoptError as err:
-        print happy.Traceroute.Traceroute.__doc__
-        print hred(str(err))
+        print(happy.Traceroute.Traceroute.__doc__)
+        print(hred(str(err)))
         sys.exit(hred("%s: Failed to parse arguments." % (__file__)))
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print happy.Traceroute.Traceroute.__doc__
+            print(happy.Traceroute.Traceroute.__doc__)
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
diff --git a/doc/logging.md b/doc/logging.md
new file mode 100644
index 00000000..7b9adab8
--- /dev/null
+++ b/doc/logging.md
@@ -0,0 +1,19 @@
+# How does Happy test log work
+
+## Types of Happy logs
+
+  * Debug logs - Logs related to Happy's role as test coordinator and orchestrator, where it facilitates the activities of virtual device nodes.
+
+  * Device logs - Logs related to software such as OpenWeave running on individual virtual device nodes within a Happy test network. OpenWeave is one of the software example, device logs may have OpenWeave logs.
+
+## Where Happy logs are stored
+
+  * If the `HAPPY_LOG_DIR` environment variable exists, Happy uses the `HAPPY_LOG_DIR` value as log folder location to store Happy test logs.
+
+  * If the `HAPPY_LOG_DIR` environment variable doesn't exist, Happy uses the `default_happy_log_dir` value from [happy/conf/main_config.json](https://github.com/openweave/happy/blob/master/happy/conf/main_config.json) as the default log folder location to store Happy test logs. The default value of `default_happy_log_dir` is `/tmp`.
+
+## How to set `HAPPY_LOG_DIR`
+
+  There are multiple ways to see the `HAPPY_LOG_DIR` environment variable::
+  * If your project is being built with GNU autotools and you are running functional and unit tests with Happy with the DejaGnu test framework, then you might add `HAPPY_LOG_DIR=<dir value>` to TESTS_ENVIRONMENT in your Makefile.am or Makefile.in.
+  * Add `HAPPY_LOG_DIR=<dir value>` to .bashrc and source it before you run the test script.
diff --git a/happy/Driver.py b/happy/Driver.py
index 190bc538..3072e9b6 100644
--- a/happy/Driver.py
+++ b/happy/Driver.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -23,6 +23,8 @@
 #       necessary to run Happy. Most other classes inherit Driver.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import getopt
 import getpass
 import json
@@ -34,8 +36,9 @@
 import subprocess
 import sys
 import time
-import happy.HappyLogger as HappyLogger
+import warnings
 
+import happy.HappyLogger as HappyLogger
 from happy.utils.IP import IP
 from happy.Utils import *
 
@@ -59,6 +62,7 @@ def __init__(self, name, maxattempts, filename, logger):
         self.filelock = lockfile.FileLock(self.filename)
         self.counter = 0
         self.logger = logger
+        warnings.filterwarnings("ignore", category=ResourceWarning)
 
     def lock(self):
         wait_time = 0.1
@@ -121,6 +125,9 @@ def break_lock(self):
 
 
 class Driver:
+    """
+    Driver init loads configuration base on conf/log_config.json and conf/main_config.json
+    """
     def __init__(self):
         self.state = {}
         self.isp_state = {}
@@ -133,6 +140,9 @@ def __init__(self):
         self.log_conf_file = self.happy_path + "/" + log_config
         self.main_conf_file = self.happy_path + "/" + main_config
 
+        if "HAPPY_MAIN_CONFIG_FILE" in os.environ.keys():
+            self.main_conf_file = os.environ["HAPPY_MAIN_CONFIG_FILE"]
+
         self.__configure()
         self.readConfiguration()
         self.configHappyLogPath()
@@ -169,27 +179,49 @@ def __configure(self):
 
             self.main_conf = json.loads(json_data)
         except Exception:
-            print "Failed to load config from %s." % (self.main_conf_file)
+            print("Failed to load config from %s." % (self.main_conf_file))
             sys.exit(1)
 
         try:
             self.default_state = self.main_conf["default_state"]
         except Exception:
-            print "Failed to find default state name in the main configuration file %s." \
-                % (self.main_conf_file)
+            print("Failed to find default state name in the main configuration file %s." \
+                % (self.main_conf_file))
             sys.exit(1)
 
         try:
             self.state_environ = self.main_conf["state_environ"]
         except Exception:
-            print "Failed to find state environment variable name in the main configuration file %s." \
-                % (self.main_conf_file)
+            print("Failed to find state environment variable name in the main configuration file %s." \
+                % (self.main_conf_file))
             sys.exit(1)
 
         try:
             self.state_id = self.getStateId()
         except Exception:
-            print "Failed to get state id from %s." % (self.main_conf_file)
+            print("Failed to get state id from %s." % (self.main_conf_file))
+            sys.exit(1)
+
+        # default_happy_log_dir will be default dir for happy log
+        # happy_log_environ is os environment variable passed from test-engine
+        try:
+            self.default_happy_log_dir = self.main_conf["default_happy_log_dir"]
+        except Exception:
+            print("Failed to find default_happy_log dir in the main configuration file %s." \
+                % (self.main_conf_file))
+            sys.exit(1)
+
+        try:
+            self.happy_log_environ = "HAPPY_LOG_DIR"
+        except Exception:
+            print("Failed to find happy log environment variable name in the main configuration file %s." \
+                % (self.main_conf_file))
+            sys.exit(1)
+
+        try:
+            self.happy_log_dir = self.getHappyLogDir()
+        except Exception:
+            print("Failed to get happy log dir from %s." % (self.main_conf_file))
             sys.exit(1)
 
         try:
@@ -203,8 +235,8 @@ def __configure(self):
             self.configuration_file = os.path.expanduser(self.main_conf["configuration_file"])
 
         except Exception:
-            print "Failed to parse main configuration for state file names: %s" \
-                % (self.main_conf_file)
+            print("Failed to parse main configuration for state file names: %s" \
+                % (self.main_conf_file))
             sys.exit(1)
 
         try:
@@ -224,14 +256,15 @@ def __logging(self):
 
                 self.log_conf = json.loads(json_data)
         except Exception:
-            print "Failed to load config from %s." % (self.log_conf_file)
+            print("Failed to load config from %s." % (self.log_conf_file))
             sys.exit(1)
 
         try:
             confDict = dict(self.main_conf)
             confDict['state_id'] = self.state_id
-            self.log_conf['handlers']['file']['filename'] = self.log_conf['handlers']['file']['filename'] % confDict
+            confDict['happy_log_dir'] = self.happy_log_dir
 
+            self.log_conf['handlers']['file']['filename'] = self.log_conf['handlers']['file']['filename'] % confDict
             self.log_conf['handlers']['file']['level'] = logging.getLevelName(self.log_level_file)
             self.log_conf['handlers']['stream']['level'] = logging.getLevelName(self.log_level_console)
 
@@ -240,28 +273,28 @@ def __logging(self):
 
             h = HappyLogger.HostnameFilter()
 
-        except ValueError, e:
+        except ValueError as e:
             emsg = "Failed to load logging configuration: "
-            print emsg
-            print e
+            print(emsg)
+            print(e)
             sys.exit(1)
 
-        except TypeError, e:
+        except TypeError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
-        except AttributeError, e:
+        except AttributeError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
-        except ImportError, e:
+        except ImportError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
     def __initConfiguration(self):
@@ -291,15 +324,16 @@ def configHappyLogPath(self):
 
             confDict = dict(self.main_conf)
             confDict['state_id'] = self.state_id
+            confDict['happy_log_dir'] = self.happy_log_dir
 
-            if not os.path.exists(confDict["log_directory"]):
-                os.makedirs(confDict["log_directory"])
+            if not os.path.exists(self.happy_log_dir):
+                os.makedirs(self.happy_log_dir)
 
             self.process_log_prefix = self.main_conf["process_log_prefix"] % confDict
 
         except Exception as e:
-            print "Failed to find process's log prefix in the main configuration file %s. Exception %s" \
-                % (self.main_conf_file, str(e))
+            print("Failed to find process's log prefix in the main configuration file %s. Exception %s" \
+                % (self.main_conf_file, str(e)))
             sys.exit(1)
 
     def writeConfiguration(self, configuration, config_type='user'):
@@ -319,7 +353,7 @@ def writeConfiguration(self, configuration, config_type='user'):
             filename = self.getLogConfigPath()
         else:
             emsg = "Invalid configuration type: %s" % (str(config_type))
-            print emsg
+            print(emsg)
             sys.exit(1)
 
         with open(filename, 'w') as jfile:
@@ -450,21 +484,20 @@ def CallCmd(self, cmd, env=None, quiet=False, output='debuglog'):
 
         if output == 'shell':
             return result, None, None
+        else:
+            out_result = process.stdout.read().decode("utf-8")
+            err_result = process.stderr.read().decode("utf-8")
+            if out_result is not None:
+                for line in out_result.split("\n"):
+                    if len(line) > 0:
+                        self.logger.debug("Happy [%s]:      %s" % (self.state_id, line))
 
-        out_result = process.stdout.read()
-        err_result = process.stderr.read()
-
-        if out_result is not None:
-            for line in out_result.split("\n"):
-                if len(line) > 0:
-                    self.logger.debug("Happy [%s]:      %s" % (self.state_id, line))
-
-        if err_result is not None:
-            for line in err_result.split("\n"):
-                if len(line) > 0:
-                    self.logger.debug("Happy [%s]:      %s" % (self.state_id, line))
+            if err_result is not None:
+                for line in err_result.split("\n"):
+                    if len(line) > 0:
+                        self.logger.debug("Happy [%s]:      %s" % (self.state_id, line))
 
-        return result, out_result, err_result
+            return result, out_result, err_result
 
     def CallAtHost(self, cmd, env=None, quiet=False, output='debuglog'):
         result, out_result, err_result = self.CallCmd(cmd, env, quiet, output)
@@ -517,23 +550,30 @@ def CallAtNodeForOutput(self, node_id, cmd, env=None):
     def CallAtNetworkForOutput(self, network_id, cmd, env=None):
         return self.CallAtNodeForOutput(network_id, cmd, env)
 
-    def runAsRoot(self, cmd):
-        if "SUDO" in os.environ.keys():
-            return os.environ["SUDO"] + " " + cmd
+    def getRunAsRootPrefixList(self):
+        if "SUDO" in list(os.environ.keys()):
+            return [os.environ["SUDO"]]
         elif os.getuid() == 0:
             # We are already root
-            return cmd
+            return []
         else:
-            return "sudo " + cmd
+            return ["sudo"]
 
-    def runAsUser(self, cmd, username=None):
+
+    def runAsRoot(self, cmd):
+        return ' '.join(self.getRunAsRootPrefixList() + [cmd])
+
+    def getRunAsUserPrefixList(self, username=None):
         if username is None:
             username = getpass.getuser()
 
-        if "SUDO" in os.environ.keys():
-            return "$SUDO -u " + username + " " + cmd
+        if "SUDO" in list(os.environ.keys()):
+            return [os.environ["SUDO"], "-u", username]
         else:
-            return "sudo -u " + username + " " + cmd
+            return ["sudo", "-u", username]
+
+    def runAsUser(self, cmd, username=None):
+        return ' '.join(self.getRunAsUserPrefixList(username=username) + [cmd])
 
     def uniquePrefix(self, txt, state=None):
         prefix = self.getStateId(state)
@@ -541,13 +581,13 @@ def uniquePrefix(self, txt, state=None):
 
     def getShortIdToLongIdMap(self, state=None):
         state = self.getState(state)
-        if "identifiers" not in state.keys():
+        if "identifiers" not in list(state.keys()):
             state["identifiers"] = {}
         return state["identifiers"]
 
     def getLongIdToShortIdMap(self, state=None):
         state = self.getState(state)
-        if "netns" not in state.keys():
+        if "netns" not in list(state.keys()):
             state["netns"] = {}
         return state["netns"]
 
@@ -578,11 +618,17 @@ def getShortIdentifier(self, identifier, state=None):
         return longToShortMap[identifier]
 
     def getStateId(self, state=None):
-        if state is not None and len(state.keys()) > 0:
-            state_key = state.keys()[0]
+        if state is not None and len(list(state.keys())) > 0:
+            state_key = list(state.keys())[0]
             return state_key
 
-        if self.state_environ in os.environ.keys():
+        if self.state_environ in list(os.environ.keys()):
             return os.environ[self.state_environ]
         else:
             return self.default_state
+
+    def getHappyLogDir(self):
+        if self.happy_log_environ in list(os.environ.keys()):
+            return os.environ[self.happy_log_environ]
+        else:
+            return self.default_happy_log_dir
diff --git a/happy/HappyConfiguration.py b/happy/HappyConfiguration.py
index a69e4ddf..910c54bc 100644
--- a/happy/HappyConfiguration.py
+++ b/happy/HappyConfiguration.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyConfiguration class that modifies a user's configuration setup.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -76,8 +78,8 @@ def __init__(self, opts=options):
         self.value = opts["value"]
         self.config_type = opts["config-type"]
         if not (self.config_type in ('main', 'log', 'user')):
-            print "Invalid value for the configuration type"
-            print happy.HappyConfiguration.HappyConfiguration.__doc__
+            print("Invalid value for the configuration type")
+            print(happy.HappyConfiguration.HappyConfiguration.__doc__)
             sys.exit(1)
 
     def __pre_check(self):
@@ -87,7 +89,7 @@ def __post_check(self):
         pass
 
     def __print_configuration(self, config_type):
-        print config_type.capitalize() + " Happy Configuration"
+        print(config_type.capitalize() + " Happy Configuration")
         if config_type == 'user':
             d = self.configuration
         elif config_type == 'main':
@@ -96,8 +98,8 @@ def __print_configuration(self, config_type):
             d = self.log_conf
 
         for key in d.keys():
-            print "\t" + key + "\t " + str(d[key])
-        print
+            print("\t" + key + "\t " + str(d[key]))
+        print()
 
     def run(self):
         self.__pre_check()
@@ -114,18 +116,18 @@ def run(self):
             d = self.log_conf
 
         if self.delete:
-            if self.delete in d.keys():
+            if self.delete in list(d.keys()):
                 del d[self.delete]
 
         if self.key is not None and self.value is not None:
             d[self.key] = self.value
 
         if self.key is not None and self.value is None:
-            print "\t" + self.key + "\t",
-            if self.key in d.keys():
-                print d[self.key]
+            print("\t" + self.key + "\t", end=' ')
+            if self.key in list(d.keys()):
+                print(d[self.key])
             else:
-                print "not defined"
+                print("not defined")
 
         self.writeConfiguration(d, self.config_type)
 
diff --git a/happy/HappyDNS.py b/happy/HappyDNS.py
index cd870507..8be1d6c6 100644
--- a/happy/HappyDNS.py
+++ b/happy/HappyDNS.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -23,6 +23,7 @@
 #
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -79,7 +80,7 @@ def __pre_check(self):
             self.add = True
 
         if not self.dns:
-            if "happy_dns" in os.environ.keys():
+            if "happy_dns" in list(os.environ.keys()):
                 self.dns = os.environ['happy_dns'].split()
 
         if self.add and self.dns is None:
diff --git a/happy/HappyHost.py b/happy/HappyHost.py
index 6f853679..6e6c0ab5 100644
--- a/happy/HappyHost.py
+++ b/happy/HappyHost.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Implements HappyHost class that interacts with host OS.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyInternet.py b/happy/HappyInternet.py
index 971b1b4b..a149032c 100644
--- a/happy/HappyInternet.py
+++ b/happy/HappyInternet.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #
 #
 
+from __future__ import absolute_import
 import os
 import sys
 import time
@@ -32,6 +33,7 @@
 from happy.Utils import *
 from happy.HappyNode import HappyNode
 from happy.HappyNodeRoute import HappyNodeRoute
+from six.moves import range
 
 options = {}
 options["quiet"] = False
@@ -47,7 +49,7 @@ def option():
     return options.copy()
 
 
-class HappyInternet(HappyNode, HappyNodeRoute):
+class HappyInternet(HappyNodeRoute):
     """
     Connects a virtual node to the internet through a virtual ISP.
 
@@ -85,14 +87,6 @@ def __init__(self, opts=options):
         self.isp_id = opts["isp"]
         self.seed = int(opts["seed"])
         self.prefix = '172.16.' + opts["seed"] + '.'
-
-        if "weave_service_address" in os.environ.keys():
-            tier = os.environ['weave_service_address'].split(".")[3][0:3]
-            self.isp_id = tier + self.isp_id
-        else:
-            tier = "test"
-            self.isp_id = tier + self.isp_id
-
         self.mask = "24"
         self.host_addr = self.prefix + "1"
         self.isp_internet_id = self.isp_id + "1"
@@ -174,7 +168,7 @@ def __get_internet_interface_info(self):
 
     def __initialize_isp_pool(self):
         # initialize isp pool
-        isp_seed = range(1, 256)
+        isp_seed = list(range(1, 256))
         self.isp_pool = [{"isp_addr": self.prefix + str(i),
                           "isp_index": str(i),
                           "occupy": False,
@@ -324,6 +318,8 @@ def __internet_state(self):
             isp_dic["isp_addr"] = self.isp_addr
             isp_dic["node_id"] = self.node_id
             isp_dic["isp_index"] = self.isp_index
+            isp_dic["isp"] = self.isp_id
+            isp_dic["iface"] = self.iface
             internet[self.isp_id] = isp_dic
             self.setGlobalInternet(internet)
         else:
@@ -363,7 +359,7 @@ def __route(self):
     def __nat_host(self):
         # If interface is not provided, try guessing one
         if not self.iface:
-            if "happy_host_netif" in os.environ.keys():
+            if "happy_host_netif" in list(os.environ.keys()):
                 self.iface = os.environ['happy_host_netif']
             else:
                 self.iface = self.getDefaultInterfaceName()
@@ -437,7 +433,7 @@ def run(self):
         if self.add:
             with self.getStateLockManager(lock_id="isp"):
                 self.readIspState()
-                # print self.getStateId()
+                # print(self.getStateId())
                 self.isp_pool = self.getIsp()
                 if not bool(self.isp_pool):
                     self.__initialize_isp_pool()
diff --git a/happy/HappyLink.py b/happy/HappyLink.py
index bca8eaea..a880bebb 100644
--- a/happy/HappyLink.py
+++ b/happy/HappyLink.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #       Virtual Link-specific actions inherit from this class.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyLinkAdd.py b/happy/HappyLinkAdd.py
index c6c102f8..fb65fe06 100644
--- a/happy/HappyLinkAdd.py
+++ b/happy/HappyLinkAdd.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #       A virtual node is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -31,6 +32,7 @@
 from happy.Utils import *
 from happy.HappyLink import HappyLink
 import happy.HappyLinkDelete
+from six.moves import range
 
 options = {}
 options["quiet"] = False
@@ -99,7 +101,7 @@ def __pre_check(self):
 
         self.type = self.type.lower()
 
-        if self.type not in self.network_type.keys():
+        if self.type not in list(self.network_type.keys()):
             emsg = "Invalid link type " + self.type
             self.logger.error("[%s] HappyLinkAdd: %s" % ("Link", emsg))
             self.exit()
@@ -161,7 +163,7 @@ def __create_tap_link(self):
         #
 
         cmd = "ip tuntap add " + self.link_node_end + " mode tap"
-        if "USER" in os.environ.keys():
+        if "USER" in list(os.environ.keys()):
             cmd += " user " + os.environ["USER"]
         cmd = self.runAsRoot(cmd)
         ret = self.CallAtHost(cmd)
diff --git a/happy/HappyLinkDelete.py b/happy/HappyLinkDelete.py
index f145776d..7adca5f0 100644
--- a/happy/HappyLinkDelete.py
+++ b/happy/HappyLinkDelete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       deleting a virtual link corresponds to deleting a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyLinkList.py b/happy/HappyLinkList.py
index 082b821b..b3b6541b 100644
--- a/happy/HappyLinkList.py
+++ b/happy/HappyLinkList.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyLinkList class that displays a list of virtual links.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -71,20 +73,20 @@ def __show_links(self):
         link_ids.sort()
 
         for link_id in link_ids:
-            print link_id
+            print(link_id)
 
             if self.getLinkNetwork(link_id) is None:
-                print "\tNetwork: not assigned"
+                print("\tNetwork: not assigned")
             else:
-                print "\tNetwork: " + self.getLinkNetwork(link_id)
+                print("\tNetwork: " + self.getLinkNetwork(link_id))
 
             if self.getLinkNode(link_id) is None:
-                print "\tNode: not assigned"
+                print("\tNode: not assigned")
             else:
-                print "\tNode: " + self.getLinkNode(link_id)
+                print("\tNode: " + self.getLinkNode(link_id))
 
-            print "\tType: " + self.getLinkType(link_id)
-            print
+            print("\tType: " + self.getLinkType(link_id))
+            print()
 
     def __post_check(self):
         pass
diff --git a/happy/HappyLogger.py b/happy/HappyLogger.py
index e7fd512c..743d1a78 100644
--- a/happy/HappyLogger.py
+++ b/happy/HappyLogger.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyLogger class that provides logging routines
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import logging
 import logging.config
 import os
@@ -66,7 +68,7 @@ def __read_conf(self):
                 json_data = jfile.read()
                 self.log_conf = json.loads(json_data)
         except:
-            print "Failed to load config from %s." % (self.log_conf_file)
+            print("Failed to load config from %s." % (self.log_conf_file))
             sys.exit(1)
 
         confDict = dict(self.main_conf)
@@ -83,28 +85,28 @@ def __read_conf(self):
 
         try:
             pass
-        except ValueError, e:
+        except ValueError as e:
             emsg = "Failed to load logging configuration: "
-            print emsg
-            print e
+            print(emsg)
+            print(e)
             sys.exit(1)
 
-        except TypeError, e:
+        except TypeError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
-        except AttributeError, e:
+        except AttributeError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
-        except ImportError, e:
+        except ImportError as e:
             emsg = "Failed to load logging configuration: %d (%s)" % \
                 (e.errno, e.strerror)
-            print emsg
+            print(emsg)
             sys.exit(1)
 
 if __name__ == '__main__':
diff --git a/happy/HappyNetwork.py b/happy/HappyNetwork.py
index 01d1a2ce..89cd7f01 100644
--- a/happy/HappyNetwork.py
+++ b/happy/HappyNetwork.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       virtual network-specific actions inherit from this class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -144,7 +146,7 @@ def getNextNetworkIPv4Id(self, prefix, network_id=None):
 
         prefix_list = prefix.split(".")
         if len(prefix_list) < 3:
-            print hred("Invalid prefix %s, guessing id" % (prefix))
+            print(hred("Invalid prefix %s, guessing id" % (prefix)))
             return 100
 
         for ip in all_ips:
diff --git a/happy/HappyNetworkAdd.py b/happy/HappyNetworkAdd.py
index 20b9f657..2dbbe464 100644
--- a/happy/HappyNetworkAdd.py
+++ b/happy/HappyNetworkAdd.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       bridge that acts like a hub.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -102,7 +103,7 @@ def __pre_check(self):
             self.exit()
 
         # Check if the type of the new network is given
-        if self.type.lower() not in self.network_type.keys():
+        if self.type.lower() not in list(self.network_type.keys()):
             emsg = "Invalid virtual network type: " + self.type + "."
             self.logger.error("[%s] HappyNetworkAdd: %s" % (self.network_id, emsg))
             self.exit()
diff --git a/happy/HappyNetworkAddress.py b/happy/HappyNetworkAddress.py
index b6545554..44f20a40 100644
--- a/happy/HappyNetworkAddress.py
+++ b/happy/HappyNetworkAddress.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       bridge that acts like a hub.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyNetworkDelete.py b/happy/HappyNetworkDelete.py
index ed5351cf..ade400e4 100644
--- a/happy/HappyNetworkDelete.py
+++ b/happy/HappyNetworkDelete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       bridge, thus deleting a virtual network corresponds to deleting a bridge.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyNetworkList.py b/happy/HappyNetworkList.py
index 96ff1c2d..562c73fd 100644
--- a/happy/HappyNetworkList.py
+++ b/happy/HappyNetworkList.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       A virtual network is logical representation of an ethernet bridge.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -62,7 +64,7 @@ def __list_networks(self):
         self.networks = self.getNetworkIds()
 
         for n in self.networks:
-            print n
+            print(n)
             if not self.quiet:
                 self.logger.info(n)
 
diff --git a/happy/HappyNetworkRoute.py b/happy/HappyNetworkRoute.py
index d7a55a8e..11d4e473 100644
--- a/happy/HappyNetworkRoute.py
+++ b/happy/HappyNetworkRoute.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       bridge that acts like a hub.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -235,12 +236,13 @@ def __update_state(self):
 
     def run(self):
         with self.getStateLockManager():
-
             self.__pre_check()
 
         self.__configure_nodes_routes()
-
-        self.__configure_gateway_routing()
+        # For TAP device, happy will not configure address/routing
+        # And it will be configured by LwIP stack in whatever process LwIP is running
+        if not self.IsTapDevice(self.node_id):
+            self.__configure_gateway_routing()
 
         with self.getStateLockManager():
 
diff --git a/happy/HappyNetworkState.py b/happy/HappyNetworkState.py
index 439505a1..7fd47258 100644
--- a/happy/HappyNetworkState.py
+++ b/happy/HappyNetworkState.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyNetworkState class that shows virtual networks.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import json
 import os
 import sys
@@ -139,7 +141,7 @@ def run(self):
 
             if not self.up and not self.down:
                 state = self._networkState()
-                print "Network " + self.network_id + ": " + state
+                print("Network " + self.network_id + ": " + state)
 
             else:
                 self.__update_network_state()
diff --git a/happy/HappyNetworkStatus.py b/happy/HappyNetworkStatus.py
index 2ee96eff..a5423f8e 100644
--- a/happy/HappyNetworkStatus.py
+++ b/happy/HappyNetworkStatus.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyNetworkStatus class that shows virtual networks.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import json
 import os
 import sys
@@ -29,6 +31,7 @@
 from happy.ReturnMsg import ReturnMsg
 from happy.Utils import *
 from happy.HappyNetwork import HappyNetwork
+from six.moves import range
 
 options = {}
 options["quiet"] = False
@@ -77,26 +80,26 @@ def __post_check(self):
         pass
 
     def __print_all_networks(self):
-        print "{0: >15} {1: >12} {2: >7} {3: >44}".format("NETWORKS   Name", "Type", "State", "Prefixes")
+        print("{0: >15} {1: >12} {2: >7} {3: >44}".format("NETWORKS   Name", "Type", "State", "Prefixes"))
 
         for network_id in self.getNetworkIds():
-            print "{0: >15} {1: >12} {2: >7}".format(network_id,
+            print("{0: >15} {1: >12} {2: >7}".format(network_id,
                                                      self.getNetworkType(network_id),
-                                                     self.getNetworkState(network_id)),
+                                                     self.getNetworkState(network_id)), end=' ')
 
             prefixes = self.getNetworkPrefixes(network_id)
 
             if len(prefixes) == 0:
-                print
+                print()
                 continue
 
             for i in range(len(prefixes)):
                 if i > 0:
-                    print " " * 36,
+                    print(" " * 36, end=' ')
 
-                print " {0: >40}/{1: <3}".format(prefixes[i], self.getNetworkPrefixMask(prefixes[i], network_id))
+                print(" {0: >40}/{1: <3}".format(prefixes[i], self.getNetworkPrefixMask(prefixes[i], network_id)))
 
-            print
+            print()
 
     def run(self):
         self.__pre_check()
@@ -108,11 +111,11 @@ def run(self):
         data_state = json.dumps(self.getNetwork(self.network_id), sort_keys=True, indent=4)
         emsg = "virtual network state: " + self.network_id
 
-        print emsg
+        print(emsg)
 
         self.logger.info("[%s] HappyNetworkStatus: %s" % (self.network_id, emsg))
 
-        print data_state
+        print(data_state)
 
         for line in data_state.split("\n"):
             if line is None or len(line) == 0:
diff --git a/happy/HappyNode.py b/happy/HappyNode.py
index 38e77d84..21ac4fee 100644
--- a/happy/HappyNode.py
+++ b/happy/HappyNode.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       virtual node specific actions inherit from this class.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -31,6 +33,7 @@
 from happy.utils.IP import IP
 from happy.HappyHost import HappyHost
 import happy.HappyLinkDelete
+from six.moves import range
 
 
 class HappyNode(HappyHost):
@@ -150,7 +153,6 @@ def getIpAddressStatus(self, address, interface_id=None, node_id=None):
                 if addr != address:
                     continue
 
-                print line
                 return line
 
         return None
@@ -178,7 +180,7 @@ def getHwAddress(self, interface_id=None, node_id=None):
     def getInterfaceEUI64(self, interface_id, node_id=None):
         eui = None
         node_interface = self.getNodeInterface(interface_id, node_id)
-        if "customized_eui64" in node_interface.keys():
+        if "customized_eui64" in list(node_interface.keys()):
             eui = str(node_interface["customized_eui64"])
         else:
             hwAddr = self.getHwAddress(interface_id, node_id)
@@ -219,7 +221,7 @@ def __getNodeIPv4AddressOnPrefix(self, prefix, id):
         prefix_addr, prefix_mask = IP.splitAddressMask(prefix)
         prefix_mask = int(float(prefix_mask))
 
-        addr = prefix_addr.split(".")[:prefix_mask / 8]
+        addr = prefix_addr.split(".")[:prefix_mask // 8]
         addr.append(str(id % 255))
 
         addr = ".".join(addr)
diff --git a/happy/HappyNodeAdd.py b/happy/HappyNodeAdd.py
index 38c8ad59..f54c3977 100644
--- a/happy/HappyNodeAdd.py
+++ b/happy/HappyNodeAdd.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #       A virtual node is logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 import time
diff --git a/happy/HappyNodeAddress.py b/happy/HappyNodeAddress.py
index da0b8bc1..60850293 100644
--- a/happy/HappyNodeAddress.py
+++ b/happy/HappyNodeAddress.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       This is a wrapper around Linux ip-address command.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -31,6 +33,7 @@
 from happy.Utils import *
 from happy.utils.IP import IP
 from happy.HappyNode import HappyNode
+import json
 
 options = {}
 options["quiet"] = False
@@ -187,21 +190,30 @@ def __update_state(self):
             self.removeNodeInterfaceAddress(self.node_id, self.interface, self.ip_address)
 
     def run(self):
-        with self.getStateLockManager():
+        if not self.add and not self.delete:
+            data_state = json.dumps(self.getNodeInterfaceAddresses(self.interface, self.node_id), sort_keys=True, indent=4)
 
-            self.__pre_check()
+            emsg = "virtual node: " + self.node_id + " addresses list for interface id: " + self.interface
 
-            if not self.done:
+            print(emsg)
+            print(data_state)
 
-                if self.add:
-                    self.__add_address()
-                else:
-                    self.__delete_address()
+        else:
+            with self.getStateLockManager():
+
+                self.__pre_check()
+
+                if not self.done:
+                    if not self.IsTapDevice(self.node_id):
+                        if self.add:
+                            self.__add_address()
+                        else:
+                            self.__delete_address()
 
-                self.__post_check()
+                        self.__post_check()
 
-                self.__update_state()
+                    self.__update_state()
 
-                self.writeState()
+                    self.writeState()
 
         return ReturnMsg(0)
diff --git a/happy/HappyNodeDelete.py b/happy/HappyNodeDelete.py
index cae336d8..4f205118 100644
--- a/happy/HappyNodeDelete.py
+++ b/happy/HappyNodeDelete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       deleting a virtual node corresponds to deleting a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 import pprint
diff --git a/happy/HappyNodeEdit.py b/happy/HappyNodeEdit.py
new file mode 100644
index 00000000..d3fb9a82
--- /dev/null
+++ b/happy/HappyNodeEdit.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+
+#
+#    Copyright (c) 2021 Google LLC.
+#    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.
+#
+
+##
+#    @file
+#       Implements HappyNodeAdd class that creates virtual nodes.
+#
+#       A virtual node is logical representation of a network namespace.
+#
+
+from __future__ import absolute_import
+import os
+import sys
+import time
+
+from happy.ReturnMsg import ReturnMsg
+from happy.Utils import *
+from happy.utils.IP import IP
+from happy.HappyNode import HappyNode
+import happy.HappyDNS
+
+options = {}
+options["quiet"] = False
+options["node_id"] = None
+options["new_node_id"] = None
+options["type"] = None
+
+
+def option():
+    return options.copy()
+
+
+class HappyNodeEdit(HappyNode):
+    """
+    Edit the state of an existing virtual node.  Example operations are rename.
+
+    happy-node-edit [-h --help] [-q --quiet]
+                    [-i --id <NODE_NAME>] [-n --new <NEW_NODE_NAME>]
+
+        -i --id         Required. Name of the node to edit.
+        -n --new        New name of the node for rename.
+
+    Note: A node cannot be more than one type. It is either ap, service,
+          or local.
+
+    Examples:
+    $ happy-node-edit -i node1 -n onhub 
+        Renames node1 to now be called onhub.
+
+    return:
+        0    success
+        1    fail
+    """
+
+    def __init__(self, opts=options):
+        HappyNode.__init__(self)
+
+        self.quiet = opts["quiet"]
+        self.node_id = opts["node_id"]
+        self.new_node_id = opts["new_node_id"]
+        self.type = opts["type"]
+
+    def __pre_check(self):
+        # Check if the name of the node is given
+        if not self.node_id:
+            emsg = "Missing name of the virtual node to edit."
+            self.logger.error("[localhost] HappyNodeEdit: %s" % (emsg))
+            self.exit()
+
+        # Check if the name of new node is not a duplicate (that it does not already exists).
+        if not self._nodeExists():
+            emsg = "virtual node %s does not exists." % (self.node_id)
+            self.logger.warning("[%s] HappyNodeEdit: %s" % (self.node_id, emsg))
+
+        # Check if dot is in the name
+        if IP.isDomainName(self.new_node_id):
+            emsg = "Using . (dot) in the name is not allowed."
+            self.logger.error("[localhost] HappyNodeEdit: %s" % (emsg))
+            self.exit()
+
+    def __post_check(self):
+        if not self._nodeExists(self.new_node_id):
+            emsg = "Virtual node does not exist"
+            self.logger.error("[%s] HappyNodeEdit: %s" % (self.node_id, emsg))
+            self.exit()
+
+    def __edit_node_state(self):
+        if (self.new_node_id):
+            self.logger.info("HappyNodeEdit: renaming node "+self.node_id+" to "+self.new_node_id)
+            self.renameNode(self.node_id, self.new_node_id)
+
+            # Update all link references
+            links = self.getLinks()
+            for key in links:
+                if links[key]["node"] == self.node_id:
+                    links[key]["node"] = self.new_node_id
+
+            # Update identifiers references
+            identifiers = self.getShortIdToLongIdMap()
+            for value in identifiers.values():
+                if value["id"] == self.node_id:
+                    value["id"] = self.new_node_id
+
+            # Update netns references
+            netns_map = self.getNetNS()
+            netns_map[self.new_node_id] = netns_map[self.node_id]
+            del netns_map[self.node_id]
+
+    def run(self):
+        with self.getStateLockManager():
+
+            self.__pre_check()
+
+            self.__edit_node_state()
+
+            self.writeState()
+
+            self.__post_check()
+
+        return ReturnMsg(0)
diff --git a/happy/HappyNodeJoin.py b/happy/HappyNodeJoin.py
index 434fcaad..197e57d9 100644
--- a/happy/HappyNodeJoin.py
+++ b/happy/HappyNodeJoin.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       the network. Then TUN is setup on the node.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -296,6 +297,7 @@ def run(self):
 
             self.writeState()
 
+        
         self.__assign_network_addresses()
 
         self.__load_network_routes()
diff --git a/happy/HappyNodeLeave.py b/happy/HappyNodeLeave.py
index da20ac6e..27f38213 100644
--- a/happy/HappyNodeLeave.py
+++ b/happy/HappyNodeLeave.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       the network. Then TUN is setup on the node.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyNodeList.py b/happy/HappyNodeList.py
index 191a2c7c..56f133e5 100644
--- a/happy/HappyNodeList.py
+++ b/happy/HappyNodeList.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyNodeList class that shows virtual nodes.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -61,7 +63,6 @@ def __list_nodes(self):
         self.nodes.sort()
 
         for n in self.nodes:
-            print n
             if not self.quiet:
                 self.logger.info(n)
 
diff --git a/happy/HappyNodeRoute.py b/happy/HappyNodeRoute.py
index d459648a..d250d8cd 100644
--- a/happy/HappyNodeRoute.py
+++ b/happy/HappyNodeRoute.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,8 @@
 #       This is a wrapper around Linux ip-address command.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import re
 import sys
@@ -46,6 +48,7 @@
 options["record"] = True
 options["isp"] = None
 options["seed"] = None
+options["route_type"] = None
 
 
 def option():
@@ -75,6 +78,7 @@ class HappyNodeRoute(HappyNode, HappyNetwork):
                     address.
         -s --isp    Optional. The name of the routing table.
         -e --seed   Optional. Route priority in the routing table. Range: 0-255
+        -y --type   Optional. Ip type of the node's route IP address, one of: v4, v6
 
     Examples:
     $ happy-node-route BorderRouter default 2001:0db8:0111:0001
@@ -107,7 +111,7 @@ def __init__(self, opts=options):
         self.via = opts["via"]
         self.prefix = opts["prefix"]
         self.record = opts["record"]
-        if "weave_service_address" in os.environ.keys():
+        if "weave_service_address" in list(os.environ.keys()):
             tier = os.environ['weave_service_address'].split(".")[3][0:3]
         else:
             tier = "test"
@@ -115,6 +119,7 @@ def __init__(self, opts=options):
         self.route_priority = opts["seed"]
         self.via_device = None
         self.via_address = None
+        self.route_type = opts["route_type"]
 
     def __pre_check(self):
         # Check if the name of the new node is given
@@ -487,21 +492,49 @@ def __update_state(self):
         else:
             self.removeNodeRoute(self.node_id, self.to)
 
+    def getNodeRoute(self, node_id, route_type):
+        """This function will get node route base on node_id and route_type.
+
+        Args:
+           node_id (str): A string containing the node_id, example: "cloud", "BorderRouter"
+           route_type (str): A string containing ip type, example: "v4", "v6"
+
+        Returns:
+           node route ip
+        """
+        node_route_prefix = self.getNodeRoutePrefix(route_type, node_id)
+        node_route_via_id = self.getNodeRouteVia(route_type, node_id)
+        node_route_addrs = self.getNodeAddressesOnPrefix(node_route_prefix, node_route_via_id)
+        if len(node_route_addrs) != 1:
+            self.logger.error("HappyNodeRoute: there should be only one route for {}, given {}".format(node_id, len(node_route_addrs)))
+        return node_route_addrs[0]
+
+
     def run(self):
-        self.__pre_check()
-        if self.route_table is not None:
-            with self.getStateLockManager(lock_id="rt"):
-                if self.add:
-                    self.__add_route()
-                else:
-                    self.__delete_route()
+        # query node's route ip in v4 or v6 format.
+        if not self.add and not self.delete:
+            route_ip = self.getNodeRoute(self.node_id, self.route_type)
+            emsg = "virtual node: {}, route_type: {}, route ip: {}".format(
+                self.node_id, self.route_type, route_ip)
+            print(emsg)
         else:
-            if self.add:
-                self.__add_route()
-            else:
-                self.__delete_route()
-        self.__post_check()
-        with self.getStateLockManager():
-            self.__update_state()
-            self.writeState()
+            self.__pre_check()
+            # only configure route for nonTAP device
+            # for TAP device, route will be configured in lwip stack in weave.
+            if not self.IsTapDevice(self.node_id):
+                if self.route_table is not None:
+                    with self.getStateLockManager(lock_id="rt"):
+                        if self.add:
+                            self.__add_route()
+                        else:
+                            self.__delete_route()
+                else:
+                    if self.add:
+                        self.__add_route()
+                    else:
+                        self.__delete_route()
+                self.__post_check()
+            with self.getStateLockManager():
+                self.__update_state()
+                self.writeState()
         return ReturnMsg(0)
diff --git a/happy/HappyNodeStatus.py b/happy/HappyNodeStatus.py
index ea586fc4..86f93958 100644
--- a/happy/HappyNodeStatus.py
+++ b/happy/HappyNodeStatus.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyNodeStatus class that shows virtual nodes.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import json
 import os
 import sys
@@ -29,6 +31,7 @@
 from happy.ReturnMsg import ReturnMsg
 from happy.Utils import *
 from happy.HappyNode import HappyNode
+from six.moves import range
 
 options = {}
 options["quiet"] = False
@@ -77,38 +80,38 @@ def __post_check(self):
         pass
 
     def __print_all_nodes(self):
-        print "{0: >15} {1: >12} {2: >7} {3: >44}".format("NODES      Name", "Interface", "Type", "IPs")
+        print("{0: >15} {1: >12} {2: >7} {3: >44}".format("NODES      Name", "Interface", "Type", "IPs"))
 
         for node_id in self.getNodeIds():
-            print "{0: >15}".format(node_id),
+            print("{0: >15}".format(node_id), end=' ')
 
             interfaces = self.getNodeInterfaceIds(node_id)
 
             if len(interfaces) == 0:
-                print
+                print()
                 continue
 
             for i in range(len(interfaces)):
                 if i > 0:
-                    print " " * 15,
+                    print(" " * 15, end=' ')
 
-                print "{0: >12} {1: >7}".format(interfaces[i],
-                                                self.getNodeInterfaceType(interfaces[i], node_id)),
+                print("{0: >12} {1: >7}".format(interfaces[i],
+                                                self.getNodeInterfaceType(interfaces[i], node_id)), end=' ')
 
                 addresses = self.getNodeInterfaceAddresses(interfaces[i], node_id)
 
                 if len(addresses) == 0:
-                    print
+                    print()
                     continue
 
                 for a in range(len(addresses)):
                     if a > 0:
-                        print " " * 36,
+                        print(" " * 36, end=' ')
 
-                    print " {0: >40}/{1: <3}".format(addresses[a],
+                    print(" {0: >40}/{1: <3}".format(addresses[a],
                                                      self.getNodeInterfaceAddressMask(interfaces[i],
-                                                     addresses[a], node_id))
-                print
+                                                     addresses[a], node_id)))
+                print()
 
     def run(self):
         self.__pre_check()
@@ -120,11 +123,11 @@ def run(self):
         data_state = json.dumps(self.getNode(self.node_id), sort_keys=True, indent=4)
         emsg = "virtual node state: " + self.node_id
 
-        print emsg
+        print(emsg)
 
         self.logger.info("[%s] HappyNodeStatus: %s" % (self.node_id, emsg))
 
-        print data_state
+        print(data_state)
 
         for line in data_state.split("\n"):
             if line is None or len(line) == 0:
diff --git a/happy/HappyNodeTcpReset.py b/happy/HappyNodeTcpReset.py
index 0ff3935e..2ba84159 100644
--- a/happy/HappyNodeTcpReset.py
+++ b/happy/HappyNodeTcpReset.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -23,6 +23,7 @@
 #
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyNodeTmux.py b/happy/HappyNodeTmux.py
index 7495a2c6..91e46207 100644
--- a/happy/HappyNodeTmux.py
+++ b/happy/HappyNodeTmux.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Implements HappyNodeTmux class that manages virtual nodes tmux sessions.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 import time
diff --git a/happy/HappyPacketProcess.py b/happy/HappyPacketProcess.py
index 0a160116..5b94d2ef 100644
--- a/happy/HappyPacketProcess.py
+++ b/happy/HappyPacketProcess.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -26,6 +26,8 @@
 #
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 from datetime import datetime
 import getopt
 import logging
@@ -37,6 +39,7 @@
 import signal
 import sys
 import time
+from six.moves import range
 
 options = {}
 options['interface'] = None
@@ -410,7 +413,7 @@ def Sniffer(packetList, e):
         snifferFilter = Filter(packetDic, options)
         if snifferFilter.run() is True:
             continue
-        print packetDic
+        print(packetDic)
         packetList.append(packetDic)
         e.set()
 
@@ -493,12 +496,12 @@ def Attack(packetList, e):
 
     for o, a in opts:
         if o in ("-h", "--help"):
-            print 'Implements HappyPacketProcess class that sniffer packet and inject the specifc packet with tcp-reject,' \
+            print('Implements HappyPacketProcess class that sniffer packet and inject the specifc packet with tcp-reject,' \
                   'the fitler rule could be with dst ip or ip list(source ip and dest ip)' \
                   'python HappyPacketProcess.py  --interface "wlan0" --action "RESET" --ips "107.22.61.55,10.0.1.2" ' \
                   '--start "2" --duration 6 ' \
                   'python HappyPacketProcess.py  --interface "wlan0" --action "RESET" --dstPort "11095" ' \
-                  '--start "2" --duration "20"'
+                  '--start "2" --duration "20"')
             sys.exit(0)
 
         elif o in ("-q", "--quiet"):
@@ -564,12 +567,12 @@ def Attack(packetList, e):
     )
     attack.start()
 
-    print str(datetime.now())
+    print(str(datetime.now()))
     try:
         with TimeoutControl(options["duration"]):
             manager.join()
     except Exception:
         sniffer.terminate()
         attack.terminate()
-        print str(datetime.now())
+        print(str(datetime.now()))
         pass
diff --git a/happy/HappyProcess.py b/happy/HappyProcess.py
index 65e83445..93e2dd98 100644
--- a/happy/HappyProcess.py
+++ b/happy/HappyProcess.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Implements HappyProcess class that manages happy processes.
 #
 
+from __future__ import absolute_import
 import os
 import psutil
 import sys
@@ -220,9 +221,17 @@ def TerminateProcesses(self, procs):
             self.__signal_procs(alive, "SIGKILL")
 
     def GetProcessByName(self, name):
-        # TODO: Sometimes pid is there, but psutil cannot get this process, need to fix it.
         processlist = []
-        for pid in psutil.get_pid_list():
+        # At python.psutil 2.0.0, psutil.get_pid_list() has been replaced
+        # by psutil.pids(). Try to access the new one first,
+        # If that throws, try the old one.
+
+        try:
+            pids = psutil.pids()
+        except:
+            pids = psutil.get_pid_list()
+
+        for pid in pids:
             try:
                 p = psutil.Process(pid)
                 if p.name == name:
diff --git a/happy/HappyProcessOutput.py b/happy/HappyProcessOutput.py
index 337a549f..91e37283 100644
--- a/happy/HappyProcessOutput.py
+++ b/happy/HappyProcessOutput.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -26,6 +26,7 @@
 #       is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyProcessStart.py b/happy/HappyProcessStart.py
index c4a7727e..ca99e571 100644
--- a/happy/HappyProcessStart.py
+++ b/happy/HappyProcessStart.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,11 +25,13 @@
 #       is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import subprocess
 import sys
 import time
 import psutil
+import warnings
 
 from happy.ReturnMsg import ReturnMsg
 from happy.Utils import *
@@ -261,35 +263,53 @@ def __start_daemon(self):
             cmd = self.stripRunAsRoot(cmd)
             need_internal_sudo = False
 
-        env_vars = ""
+        env_vars_list = []
+        cmd_list_prefix = []
+        need_bash = False
+
+        if "bash -c" in cmd:
+            tmp = cmd.split("bash -c")
+            need_bash = True
+            cmd_list_prefix = tmp[0].split()
+            cmd = tmp[1]
+
         for key, value in self.env.items():
-            env_vars += key + "=" + value + " "
+            tmp = ""
+            try:
+                tmp = "" + key + "=" + value
+                env_vars_list.append(tmp)
+            except:
+                self.logger.error("Failed to serialize environment variable %s" % (key));
 
-        self.logger.debug("HappyProcessStart with env: > %s" % (env_vars))
+        self.logger.debug("HappyProcessStart with env: > %s" % (env_vars_list))
 
         if self.strace:
-            strace = "strace -tt -o " + self.strace_file + " "
-            for var in env_vars.split():
-                strace += ("-E " + var + " ")
-            cmd = strace + cmd
+            cmd_list_prefix = ["strace", "-tt", "-o", self.strace_file] + cmd_list_prefix
+            tmp = []
+            for i in env_vars_list:
+                tmp.append("-E")
+                tmp.append(i)
+            env_vars_list = tmp
+
         elif need_internal_sudo:
-            cmd = env_vars + cmd
-        elif len(env_vars):
-            cmd = "bash -c " + env_vars + cmd
+            pass
+        elif len(env_vars_list):
+            need_bash = True
 
         if need_internal_sudo:
             if self.rootMode:
-                cmd = self.runAsRoot(cmd)
+                tmp = self.getRunAsRootPrefixList()
             else:
-                cmd = self.runAsUser(cmd)
+                tmp = self.getRunAsUserPrefixList()
+            cmd_list_prefix = tmp + cmd_list_prefix
 
         if self.node_id:
-            cmd = "ip netns exec " + self.uniquePrefix(self.node_id) + " " + cmd
+            cmd_list_prefix = ["ip", "netns", "exec", self.uniquePrefix(self.node_id)] + cmd_list_prefix
 
-        cmd = self.runAsRoot(cmd)
+        cmd_list_prefix = self.getRunAsRootPrefixList() + cmd_list_prefix
 
         try:
-            self.fout = open(self.output_file, "w", 0)
+            self.fout = open(self.output_file, "wb", 0)
         except Exception:
             emsg = "Failed to open file %s." % (self.output_file)
             self.logger.error("[%s] HappyProcessStart: %s." % (self.node_id, emsg))
@@ -300,15 +320,22 @@ def __start_daemon(self):
         popen = None
 
         try:
-
             cmd_list = []
-            if "bash -c" in cmd:
-                tmp = cmd.split("bash -c")
-                cmd_list = tmp[0].split()
-                cmd_list = cmd_list + ['bash', '-c'] + [tmp[1]]
+            if need_bash:
+                env_vars_list = []
+                for key, value in self.env.items():
+                  tmp = ""
+                  try:
+                      tmp = "" + key + '="' + value.replace('\\','\\\\').replace('"','\\"') +'"'
+                      env_vars_list.append(tmp)
+                  except:
+                      self.logger.error("Failed to serialize environment variable %s" % (key));
+                cmd = " ".join(env_vars_list) + ' ' + cmd
+                cmd_list = cmd_list_prefix + ["bash", "-c", cmd]
             else:
-                cmd_list = cmd.split()
+                cmd_list = cmd_list_prefix + env_vars_list + cmd.split()
 
+            self.logger.debug("[%s] HappyProcessStart: executing command list %s" % (self.node_id, cmd_list))
             popen = subprocess.Popen(cmd_list, stdin=subprocess.PIPE, stdout=self.fout)
             self.child_pid = popen.pid
             emsg = "running daemon %s (PID %d)" % (self.tag, self.child_pid)
diff --git a/happy/HappyProcessStop.py b/happy/HappyProcessStop.py
index 59dcf15e..b9549174 100644
--- a/happy/HappyProcessStop.py
+++ b/happy/HappyProcessStop.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import getpass
 import os
 import sys
diff --git a/happy/HappyProcessStrace.py b/happy/HappyProcessStrace.py
index 053105c7..a90e6446 100644
--- a/happy/HappyProcessStrace.py
+++ b/happy/HappyProcessStrace.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -26,6 +26,7 @@
 #       is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/HappyProcessWait.py b/happy/HappyProcessWait.py
index 7a6cfeff..5511fba7 100644
--- a/happy/HappyProcessWait.py
+++ b/happy/HappyProcessWait.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -25,6 +25,7 @@
 #       is a logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 import time
diff --git a/happy/HappyShell.py b/happy/HappyShell.py
index 061adb89..fc4ce6a3 100644
--- a/happy/HappyShell.py
+++ b/happy/HappyShell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #       A virtual node is logical representation of a network namespace.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
@@ -108,7 +109,7 @@ def run(self):
 
         env = os.environ
 
-        env["PS1"] = '\u@' + '\[\e[1;32m\]' + self.node_id + '\[\e[m\]' + ':\w\$ '
+        env["PS1"] = r'\u@' + '\[\e[1;32m\]' + self.node_id + '\[\e[m\]' + ':\w\$ '
         env["HAPPY_HOST"] = self.node_id
 
         if self.command:
diff --git a/happy/HappyState.py b/happy/HappyState.py
index 90c76cbf..e47d073a 100644
--- a/happy/HappyState.py
+++ b/happy/HappyState.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements HappyState class that reports virtual network topology setup information.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import itertools
 import json
 import os
@@ -54,6 +56,8 @@
 options["unlock"] = False
 options["id"] = False
 options["all"] = False
+options["node"] = None
+options["extension"] = None
 
 
 def option():
@@ -66,15 +70,18 @@ class HappyState(State):
 
     happy-state [-h --help] [-q --quiet] [-s --save <JSON_FILE>] [-g --graph]
                 [-l --logs] [-j --json] [-u --unlock] [-i --id] [-a --all]
-
-        -s --save   Saves the current network topology state in a JSON file.
-        -g --graph  Generates a network topology graph.
-        -l --logs   Display Happy run-time logs. Run in a separate terminal
-                    window to observe logs while using Happy.
-        -j --json   Display the current state in JSON format.
-        -u --unlock Force unlock the Happy state file (~/.happy_state.json).
-        -i --id     Displays all known state IDs.
-        -a --all    Displays the network topology state for all known states.
+                [-n --node <node name>]
+
+        -s --save      Saves the current network topology state in a JSON file.
+        -g --graph     Generates a network topology graph.
+        -l --logs      Display Happy run-time logs. Run in a separate terminal
+                       window to observe logs while using Happy.
+        -j --json      Display the current state in JSON format.
+        -u --unlock    Force unlock the Happy state file (~/.happy_state.json).
+        -i --id        Displays all known state IDs.
+        -a --all       Displays the network topology state for all known states.
+        -n --node      Displays all information for a node
+        -e --extension Displays happy extension value (eg. weave-fabric-id)
 
     Examples:
     $ happy-state
@@ -102,13 +109,15 @@ def __init__(self, opts=options):
         self.unlock_state = opts["unlock"]
         self.show_id = opts["id"]
         self.all = opts["all"]
+        self.node = opts["node"]
+        self.extension = opts["extension"]
 
     def __pre_check(self):
         pass
 
     def __print_data_state(self):
         if self.quiet or self.graph or self.save or self.log or self.json or \
-           self.unlock_state or self.show_id:
+           self.unlock_state or self.show_id or self.node or self.extension:
             return
 
         self.__print_own_state()
@@ -133,33 +142,33 @@ def __print_data_state(self):
 
     def __print_own_state(self):
 
-        print
+        print()
 
-        print "State Name: ",
-        print self.getStateId()
+        print("State Name: ", end=' ')
+        print(self.getStateId())
 
-        print
+        print()
 
         options = happy.HappyNetworkStatus.option()
         options["quiet"] = self.quiet
         nets = happy.HappyNetworkStatus.HappyNetworkStatus(options)
         nets.run()
 
-        print
+        print()
 
         options = happy.HappyNodeStatus.option()
         options["quiet"] = self.quiet
         nodes = happy.HappyNodeStatus.HappyNodeStatus(options)
         nodes.run()
 
-        print
+        print()
 
     def __print_json_state(self):
         if self.json:
             json_data = json.dumps(self.state, sort_keys=True, indent=4)
-            print
-            print json_data
-            print
+            print()
+            print(json_data)
+            print()
 
     def __save_state(self):
         if self.save is None:
@@ -171,13 +180,45 @@ def __save_state(self):
         try:
             json_data = json.dumps(self.state, sort_keys=True, indent=4)
         except Exception:
-            print "Failed to save state file: %s" % (self.save)
+            print("Failed to save state file: %s" % (self.save))
             self.logger.error("calls self.exit()")
             self.exit()
 
         with open(self.save, 'w') as jfile:
             jfile.write(json_data)
 
+    def __print_node_state(self):
+        """print_node_state
+        print single node information
+        """
+        if self.node is None:
+            return
+        node_info = {}
+
+        print()
+
+        print("State Name: {}".format(self.getStateId()))
+
+        print("Node: {}".format(self.node))
+
+        node_info = self.getNodeInfo(self.node)
+        node_json_data = json.dumps(node_info, sort_keys=True, indent=2)
+        print(node_json_data)
+
+    def __print_extension_value(self):
+        """print happy extension value
+        example: weave-fabric-id
+        """
+        if self.extension is None:
+            return
+
+        print("{} extension state:".format(self.extension))
+
+        ext_value = self.getExtensionValue(self.extension)
+
+        ext_json_data = json.dumps(ext_value, sort_keys=True, indent=2)
+        print(ext_json_data)
+
     def __graph_state(self):
         if self.graph is None:
             return
@@ -185,17 +226,17 @@ def __graph_state(self):
         if not has_networkx:
             emsg = "Cannot generate graph. Localhost is missing networkx libraries."
             self.logger.warning("[localhost] HappyState: %s" % (emsg))
-            print hyellow(emsg)
+            print(hyellow(emsg))
             extra_msg = "Try,   apt-get install python-networkx"
-            print hyellow(extra_msg)
+            print(hyellow(extra_msg))
             return
 
         if not has_matplotlib:
             emsg = "Cannot generate graph. Localhost is missing matplotlib libraries."
             self.logger.warning("[localhost] HappyState: %s" % (emsg))
-            print hyellow(emsg)
+            print(hyellow(emsg))
             extra_msg = "Try,   apt-get install python-matplotlib"
-            print hyellow(extra_msg)
+            print(hyellow(extra_msg))
             return
 
         G = nx.Graph()
@@ -298,19 +339,19 @@ def __show_state_id(self):
         if self.show_id:
             states = self.__get_state_ids()
             this_state = self.getStateId()
-            print this_state + " <"
+            print(this_state + " <")
 
             for s in states:
                 if s == this_state:
                     continue
-                print s
+                print(s)
 
     def __get_log_path(self):
         file_path = None
 
-        if "handlers" in self.log_conf.keys():
-            if "file" in self.log_conf["handlers"].keys():
-                if "filename" in self.log_conf["handlers"]["file"].keys():
+        if "handlers" in list(self.log_conf.keys()):
+            if "file" in list(self.log_conf["handlers"].keys()):
+                if "filename" in list(self.log_conf["handlers"]["file"].keys()):
                     file_path = self.log_conf["handlers"]["file"]["filename"]
 
         return file_path
@@ -345,12 +386,12 @@ def __show_logs(self):
             if file_path is None:
                 emsg = "Happy aggregated logs file is unknown."
                 self.logger.warning("[localhost] HappyState: %s" % (emsg))
-                print hyellow(emsg)
+                print(hyellow(emsg))
                 return
 
             cmd = "tail -n 100 -f  " + file_path
 
-        print hgreen("Happy Runtime Logs. Press <Ctrl-C> to exit.")
+        print(hgreen("Happy Runtime Logs. Press <Ctrl-C> to exit."))
         os.system(cmd)
 
     def run(self):
@@ -363,6 +404,8 @@ def run(self):
         self.__show_state_id()
         self.__save_state()
         self.__graph_state()
+        self.__print_node_state()
+        self.__print_extension_value()
         self.__show_logs()
 
         self.__post_check()
diff --git a/happy/HappyStateDelete.py b/happy/HappyStateDelete.py
index d44853ab..c47ba0f8 100644
--- a/happy/HappyStateDelete.py
+++ b/happy/HappyStateDelete.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,9 +22,11 @@
 #       Implements HappyStateDelete class that deletes virtual network topology and its state.
 #
 
+from __future__ import absolute_import
 import json
 import os
 import sys
+import subprocess
 
 from happy.ReturnMsg import ReturnMsg
 from happy.Utils import *
@@ -33,7 +35,9 @@
 import happy.HappyNetworkDelete
 import happy.HappyLinkDelete
 import happy.HappyNode
+import happy.HappyInternet
 from happy.HappyHost import HappyHost
+from six.moves import input
 
 options = {}
 options["quiet"] = False
@@ -126,7 +130,7 @@ def __delete_host_netns(self):
             if self.force is True:
                 delete_it = True
             else:
-                reply = str(raw_input("Delete host namespace " + node_id + " (y/N): ")).lower().strip()
+                reply = str(input("Delete host namespace " + node_id + " (y/N): ")).lower().strip()
                 if reply[0] == 'y':
                     delete_it = True
 
@@ -149,7 +153,7 @@ def __delete_host_bridges(self):
             if self.force is True:
                 delete_it = True
             else:
-                reply = str(raw_input("Delete host bridge " + network_id + " (y/N): ")).lower().strip()
+                reply = str(input("Delete host bridge " + network_id + " (y/N): ")).lower().strip()
                 if reply[0] == 'y':
                     delete_it = True
 
@@ -172,7 +176,7 @@ def __delete_host_links(self):
             if self.force is True:
                 delete_it = True
             else:
-                reply = str(raw_input("Delete host link " + link_id + " (y/N): ")).lower().strip()
+                reply = str(input("Delete host link " + link_id + " (y/N): ")).lower().strip()
                 if len(reply) > 0 and reply[0] == 'y':
                     delete_it = True
 
@@ -193,9 +197,30 @@ def __cleanup_host(self):
         self.__delete_host_bridges()
         self.__delete_host_links()
 
+    def __delete_internet(self):
+        """
+        delete internet isp interface
+        functionality similar to command: happy-internet -d ...
+        """
+        #get global config before it is deleted
+        self.global_config = self.getGlobal()
+        if "internet" in self.global_config:
+            internet_config = self.global_config["internet"]
+            for internet_value in internet_config.values():
+                options = happy.HappyInternet.option()
+                options["delete"] = True
+                options["iface"] = internet_value["iface"]
+                options["isp"] = internet_value["isp"]
+                options["seed"] = internet_value["isp_addr"].split(".")[2]
+                options["node_id"] = internet_value["node_id"]
+                hi = happy.HappyInternet.HappyInternet(options)
+                hi.start()
+
     def run(self):
         self.__pre_check()
 
+        self.__delete_internet()
+
         self.__delete_state()
 
         self.__delete_state_file()
diff --git a/happy/HappyStateLoad.py b/happy/HappyStateLoad.py
index 36d9f60f..2950660b 100644
--- a/happy/HappyStateLoad.py
+++ b/happy/HappyStateLoad.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -24,6 +24,7 @@
 #       A virtual fabric topology consists of virtual nodes and networks.
 #
 
+from __future__ import absolute_import
 import json
 import os
 import sys
@@ -153,7 +154,7 @@ def __nodes_join_networks(self):
             options["network_id"] = link["network"]
             options["tap"] = link["tap"]
 
-            if "fix_hw_addr" in link.keys():
+            if "fix_hw_addr" in list(link.keys()):
                 options["fix_hw_addr"] = link["fix_hw_addr"]
             else:
                 options["fix_hw_addr"] = None
diff --git a/happy/HappyStateUnload.py b/happy/HappyStateUnload.py
index 0bde26df..7a0a0c66 100644
--- a/happy/HappyStateUnload.py
+++ b/happy/HappyStateUnload.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Implements HappyStateUnload class that removed nodes and networks from virtual topology.
 #
 
+from __future__ import absolute_import
 import json
 import os
 import sys
diff --git a/happy/HappyTopologyMgr.py b/happy/HappyTopologyMgr.py
index 58b9811f..ac9628f8 100644
--- a/happy/HappyTopologyMgr.py
+++ b/happy/HappyTopologyMgr.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -23,6 +23,7 @@
 #
 #
 
+from __future__ import absolute_import
 import happy.HappyNodeDelete
 
 import happy.HappyConfiguration
diff --git a/happy/Ping.py b/happy/Ping.py
index fef4e240..471badc4 100644
--- a/happy/Ping.py
+++ b/happy/Ping.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Implements Ping class that call ping to virtual nodes.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 
@@ -189,7 +191,7 @@ def run(self):
         if self.addresses == {}:
             emsg = "No address to ping at " + str(self.destination) + "."
             self.logger.warning("[%s] Ping: %s" % (self.source, emsg))
-            print hyellow(emsg)
+            print(hyellow(emsg))
             return ReturnMsg(100, self.addresses)
 
         for addr in self.addresses.keys():
diff --git a/happy/ReturnMsg.py b/happy/ReturnMsg.py
index 820b97e8..cd1b986d 100644
--- a/happy/ReturnMsg.py
+++ b/happy/ReturnMsg.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
diff --git a/happy/State.py b/happy/State.py
index b467b526..9f803138 100644
--- a/happy/State.py
+++ b/happy/State.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -23,10 +23,12 @@
 #       parts of state setup.
 #
 
+from __future__ import absolute_import
 import logging
 
 from happy.utils.IP import IP
 from happy.Driver import Driver
+import six
 
 
 class State(Driver):
@@ -65,25 +67,25 @@ def getIspState(self, state=None):
 
     def getNetNS(self, state=None):
         state = self.getState(state)
-        if "netns" not in state.keys():
+        if "netns" not in list(state.keys()):
             state["netns"] = {}
         return state["netns"]
 
     def getNetNSIds(self, state=None):
         netns = self.getNetNS(state)
-        ids = netns.keys()
+        ids = list(netns.keys())
         ids.sort()
         return ids
 
     def getIdentifiers(self, state=None):
         state = self.getState(state)
-        if "identifiers" not in state.keys():
+        if "identifiers" not in list(state.keys()):
             state["identifiers"] = {}
         return state["identifiers"]
 
     def getIdentifierByNodeId(self, node_id, state=None):
         state = self.getState(state)
-        if "netns" not in state.keys():
+        if "netns" not in list(state.keys()):
             return None
         if node_id not in state["netns"]:
             return None
@@ -91,55 +93,55 @@ def getIdentifierByNodeId(self, node_id, state=None):
 
     def getNodes(self, state=None):
         state = self.getState(state)
-        if "node" not in state.keys():
+        if "node" not in list(state.keys()):
             state["node"] = {}
         return state["node"]
 
     def getNodeIds(self, state=None):
         state = self.getState(state)
-        if "node" not in state.keys():
+        if "node" not in list(state.keys()):
             state["node"] = {}
-        ids = state["node"].keys()
+        ids = list(state["node"].keys())
         ids.sort()
         return ids
 
     def getNetworks(self, state=None):
         state = self.getState(state)
-        if "network" not in state.keys():
+        if "network" not in list(state.keys()):
             state["network"] = {}
         return state["network"]
 
     def getNetworkIds(self, state=None):
         state = self.getState(state)
-        if "network" not in state.keys():
+        if "network" not in list(state.keys()):
             state["network"] = {}
-        ids = state["network"].keys()
+        ids = list(state["network"].keys())
         ids.sort()
         return ids
 
     def getLinks(self, state=None):
         state = self.getState(state)
-        if "link" not in state.keys():
+        if "link" not in list(state.keys()):
             state["link"] = {}
         return state["link"]
 
     def getLinkIds(self, state=None):
         state = self.getState(state)
-        if "link" not in state.keys():
+        if "link" not in list(state.keys()):
             state["link"] = {}
-        ids = state["link"].keys()
+        ids = list(state["link"].keys())
         ids.sort()
         return ids
 
     def getGlobal(self, state=None):
         state = self.getState(state)
-        if "global" not in state.keys():
+        if "global" not in list(state.keys()):
             state["global"] = {}
         return state["global"]
 
     def getGlobalIsp(self, state=None):
         state = self.getIspState(state)
-        if "global_isp" not in state.keys():
+        if "global_isp" not in list(state.keys()):
             state["global_isp"] = {}
         return state["global_isp"]
 
@@ -155,17 +157,17 @@ def getNode(self, node_id=None, state=None):
 
     def getNodeInterfaces(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "interface" not in node_record.keys():
+        if "interface" not in list(node_record.keys()):
             return {}
         return node_record["interface"]
 
     def getNodeInterfaceIds(self, node_id=None, state=None):
         node_interfaces = self.getNodeInterfaces(node_id, state)
-        return node_interfaces.keys()
+        return list(node_interfaces.keys())
 
     def getNodeInterface(self, interface_id, node_id=None, state=None):
         node_interfaces = self.getNodeInterfaces(node_id, state)
-        if interface_id not in node_interfaces.keys():
+        if interface_id not in list(node_interfaces.keys()):
             return {}
         return node_interfaces[interface_id]
 
@@ -179,7 +181,25 @@ def getNodeInterfaceAddresses(self, interface_id, node_id=None, state=None):
         node_interface = self.getNodeInterface(interface_id, node_id, state)
         if node_interface == {}:
             return []
-        return node_interface["ip"].keys()
+        return list(node_interface["ip"].keys())
+
+    def getNodeAddrMatchingPrefix(self, node_id, interface, prefix):
+        """Each interface may have multiple addresses, this function will get addresses based on interface and prefix.
+
+        Args:
+           node_id (str): A string containing the node_id, example: "cloud", "BorderRouter"
+           interface (str): A string containing interface id, example: "wpan0", "wlan0"
+           prefix (str): A string containing an IP prefix, example: "10.0.1"
+
+        Returns:
+           addresses_matchPrefix: A string list containing IP addresses
+        """
+        addresses = self.getNodeInterfaceAddresses(interface, node_id)
+        addresses_matchPrefix = []
+        for address in addresses:
+            if IP.prefixMatchAddress(prefix, address):
+                addresses_matchPrefix.append(address)
+        return addresses_matchPrefix
 
     def getNodeInterfaceAddressInfo(self, interface_id, addr, node_id=None, state=None):
         node_interface = self.getNodeInterface(interface_id, node_id, state)
@@ -261,17 +281,39 @@ def getNodeInterfacePrefixes(self, interface_id, node_id=None, state=None):
 
     def getNodeRoutes(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "route" not in node_record.keys():
+        if "route" not in list(node_record.keys()):
             return {}
         return node_record["route"]
 
+    def getNodeRoutePrefix(self, route_type, node_id=None, state=None):
+        """
+        getting node route prefix base on route type "v4" or "v6"
+        """
+        node_routes = self.getNodeRoutes(node_id, state)
+        for node_route in node_routes.keys():
+            if route_type in node_route:
+                return node_routes[node_route]["prefix"]
+        else:
+            return None
+
+    def getNodeRouteVia(self, route_type, node_id=None, state=None):
+        """
+        getting node route via node id base on route type "v4" or "v6"
+        """
+        node_routes = self.getNodeRoutes(node_id, state)
+        for node_route in node_routes.keys():
+            if route_type in node_route:
+                return node_routes[node_route]["via"]
+        else:
+            return None
+
     def getNodeRouteIds(self, node_id=None, state=None):
         node_routes = self.getNodeRoutes(node_id, state)
-        return node_routes.keys()
+        return list(node_routes.keys())
 
     def getNodeRoute(self, route_to, node_id=None, state=None):
         node_routes = self.getNodeRoutes(node_id, state)
-        if route_to not in node_routes.keys():
+        if route_to not in list(node_routes.keys()):
             return {}
         return node_routes[route_to]
 
@@ -279,7 +321,9 @@ def getNodeLinkIds(self, node_id=None, state=None):
         node_interfaces = self.getNodeInterfaces(node_id, state)
         links = []
         for interface_id in node_interfaces.keys():
-            links.append(node_interfaces[interface_id]["link"])
+            link = node_interfaces[interface_id]["link"]
+            if link is not None:
+                links.append(link)
         links.sort()
         return links
 
@@ -299,13 +343,13 @@ def getNodeInterfaceFromLink(self, link_id, node_id=None, state=None):
 
     def getNodeTmuxSessionIds(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "tmux" not in node_record.keys():
+        if "tmux" not in list(node_record.keys()):
             return []
-        return node_record["tmux"].keys()
+        return list(node_record["tmux"].keys())
 
     def getNodeTmuxSession(self, session_id, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "tmux" not in node_record.keys():
+        if "tmux" not in list(node_record.keys()):
             return {}
         if session_id not in self.getNodeTmuxSessionIds(node_id, state):
             return {}
@@ -315,66 +359,66 @@ def getNodeTmuxSessionUser(self, session_id, node_id=None, state=None):
         tmux_session = self.getNodeTmuxSession(session_id, node_id, state)
         if tmux_session == {}:
             return None
-        if "run_as_user" in tmux_session.keys():
+        if "run_as_user" in list(tmux_session.keys()):
             return tmux_session["run_as_user"]
         else:
             return None
 
     def getNodeProcesses(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "process" not in node_record.keys():
+        if "process" not in list(node_record.keys()):
             return {}
         return node_record["process"]
 
     def getNodeProcessIds(self, node_id=None, state=None):
         node_processes = self.getNodeProcesses(node_id, state)
-        return node_processes.keys()
+        return list(node_processes.keys())
 
     def getNodeProcess(self, tag, node_id=None, state=None):
         node_processes = self.getNodeProcesses(node_id, state)
-        if tag not in node_processes.keys():
+        if tag not in list(node_processes.keys()):
             return {}
         return node_processes[tag]
 
     def getNodeType(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "type" not in node_record.keys():
+        if "type" not in list(node_record.keys()):
             return None
         return node_record["type"]
 
     def getNodeProcessPID(self, tag=None, node_id=None, state=None):
         process_record = self.getNodeProcess(tag, node_id, state)
-        if "pid" not in process_record.keys():
+        if "pid" not in list(process_record.keys()):
             return None
         return process_record["pid"]
 
     def getNodeProcessCreateTime(self, tag=None, node_id=None, state=None):
         process_record = self.getNodeProcess(tag, node_id, state)
-        if "create_time" not in process_record.keys():
+        if "create_time" not in list(process_record.keys()):
             return None
         return process_record["create_time"]
 
     def getNodeProcessOutputFile(self, tag=None, node_id=None, state=None):
         process_record = self.getNodeProcess(tag, node_id, state)
-        if "out" not in process_record.keys():
+        if "out" not in list(process_record.keys()):
             return None
         return process_record["out"]
 
     def getNodeProcessStraceFile(self, tag=None, node_id=None, state=None):
         process_record = self.getNodeProcess(tag, node_id, state)
-        if "strace" not in process_record.keys():
+        if "strace" not in list(process_record.keys()):
             return None
         return process_record["strace"]
 
     def getNodeProcessCommand(self, tag=None, node_id=None, state=None):
         process_record = self.getNodeProcess(tag, node_id, state)
-        if "command" not in process_record.keys():
+        if "command" not in list(process_record.keys()):
             return None
         return process_record["command"]
 
     def getNodeNetNS(self, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "netns" not in node_record.keys():
+        if "netns" not in list(node_record.keys()):
             return None
         return node_record["netns"]
 
@@ -390,7 +434,7 @@ def getNetwork(self, network_id=None, state=None):
 
     def getNetworkNetNS(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "netns" not in network_record.keys():
+        if "netns" not in list(network_record.keys()):
             return None
         return network_record["netns"]
 
@@ -451,69 +495,69 @@ def getNetworkNodesIds(self, network_id=None, state=None):
 
     def getNetworkLinks(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "interface" not in network_record.keys():
+        if "interface" not in list(network_record.keys()):
             return {}
         return network_record["interface"]
 
     def getNetworkLinkIds(self, network_id=None, state=None):
         network_links = self.getNetworkLinks(network_id, state)
-        ids = network_links.keys()
+        ids = list(network_links.keys())
         ids.sort()
         return ids
 
     def getNetworkLink(self, interface_id, network_id=None, state=None):
         network_links = self.getNetworkLinks(network_id, state)
-        if interface_id not in network_links.keys():
+        if interface_id not in list(network_links.keys()):
             return {}
         return network_links[interface_id]
 
     def getNetworkType(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "type" not in network_record.keys():
+        if "type" not in list(network_record.keys()):
             return None
         return network_record["type"]
 
     def getNetworkState(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "state" not in network_record.keys():
+        if "state" not in list(network_record.keys()):
             return None
         return network_record["state"]
 
     def getNetworkPrefixRecords(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "prefix" not in network_record.keys():
+        if "prefix" not in list(network_record.keys()):
             return {}
         return network_record["prefix"]
 
     def getNetworkPrefixes(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "prefix" not in network_record.keys():
+        if "prefix" not in list(network_record.keys()):
             return []
-        return network_record["prefix"].keys()
+        return list(network_record["prefix"].keys())
 
     def getNetworkPrefixMask(self, prefix, network_id=None, state=None):
         network_prefixes = self.getNetworkPrefixRecords(network_id, state)
         if prefix not in self.getNetworkPrefixes(network_id, state):
             return None
-        if "mask" not in network_prefixes[prefix].keys():
+        if "mask" not in list(network_prefixes[prefix].keys()):
             return None
         return network_prefixes[prefix]["mask"]
 
     def getNetworkRoutes(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "route" not in network_record.keys():
+        if "route" not in list(network_record.keys()):
             return {}
         return network_record["route"]
 
     def getNetworkRouteIds(self, network_id=None, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "route" not in network_record.keys():
+        if "route" not in list(network_record.keys()):
             return []
-        return network_record["route"].keys()
+        return list(network_record["route"].keys())
 
     def getNetworkRoute(self, route_to, network_id=None, state=None):
         network_routes = self.getNetworkRoutes(network_id, state)
-        if route_to not in network_routes.keys():
+        if route_to not in list(network_routes.keys()):
             return {}
         return network_routes[route_to]
 
@@ -529,109 +573,109 @@ def getLink(self, link_id=None, state=None):
 
     def getLinkNode(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "node" not in link.keys():
+        if "node" not in list(link.keys()):
             return None
         return link["node"]
 
     def getLinkNetwork(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "network" not in link.keys():
+        if "network" not in list(link.keys()):
             return None
         return link["network"]
 
     def getLinkType(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "type" not in link.keys():
+        if "type" not in list(link.keys()):
             return None
         return link["type"]
 
     def getLinkNumber(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "number" not in link.keys():
+        if "number" not in list(link.keys()):
             return None
         return link["number"]
 
     def getLinkTap(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "tap" not in link.keys():
+        if "tap" not in list(link.keys()):
             return None
         return link["tap"]
 
     def getLinkNodeEnd(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "node_end" not in link.keys():
+        if "node_end" not in list(link.keys()):
             return None
         return link["node_end"]
 
     def getLinkNetworkEnd(self, link_id=None, state=None):
         link = self.getLink(link_id, state)
-        if "network_end" not in link.keys():
+        if "network_end" not in list(link.keys()):
             return None
         return link["network_end"]
 
     def getInternet(self, state=None):
         global_record = self.getGlobal(state)
-        if "internet" not in global_record.keys():
+        if "internet" not in list(global_record.keys()):
             return {}
         return global_record["internet"]
 
     def getIsp(self, state=None):
         global_isp_record = self.getGlobalIsp(state)
-        if "isp" not in global_isp_record.keys():
+        if "isp" not in list(global_isp_record.keys()):
             return {}
         return global_isp_record["isp"]
 
     def getDNS(self, state=None):
         global_record = self.getGlobal(state)
-        if "DNS" not in global_record.keys():
+        if "DNS" not in list(global_record.keys()):
             return None
         return global_record["DNS"]
 
     def getInternetHostLinkId(self, isp_id, state=None):
         internet_record = self.getInternet(state)
-        if isp_id in internet_record.keys() and "host_link" not in internet_record[isp_id]:
+        if isp_id in list(internet_record.keys()) and "host_link" not in internet_record[isp_id]:
             return None
         return internet_record[isp_id]["host_link"]
 
     def getInternetNodeLinkId(self, isp_id, state=None):
         internet_record = self.getInternet(state)
-        if isp_id in internet_record.keys() and "node_link" not in internet_record[isp_id]:
+        if isp_id in list(internet_record.keys()) and "node_link" not in internet_record[isp_id]:
             return None
         return internet_record[isp_id]["node_link"]
 
     def getInternetNodeId(self, isp_id, state=None):
         internet_record = self.getInternet(state)
-        if isp_id in internet_record.keys() and "node_id" not in internet_record[isp_id]:
+        if isp_id in list(internet_record.keys()) and "node_id" not in internet_record[isp_id]:
             return None
         return internet_record[isp_id]["node_id"]
 
     def getInternetIspAddr(self, isp_id, state=None):
         internet_record = self.getInternet(state)
-        if isp_id in internet_record.keys() and "isp_addr" not in internet_record[isp_id]:
+        if isp_id in list(internet_record.keys()) and "isp_addr" not in internet_record[isp_id]:
             return None
         return internet_record[isp_id]["isp_addr"]
 
     def getInternetIspIndex(self, isp_id, state=None):
         internet_record = self.getInternet(state)
-        if isp_id in internet_record.keys() and "isp_index" not in internet_record[isp_id]:
+        if isp_id in list(internet_record.keys()) and "isp_index" not in internet_record[isp_id]:
             return None
         return internet_record[isp_id]["isp_index"]
 
     def getIspAvailable(self, state=None):
         isp_record = self.getIsp(state)
-        available_ip_pool = filter(lambda s: not s["occupy"], isp_record)
+        available_ip_pool = [s for s in isp_record if not s["occupy"]]
         return available_ip_pool
 
     def getIspAvailableIndex(self, state=None):
         isp_record = self.getIsp(state)
-        available_ip_pool = filter(lambda s: not s["occupy"], isp_record)
+        available_ip_pool = [s for s in isp_record if not s["occupy"]]
         return int(available_ip_pool[0]['isp_index']) - 1
 
     def getIspAddr(self, index, state=None):
         isp_record = self.getIsp(state)
         if index >= len(isp_record) or index < 0:
             return None
-        if "isp_addr" not in isp_record[index].keys():
+        if "isp_addr" not in list(isp_record[index].keys()):
             return None
         return isp_record[index]["isp_addr"]
 
@@ -639,7 +683,7 @@ def getIspHostLinkId(self, index, state=None):
         isp_record = self.getIsp(state)
         if index > len(isp_record) or index < 0:
             return None
-        if "isp_host_end" not in isp_record[index].keys():
+        if "isp_host_end" not in list(isp_record[index].keys()):
             return None
         return isp_record[index]["isp_host_end"]
 
@@ -647,7 +691,7 @@ def getIspNodeLinkId(self, index, state=None):
         isp_record = self.getIsp(state)
         if index >= len(isp_record) or index < 0:
             return None
-        if "isp_node_end" not in isp_record[index].keys():
+        if "isp_node_end" not in list(isp_record[index].keys()):
             return None
         return isp_record[index]["isp_node_end"]
 
@@ -655,13 +699,13 @@ def getIspIndex(self, index, state=None):
         isp_record = self.getIsp(state)
         if index >= len(isp_record) or index < 0:
             return None
-        if "isp_index" not in isp_record[index].keys():
+        if "isp_index" not in list(isp_record[index].keys()):
             return None
         return isp_record[index]["isp_index"]
 
     def setNodeProcess(self, process, tag, node_id=None, state=None):
         node_record = self.getNode(node_id, state)
-        if "process" not in node_record.keys():
+        if "process" not in list(node_record.keys()):
             node_record["process"] = {}
         node_record["process"][tag] = process
 
@@ -673,7 +717,7 @@ def setLink(self, link_id, link, state=None):
     def setLinkNetworkNodeHw(self, link_id, network_id, node_id, hw_addr, state=None):
         links = self.getLinks(state)
         if links is not None:
-            if link_id not in links.keys():
+            if link_id not in list(links.keys()):
                 links[link_id] = {}
             links[link_id]["network"] = network_id
             links[link_id]["node"] = node_id
@@ -692,13 +736,13 @@ def setNetwork(self, network_id, network, state=None):
     def setNodeIpAddress(self, node_id, interface_id, ip_address, record, state=None):
         node_interface = self.getNodeInterface(interface_id, node_id, state)
         if node_interface is not None:
-            if "ip" not in node_interface.keys():
+            if "ip" not in list(node_interface.keys()):
                 node_interface["ip"] = {}
             node_interface["ip"][ip_address] = record
 
     def setNodeTmux(self, node_id, session_id, record, state=None):
         node_record = self.getNode(node_id, state)
-        if "tmux" not in node_record.keys():
+        if "tmux" not in list(node_record.keys()):
             node_record["tmux"] = {}
         node_record["tmux"][session_id] = record
 
@@ -710,11 +754,11 @@ def setNodeInterface(self, node_id, interface_id, record, state=None):
     def setNodeRoute(self, node_id, to, record, state=None):
         node_record = self.getNode(node_id, state)
         if node_record is not None:
-            if "route" not in node_record.keys():
+            if "route" not in list(node_record.keys()):
                 node_record["route"] = {}
 
-            if ("via" in record.keys() and IP.isIpv6(record["via"])) or \
-               ("prefix" in record.keys() and IP.isIpv6(record["prefix"])):
+            if ("via" in list(record.keys()) and IP.isIpv6(record["via"])) or \
+               ("prefix" in list(record.keys()) and IP.isIpv6(record["prefix"])):
                 to = to + "_v6"
             else:
                 to = to + "_v4"
@@ -734,11 +778,11 @@ def setNetworkLink(self, network_id, link_id, record, state=None):
     def setNetworkRoute(self, network_id, to, record, state=None):
         network_record = self.getNetwork(network_id, state)
         if network_record is not None:
-            if "route" not in network_record.keys():
+            if "route" not in list(network_record.keys()):
                 network_record["route"] = {}
 
-            if ("via" in record.keys() and IP.isIpv6(record["via"])) or \
-               ("prefix" in record.keys() and IP.isIpv6(record["prefix"])):
+            if ("via" in list(record.keys()) and IP.isIpv6(record["via"])) or \
+               ("prefix" in list(record.keys()) and IP.isIpv6(record["prefix"])):
                 to = to + "_v6"
             else:
                 to = to + "_v4"
@@ -748,7 +792,7 @@ def setNetworkRoute(self, network_id, to, record, state=None):
     def setNetworkPrefix(self, network_id, prefix, record, state=None):
         network_record = self.getNetwork(network_id, state)
         if network_record is not None:
-            if "prefix" not in network_record.keys():
+            if "prefix" not in list(network_record.keys()):
                 network_record["prefix"] = {}
             network_record["prefix"][prefix] = record
 
@@ -770,6 +814,14 @@ def setGlobalDNS(self, record, state=None):
         global_record = self.getGlobal(state)
         global_record["DNS"] = record
 
+    def renameNode(self, node_id, new_node_id, state=None):
+        nodes = self.getNodes(state)
+        if nodes is not None:
+            node_record = self.getNode(node_id, state)
+            if node_record is not None:
+                nodes[new_node_id] = node_record
+                self.removeNode(node_id)
+
     def removeLink(self, link_id, state=None):
         links = self.getLinks(state)
         if link_id in self.getLinkIds(state):
@@ -803,14 +855,14 @@ def removeNodeInterface(self, node_id, interface_id, state=None):
 
     def removeNodeTmux(self, node_id, session_id, state=None):
         node_record = self.getNode(node_id, state)
-        if "tmux" in node_record.keys():
-            if session_id in node_record["tmux"].keys():
+        if "tmux" in list(node_record.keys()):
+            if session_id in list(node_record["tmux"].keys()):
                 del node_record["tmux"][session_id]
 
     def removeNodeRoute(self, node_id, to, state=None):
         node_record = self.getNode(node_id, state)
-        if "route" in node_record.keys():
-            if to in node_record["route"].keys():
+        if "route" in list(node_record.keys()):
+            if to in list(node_record["route"].keys()):
                 del node_record["route"][to]
 
     def removeNodeInterfaceAddress(self, node_id, interface_id, ip_address, state=None):
@@ -825,29 +877,89 @@ def removeNetworkLink(self, network_id, link_id, state=None):
 
     def removeNetworkRoute(self, network_id, to, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "route" in network_record.keys():
-            if to in network_record["route"].keys():
+        if "route" in list(network_record.keys()):
+            if to in list(network_record["route"].keys()):
                 del network_record["route"][to]
 
     def removeNetworkPrefix(self, network_id, prefix, state=None):
         network_record = self.getNetwork(network_id, state)
-        if "prefix" in network_record.keys():
-            if prefix in network_record["prefix"].keys():
+        if "prefix" in list(network_record.keys()):
+            if prefix in list(network_record["prefix"].keys()):
                 del network_record["prefix"][prefix]
 
     def removeGlobalInternet(self, isp_id, state=None):
         global_record = self.getGlobal(state)
-        if "internet" in global_record.keys() and isp_id in global_record['internet'].keys():
+        if "internet" in list(global_record.keys()) and isp_id in list(global_record['internet'].keys()):
             del global_record["internet"][isp_id]
         if not bool(global_record['internet']):
             del global_record["internet"]
 
     def removeGlobalIsp(self, state=None):
         global_record = self.getGlobalIsp(state)
-        if "isp" in global_record.keys():
+        if "isp" in list(global_record.keys()):
             del global_record["isp"]
 
     def removeGlobalDNS(self, state=None):
         global_record = self.getGlobal(state)
-        if "DNS" in global_record.keys():
+        if "DNS" in list(global_record.keys()):
             del global_record["DNS"]
+
+    def getExtensionState(self, state=None):
+        """
+        return a list of states created by happy plugins,
+        "weave" is one of the extension created by happy plugins.
+        """
+        knownStateKeys = set(['identifiers', 'link', 'node', 'netns', 'network'])
+        state = self.getState(state)
+        filteredState = dict([i for i in six.iteritems(state) if i[0] not in knownStateKeys])
+        return filteredState
+
+    def getNodeInfo(self, node, state=None, happy_only=False):
+        """
+        get all information of a node
+        happy_only: only display happy states, not including extension states like weave
+        """
+        happy_node_info = self.getNodes()[node]
+        node_info = {"happy": happy_node_info}
+
+        # get extension state including weave
+        if not happy_only:
+            for i in six.iteritems(self.getExtensionState(state)):
+                extState = self.getNodes(i[1])
+                if extState and node in extState:
+                    node_info[i[0]] = extState[node]
+
+        return node_info
+
+    def getExtensionValue(self, extension_name, state=None):
+        """
+        extension_name example: weave-fabric-id
+        """
+        ext_look_list = extension_name.split("-")
+        ext_states = self.getExtensionState(state)
+        for item in ext_look_list:
+            if item not in ext_states:
+                return None
+            else:
+                ext_states = ext_states[item]
+                ext_value = ext_states
+        return ext_value
+
+    def IsTapDevice(self, node_id, state=None):
+        """
+        check if a device is a tap device or not
+        if tap device, will not call netns command to add route
+        1. get links of a node
+        2. verify link is TAP or not
+        Happy only support one interface type now,
+        it is either tap device or regular linux socket device
+        """
+        node_links = self.getNodeLinkIds(node_id)
+        # no link_id for cloud node service-tun0 interface
+        if None in node_links:
+            node_links.remove(None)
+        for link in node_links:
+            if self.getLinkTap(link):
+                return True
+        else:
+            return False
diff --git a/happy/Traceroute.py b/happy/Traceroute.py
index 92a70176..11948aec 100644
--- a/happy/Traceroute.py
+++ b/happy/Traceroute.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Implements Traceroute class that call traceroute to a virtual nodes.
 #
 
+from __future__ import absolute_import
 import os
 import sys
 
diff --git a/happy/Utils.py b/happy/Utils.py
index c0b16c5e..1ab0f01b 100644
--- a/happy/Utils.py
+++ b/happy/Utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -17,6 +17,7 @@
 #    limitations under the License.
 #
 
+from __future__ import absolute_import
 import time
 
 ##
diff --git a/happy/conf/log_config.json b/happy/conf/log_config.json
index 90a6dfb0..944740ae 100644
--- a/happy/conf/log_config.json
+++ b/happy/conf/log_config.json
@@ -14,8 +14,8 @@
             "level": "DEBUG",
             "class" : "logging.handlers.RotatingFileHandler",
             "formatter": "standard",
-            "filename":"%(log_directory)s/%(state_id)s_debug_log.txt",
-            "maxBytes": "5242880"
+            "filename":"%(happy_log_dir)s/%(state_id)s_debug_log.txt",
+            "maxBytes": 5242880
         },
         "stream": {
             "level": "INFO",
diff --git a/happy/conf/main_config.json b/happy/conf/main_config.json
index af32d8ac..a9659317 100644
--- a/happy/conf/main_config.json
+++ b/happy/conf/main_config.json
@@ -1,10 +1,11 @@
 {
     "configuration_file":"~/.happy_conf.json",
     "log_directory":"/tmp",
+    "default_happy_log_dir":"/tmp",
     "log_level_file": "DEBUG",
     "log_level_console": "INFO",
     "default_state":"happy",
-    "process_log_prefix":"%(log_directory)s/%(state_id)s_",
+    "process_log_prefix":"%(happy_log_dir)s/%(state_id)s_",
     "state_environ":"HAPPY_STATE_ID",
     "state_file_prefix":"~/.",
     "state_file_suffix":"_state.json",
diff --git a/happy/utils/IP.py b/happy/utils/IP.py
index 4f120160..e021b8e6 100644
--- a/happy/utils/IP.py
+++ b/happy/utils/IP.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -23,7 +23,9 @@
 #       Provides general IP formatting tools.
 #
 
+from __future__ import absolute_import
 import socket
+from six.moves import range
 
 
 class IP:
@@ -93,18 +95,125 @@ def dropZeros(addr):
 
     @staticmethod
     def prefixMatchAddress(prefix, addr):
+        """This function attempts to match the specified network prefix against the provided address.
+
+        Args:
+           prefix (str): A string containing the IPv4 or IPv6 prefix, in slash notation, to match against 'addr'.
+           addr (str): A string containing the IPv4 or IPv6 address against which 'prefix' will be matched.
+
+        Returns:
+           bool. The return code::
+
+              True  -- The prefix matches the address.
+              False -- The prefix does not match the address.
+        """
+
+        # If there is no prefix, then the match can never succeed.
+
         if prefix is None:
             return False
 
+        # Split the address into prefix and mask portions.
+        
         aprefix, amask = IP.splitAddressMask(prefix)
-        aprefix = IP.dropZeros(aprefix)
 
-        if len(aprefix) > len(addr):
+        # Ensure like address families are being compared between the
+        # prefix and address. If not, the match can never succeed.
+
+        if ((IP.isIpv4(aprefix) and not IP.isIpv4(addr)) or
+            (IP.isIpv6(aprefix) and not IP.isIpv6(addr))):
             return False
 
-        plen = len(aprefix)
+        # Perform address family-specific matching.
+
+        if (IP.isIpv6(aprefix)):
+            return IP.__ipv6PrefixMatchAddress(aprefix, amask, addr)
+        elif (IP.isIpv4(aprefix)):
+            return IP.__ipv4PrefixMatchAddress(aprefix, amask, addr)
+        else:
+            return False
+
+
+    @staticmethod
+    def __ipv4PrefixMatchAddress(prefix, mask, addr):
+        # First, canonicalize the address and prefix
+
+        prefix = IP.__ipv4Canonicalize(prefix)
+
+        addr = IP.__ipv4Canonicalize(addr)
+
+        # Then, to check for a match, convert the prefix, mask, and
+        # address into binary values and check that the logical and of
+        # the prefix with the mask matches the logical and of the
+        # address and the mask.
+
+        bmask = IP.__ipv4MaskStringToBinary(mask)
+
+        bprefix = IP.__ipv4AddressStringToBinary(prefix)
+
+        baddr = IP.__ipv4AddressStringToBinary(addr)
+
+        matched = ((bprefix & bmask) == (baddr & bmask))
+
+        return matched
+
+
+    @staticmethod
+    def __ipv6PrefixMatchAddress(prefix, mask, addr):
+        prefix = IP.dropZeros(prefix)
+
+        if len(prefix) > len(addr):
+            return False
+
+        plen = len(prefix)
+
+        return prefix[:plen] == addr[:plen]
+
+
+    @staticmethod
+    def __ipv4Canonicalize(addr):
+        """This function attempts to canonicalize an IPv4 address (four completely-specified octets) or prefix (less than four octets) into a four octet, dotted decimal string representation by appending the appropriate number of zeroes (.0) to the address. Note, an empty string is canonicalized into the any address (0.0.0.0).
+
+        Args:
+           addr (str): A string containing the IPv4 address or prefix to canonicalize
+
+        Returns:
+           str. The canonicalized IPv4 address or prefix.
+        """
+        
+        if len(addr) > 0:
+            octets = addr.count(".") + 1
+        else:
+            octets = 0
+
+        missing = 4 - octets
+
+        if missing > 0:
+            padding = ".".join(["0"] * missing)
+            if octets > 0:
+                addr += "." + padding
+            else:
+                addr += padding
+
+        return addr
+
+
+    @staticmethod
+    def __ipv4AddressStringToBinary(addr):
+        baddr = 0
+        
+        for index, octet in list(enumerate(reversed(addr.split(".")))):
+            baddr = baddr + ((int(octet) & 0xFF) << (index * 8))
+
+        return baddr
+
+
+    @staticmethod
+    def __ipv4MaskStringToBinary(mask):
+        bmask = ((2**32) - 1) ^ ((2**32) - 1) >> int(mask)
+
+        return bmask
 
-        return aprefix[:plen] == addr[:plen]
 
     @staticmethod
     def getPrefix(addr, mask=None):
@@ -117,13 +226,13 @@ def getPrefix(addr, mask=None):
         if ":" in addr:
             if mask is None:
                 mask = 64
-            addr_list = addr.split(":")[:mask/16]
+            addr_list = addr.split(":")[:mask//16]
             return ":".join(addr_list)
 
         if "." in addr:
             if mask is None:
                 mask = 24
-            addr_list = addr.split(".")[:mask/8]
+            addr_list = addr.split(".")[:mask//8]
             return ".".join(addr_list)
 
     @staticmethod
@@ -185,9 +294,9 @@ def EUI64toIID(addr):
 
         if int_addr >= 65536:
             if (1 << 57) > int_addr:
-                int_addr = (1 << 57) | int_addr
+                int_addr = (1 << 57) ^ int_addr
             else:
-                int_addr = int_addr | (1 << 57)
+                int_addr = int_addr ^ (1 << 57)
 
         if type(addr) == str:
             iid_addr = IP.int_to_ipv6_addr_string(int_addr)
@@ -201,7 +310,7 @@ def IIDtoEUI64(addr):
         if type(addr) == str:
             eui_addr = IP.ipv6_addr_string_to_int(addr)
 
-        eui_addr = ~pow(2, 57) & eui_addr
+        eui_addr = pow(2, 57) ^ eui_addr
 
         if type(addr) == str:
             eui_addr = IP.int_to_eui64_string(eui_addr)
diff --git a/happy/version.py b/happy/version.py
index 968065fd..d87c72f4 100644
--- a/happy/version.py
+++ b/happy/version.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -17,4 +17,4 @@
 #    limitations under the License.
 #
 
-__version__ = '1.1.36'
+__version__ = '1.1.37'
diff --git a/pip_packages.py b/pip_packages.py
index abefd85c..1b3208b3 100644
--- a/pip_packages.py
+++ b/pip_packages.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2017 Nest Labs, Inc.
@@ -21,6 +21,7 @@
 #    @file
 #       install all necessary python modules for all weave happy tests
 #
+from __future__ import absolute_import
 import subprocess
 import sys
 import pip
@@ -33,6 +34,7 @@ def pip_install(package):
 # we should == to make sure version for the above modules is working
 required_packages = [
     'requests==2.9.1',
+    'pexpect==4.6.0',
     'protobuf==3.0.0b2',
     'googleapis-common-protos==1.1.0',
     'grpcio-tools==0.14.0',
@@ -42,7 +44,7 @@ def pip_install(package):
     'lockfile==0.12.2',
     'grpcio==1.3.5',
     'psutil==1.2.1',
-    'M2Crypto==0.30.1']
+    'wheel==0.34.2']
 
 for package in required_packages:
     pip_install(package)
diff --git a/plugins/plaid/Plaid.py b/plugins/plaid/Plaid.py
index 088fed26..5c38a64a 100644
--- a/plugins/plaid/Plaid.py
+++ b/plugins/plaid/Plaid.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -23,6 +23,8 @@
 #       framework in Happy tests
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import os
 import sys
 import glob
@@ -61,7 +63,7 @@ def default_options():
 
 def deletePlaidNetwork():
 
-    print "Deleting the plaid network"
+    print("Deleting the plaid network")
 
     node_delete_options = happy.HappyNodeDelete.option()
     node_delete_options["node_id"] = plaid_server_node_id
@@ -76,10 +78,10 @@ def deletePlaidNetwork():
     network_delete_cmd.run()
 
     state = happy.State.State()
-    print "state.isStateEmpty(): " + str(state.isStateEmpty())
+    print("state.isStateEmpty(): " + str(state.isStateEmpty()))
 
     if state.isStateEmpty():
-        print "The happy state is now empty, deleting the file"
+        print("The happy state is now empty, deleting the file")
         state_delete_options = happy.HappyStateDelete.option()
         state_delete_options["quiet"] = True
         state_delete_cmd = happy.HappyStateDelete.HappyStateDelete(state_delete_options)
@@ -117,7 +119,7 @@ def __check_installed_version_of_plaid(self):
 
     def __get_plaid_happy_conf_path(self):
         plaid_happy_conf_path = None
-        if "plaid_path" in self.configuration.keys():
+        if "plaid_path" in list(self.configuration.keys()):
             plaid_happy_conf_path = self.configuration["plaid_path"]
             emsg = "Found plaid path: %s." % (plaid_happy_conf_path)
             self.logger.debug("[localhost] Plaid: %s" % (emsg))
@@ -180,7 +182,7 @@ def __create_plaid_network(self):
 
     def isPlaidConfigured(self):
         # Plaid is not yet supported on lwip
-        running_on_lwip = ("WEAVE_SYSTEM_CONFIG_USE_LWIP" in os.environ.keys() and
+        running_on_lwip = ("WEAVE_SYSTEM_CONFIG_USE_LWIP" in list(os.environ.keys()) and
                            os.environ["WEAVE_SYSTEM_CONFIG_USE_LWIP"] == "1")
         return self.plaid_path is not None and not running_on_lwip
 
diff --git a/plugins/testrail/TestrailResultOutput.py b/plugins/testrail/TestrailResultOutput.py
index ac525cab..3c358764 100644
--- a/plugins/testrail/TestrailResultOutput.py
+++ b/plugins/testrail/TestrailResultOutput.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -21,6 +21,8 @@
 #    @file
 #       Implements TestrailResultOutput class that output the Weave standalone test result to json file for posting testrail
 #
+from __future__ import absolute_import
+from __future__ import print_function
 import json
 import os
 from happy.Utils import *
@@ -47,14 +49,14 @@ def __pre_check(self):
             emsg = "Missing output Weave test results for testrail."
             self.logger.error("[localhost] TestrailResultOutput: %s" % (emsg))
             if not self.quiet:
-                print hred(emsg)
+                print(hred(emsg))
             self.RaiseError(emsg)
 
         if not self.file_path:
             emsg = "Missing file name of testrail results."
             self.logger.error("[%s] TestrailResultOutput: %s" % (emsg))
             if not self.quiet:
-                print hred(emsg)
+                print(hred(emsg))
             self.RaiseError(emsg)
 
     def __results_output(self):
@@ -62,8 +64,8 @@ def __results_output(self):
         try:
             json_data = json.dumps(self.output_data, sort_keys=True, indent=4)
         except Exception:
-            print "Failed to save testrail json file: %s" % \
-                (self.file_path)
+            print("Failed to save testrail json file: %s" % \
+                (self.file_path))
             self.logger.error("calls sys.exit(1)")
             self.exit()
 
diff --git a/setup.py b/setup.py
index 5a574603..892a5d58 100644
--- a/setup.py
+++ b/setup.py
@@ -14,6 +14,7 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 #
+from __future__ import absolute_import
 import os
 from setuptools import find_packages, setup
 
diff --git a/tests/happy/test_01_happy_node_shell.py b/tests/happy/test_01_happy_node_shell.py
index 92a8dae2..f2e27afe 100755
--- a/tests/happy/test_01_happy_node_shell.py
+++ b/tests/happy/test_01_happy_node_shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy for creating, deleting, and listing nodes.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_02_happy_node_module.py b/tests/happy/test_02_happy_node_module.py
index b1e17320..c12a08bc 100755
--- a/tests/happy/test_02_happy_node_module.py
+++ b/tests/happy/test_02_happy_node_module.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy for creating, deleting, and listing nodes.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_03_happy_network_shell.py b/tests/happy/test_03_happy_network_shell.py
index 1926c115..63dda413 100755
--- a/tests/happy/test_03_happy_network_shell.py
+++ b/tests/happy/test_03_happy_network_shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy for creating, deleting, and listing networks.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_04_happy_link_module.py b/tests/happy/test_04_happy_link_module.py
index 9480b088..f85b81cd 100755
--- a/tests/happy/test_04_happy_link_module.py
+++ b/tests/happy/test_04_happy_link_module.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy for creating, deleting, and listing nodes.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_05_happy_link_shell.py b/tests/happy/test_05_happy_link_shell.py
index f264725e..6f389f06 100755
--- a/tests/happy/test_05_happy_link_shell.py
+++ b/tests/happy/test_05_happy_link_shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy node joining and leaving a network.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_06_happy_network_module.py b/tests/happy/test_06_happy_network_module.py
index f2ef0ab8..4d410e60 100755
--- a/tests/happy/test_06_happy_network_module.py
+++ b/tests/happy/test_06_happy_network_module.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy for creating, deleting, and listing networks.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_07_happy_node_network_shell.py b/tests/happy/test_07_happy_node_network_shell.py
index b59e3aa0..f2b67921 100755
--- a/tests/happy/test_07_happy_node_network_shell.py
+++ b/tests/happy/test_07_happy_node_network_shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy node joining and leaving a network.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_08_happy_node_network_module.py b/tests/happy/test_08_happy_node_network_module.py
index 57fffb6d..dabf82d2 100755
--- a/tests/happy/test_08_happy_node_network_module.py
+++ b/tests/happy/test_08_happy_node_network_module.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy node joining and leaving a network.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import unittest
diff --git a/tests/happy/test_11_happy_process_shell.py b/tests/happy/test_11_happy_process_shell.py
index ecb485e0..5c2e9ca0 100755
--- a/tests/happy/test_11_happy_process_shell.py
+++ b/tests/happy/test_11_happy_process_shell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2016-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Tests happy process starting, stopping, waiting, getting output.
 #
 
+from __future__ import absolute_import
 import pexpect
 import os
 import time
diff --git a/tests/ip/ping/test_ip_ping_01.py b/tests/ip/ping/test_ip_ping_01.py
index ce13ee5d..9c6c20d2 100644
--- a/tests/ip/ping/test_ip_ping_01.py
+++ b/tests/ip/ping/test_ip_ping_01.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Calls ping between nodes.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import itertools
 import os
 import unittest
@@ -84,12 +86,12 @@ def test_ip_ping_among_all_nodes(self):
         for pair in pairs_of_nodes:
             value, data = self.__send_ping_between(pair[0], pair[1])
 
-            print "ping from " + pair[0] + " to " + pair[1] + " ",
+            print("ping from " + pair[0] + " to " + pair[1] + " ", end=' ')
 
             if value > 11:
-                print hred("Failed")
+                print(hred("Failed"))
             else:
-                print hgreen("Passed")
+                print(hgreen("Passed"))
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/ip/traceroute/test_ip_traceroute_01.py b/tests/ip/traceroute/test_ip_traceroute_01.py
index 5391a19f..eb92f02d 100644
--- a/tests/ip/traceroute/test_ip_traceroute_01.py
+++ b/tests/ip/traceroute/test_ip_traceroute_01.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,7 @@
 #       Calls traceroute between nodes.
 #
 
+from __future__ import absolute_import
 import os
 import unittest
 
diff --git a/tests/ip/traceroute/test_ip_traceroute_02.py b/tests/ip/traceroute/test_ip_traceroute_02.py
index 9826760a..5d5e60df 100644
--- a/tests/ip/traceroute/test_ip_traceroute_02.py
+++ b/tests/ip/traceroute/test_ip_traceroute_02.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 #
 #    Copyright (c) 2015-2017 Nest Labs, Inc.
@@ -22,6 +22,8 @@
 #       Calls traceroute between nodes.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import itertools
 import os
 import unittest
@@ -82,12 +84,12 @@ def test_ip_traceroute_among_all_nodes(self):
         for pair in pairs_of_nodes:
             value, data = self.__send_traceroute_between(pair[0], pair[1])
 
-            print "traceroute from " + pair[0] + " to " + pair[1] + " ",
+            print("traceroute from " + pair[0] + " to " + pair[1] + " ", end=' ')
 
             if value < 1:
-                print hred("Failed")
+                print(hred("Failed"))
             else:
-                print hgreen("Passed")
+                print(hgreen("Passed"))
 
 if __name__ == "__main__":
     unittest.main()
