Merge "Add INFO.yaml"
authorCristina Pauna <cristina.pauna@enea.com>
Thu, 2 Apr 2020 09:08:47 +0000 (09:08 +0000)
committerGerrit Code Review <gerrit@akraino.org>
Thu, 2 Apr 2020 09:08:47 +0000 (09:08 +0000)
175 files changed:
.gitmodules
misc/type1/macbin/defconfig-mcbin-edge
src/foundation/api/revel/.codebeatsettings [new file with mode: 0644]
src/foundation/api/revel/.travis.yml [new file with mode: 0644]
src/foundation/api/revel/AUTHORS [new file with mode: 0644]
src/foundation/api/revel/CHANGELOG.md [new file with mode: 0644]
src/foundation/api/revel/CONTRIBUTING.md [new file with mode: 0644]
src/foundation/api/revel/LICENSE [new file with mode: 0644]
src/foundation/api/revel/README.md [new file with mode: 0644]
src/foundation/api/revel/before_after_filter.go [new file with mode: 0644]
src/foundation/api/revel/binder.go [new file with mode: 0644]
src/foundation/api/revel/binder_test.go [new file with mode: 0644]
src/foundation/api/revel/cache/cache.go [new file with mode: 0644]
src/foundation/api/revel/cache/cache_test.go [new file with mode: 0644]
src/foundation/api/revel/cache/init.go [new file with mode: 0644]
src/foundation/api/revel/cache/inmemory.go [new file with mode: 0644]
src/foundation/api/revel/cache/inmemory_test.go [new file with mode: 0644]
src/foundation/api/revel/cache/memcached.go [new file with mode: 0644]
src/foundation/api/revel/cache/memcached_test.go [new file with mode: 0644]
src/foundation/api/revel/cache/redis.go [new file with mode: 0644]
src/foundation/api/revel/cache/redis_test.go [new file with mode: 0644]
src/foundation/api/revel/cache/serialization.go [new file with mode: 0644]
src/foundation/api/revel/cache/serialization_test.go [new file with mode: 0644]
src/foundation/api/revel/compress.go [new file with mode: 0644]
src/foundation/api/revel/compress_test.go [new file with mode: 0644]
src/foundation/api/revel/conf/mime-types.conf [new file with mode: 0644]
src/foundation/api/revel/controller.go [new file with mode: 0644]
src/foundation/api/revel/controller_type.go [new file with mode: 0644]
src/foundation/api/revel/errors.go [new file with mode: 0644]
src/foundation/api/revel/event.go [new file with mode: 0644]
src/foundation/api/revel/event_test.go [new file with mode: 0644]
src/foundation/api/revel/fakeapp_test.go [new file with mode: 0644]
src/foundation/api/revel/field.go [new file with mode: 0644]
src/foundation/api/revel/filter.go [new file with mode: 0644]
src/foundation/api/revel/filterconfig.go [new file with mode: 0644]
src/foundation/api/revel/filterconfig_test.go [new file with mode: 0644]
src/foundation/api/revel/flash.go [new file with mode: 0644]
src/foundation/api/revel/http.go [new file with mode: 0644]
src/foundation/api/revel/i18n.go [new file with mode: 0644]
src/foundation/api/revel/i18n_test.go [new file with mode: 0644]
src/foundation/api/revel/intercept.go [new file with mode: 0644]
src/foundation/api/revel/intercept_test.go [new file with mode: 0644]
src/foundation/api/revel/invoker.go [new file with mode: 0644]
src/foundation/api/revel/invoker_test.go [new file with mode: 0644]
src/foundation/api/revel/libs.go [new file with mode: 0644]
src/foundation/api/revel/libs_test.go [new file with mode: 0644]
src/foundation/api/revel/logger.go [new file with mode: 0644]
src/foundation/api/revel/logger/composite_multihandler.go [new file with mode: 0644]
src/foundation/api/revel/logger/doc.go [new file with mode: 0644]
src/foundation/api/revel/logger/handlers.go [new file with mode: 0644]
src/foundation/api/revel/logger/init.go [new file with mode: 0644]
src/foundation/api/revel/logger/init_test.go [new file with mode: 0644]
src/foundation/api/revel/logger/log_function_map.go [new file with mode: 0644]
src/foundation/api/revel/logger/logger.go [new file with mode: 0644]
src/foundation/api/revel/logger/revel_logger.go [new file with mode: 0644]
src/foundation/api/revel/logger/terminal_format.go [new file with mode: 0644]
src/foundation/api/revel/logger/utils.go [new file with mode: 0644]
src/foundation/api/revel/logger/wrap_handlers.go [new file with mode: 0644]
src/foundation/api/revel/model/revel_container.go [new file with mode: 0644]
src/foundation/api/revel/module.go [new file with mode: 0644]
src/foundation/api/revel/namespace.go [new file with mode: 0644]
src/foundation/api/revel/panic.go [new file with mode: 0644]
src/foundation/api/revel/params.go [new file with mode: 0644]
src/foundation/api/revel/params_test.go [new file with mode: 0644]
src/foundation/api/revel/results.go [new file with mode: 0644]
src/foundation/api/revel/results_test.go [new file with mode: 0644]
src/foundation/api/revel/revel.go [new file with mode: 0644]
src/foundation/api/revel/revel_hooks.go [new file with mode: 0644]
src/foundation/api/revel/revel_test.go [new file with mode: 0644]
src/foundation/api/revel/router.go [new file with mode: 0644]
src/foundation/api/revel/router_test.go [new file with mode: 0644]
src/foundation/api/revel/server-engine.go [new file with mode: 0644]
src/foundation/api/revel/server.go [new file with mode: 0644]
src/foundation/api/revel/server_adapter_go.go [new file with mode: 0644]
src/foundation/api/revel/server_test.go [new file with mode: 0644]
src/foundation/api/revel/session/init.go [new file with mode: 0644]
src/foundation/api/revel/session/session.go [new file with mode: 0644]
src/foundation/api/revel/session/session_cookie_test.go [new file with mode: 0644]
src/foundation/api/revel/session/session_test.go [new file with mode: 0644]
src/foundation/api/revel/session_adapter_cookie.go [new file with mode: 0644]
src/foundation/api/revel/session_engine.go [new file with mode: 0644]
src/foundation/api/revel/session_filter.go [new file with mode: 0644]
src/foundation/api/revel/template.go [new file with mode: 0644]
src/foundation/api/revel/template_adapter_go.go [new file with mode: 0644]
src/foundation/api/revel/template_engine.go [new file with mode: 0644]
src/foundation/api/revel/template_functions.go [new file with mode: 0644]
src/foundation/api/revel/templates/errors/403.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/403.json [new file with mode: 0644]
src/foundation/api/revel/templates/errors/403.txt [new file with mode: 0644]
src/foundation/api/revel/templates/errors/403.xml [new file with mode: 0644]
src/foundation/api/revel/templates/errors/404-dev.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/404.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/404.json [new file with mode: 0644]
src/foundation/api/revel/templates/errors/404.txt [new file with mode: 0644]
src/foundation/api/revel/templates/errors/404.xml [new file with mode: 0644]
src/foundation/api/revel/templates/errors/405.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/405.json [new file with mode: 0644]
src/foundation/api/revel/templates/errors/405.txt [new file with mode: 0644]
src/foundation/api/revel/templates/errors/405.xml [new file with mode: 0644]
src/foundation/api/revel/templates/errors/500-dev.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/500.html [new file with mode: 0644]
src/foundation/api/revel/templates/errors/500.json [new file with mode: 0644]
src/foundation/api/revel/templates/errors/500.txt [new file with mode: 0644]
src/foundation/api/revel/templates/errors/500.xml [new file with mode: 0644]
src/foundation/api/revel/testdata/app/views/footer.html [new file with mode: 0644]
src/foundation/api/revel/testdata/app/views/header.html [new file with mode: 0644]
src/foundation/api/revel/testdata/app/views/hotels/show.html [new file with mode: 0644]
src/foundation/api/revel/testdata/conf/app.conf [new file with mode: 0644]
src/foundation/api/revel/testdata/conf/routes [new file with mode: 0644]
src/foundation/api/revel/testdata/i18n/config/test_app.conf [new file with mode: 0644]
src/foundation/api/revel/testdata/i18n/messages/dutch_messages.nl [new file with mode: 0644]
src/foundation/api/revel/testdata/i18n/messages/english_messages.en [new file with mode: 0644]
src/foundation/api/revel/testdata/i18n/messages/english_messages2.en [new file with mode: 0644]
src/foundation/api/revel/testdata/i18n/messages/invalid_message_file_name.txt [new file with mode: 0644]
src/foundation/api/revel/testdata/public/js/sessvars.js [new file with mode: 0644]
src/foundation/api/revel/testing/testsuite.go [new file with mode: 0644]
src/foundation/api/revel/testing/testsuite_test.go [new file with mode: 0644]
src/foundation/api/revel/util.go [new file with mode: 0644]
src/foundation/api/revel/util_test.go [new file with mode: 0644]
src/foundation/api/revel/utils/simplestack.go [new file with mode: 0644]
src/foundation/api/revel/utils/simplestack_test.go [new file with mode: 0644]
src/foundation/api/revel/validation.go [new file with mode: 0644]
src/foundation/api/revel/validation_test.go [new file with mode: 0644]
src/foundation/api/revel/validators.go [new file with mode: 0644]
src/foundation/api/revel/validators_test.go [new file with mode: 0644]
src/foundation/api/revel/version.go [new file with mode: 0644]
src/foundation/api/revel/watcher.go [new file with mode: 0644]
src/foundation/scripts/cni/cilium/cilium_install.sh [new file with mode: 0755]
src/foundation/scripts/cni/cilium/quick-install.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/README.md [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset-k8s-v1.16.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/configMap.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/install-k8s-v1.16.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/multus-sriov-calico/install.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets-k8s-v1.16.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/sriov-crd.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall-k8s-v1.16.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/multus-sriov-flannel/configMap.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-flannel/flannel-daemonset.yml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-flannel/install.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/multus-sriov-flannel/multus-sriov-flannel-daemonsets.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-flannel/sriov-crd.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/multus-sriov-flannel/uninstall.sh [new file with mode: 0755]
src/foundation/scripts/cni/multus/use-cases/Dockerfile.iperf2 [new file with mode: 0644]
src/foundation/scripts/cni/multus/use-cases/iperfv2-client-sriov.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/use-cases/iperfv2-server-sriov.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/use-cases/pod1.yaml [new file with mode: 0644]
src/foundation/scripts/cni/multus/use-cases/pod2.yaml [new file with mode: 0644]
src/foundation/scripts/config
src/foundation/scripts/setup-cni.sh
src/foundation/scripts/startup.sh
src/foundation/scripts/uninstall.sh [changed mode: 0644->0755]
src/foundation/service_mesh/Istio/init/crd-10.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/init/crd-11.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/init/crd-12.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/init/crd-certmanager-10.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/init/crd-certmanager-11.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/istio-demo-arm64.yaml [new file with mode: 0644]
src/foundation/service_mesh/Istio/istio-inject-configmap-1.1.7.yaml [new file with mode: 0644]
src/foundation/service_mesh/README.md [new file with mode: 0644]
src/foundation/service_mesh/install_Istio.sh [new file with mode: 0755]
src/type3_AndroidCloud/README.rst [new file with mode: 0644]
src/type5_SmartNIC/README.rst [new file with mode: 0644]
src/use_cases/seba_on_arm/src_repo/helm-charts
src/use_cases/seba_on_arm/test/ponsim/install.sh
src/use_cases/service_mesh/bookinfo/README.md [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/config/bookinfo-gateway.yaml [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/config/bookinfo.yaml [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/config/destination-rule-all.yaml [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-80-20.yaml [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-v3.yaml [new file with mode: 0644]
src/use_cases/service_mesh/bookinfo/install/install.sh [new file with mode: 0755]

index 191a8df..bf1ebb3 100644 (file)
 [submodule "helm-charts"]
        path = src/use_cases/seba_on_arm/src_repo/helm-charts
        url = https://github.com/iecedge/helm-charts.git
-       branch = ponsim_3node_arm64
+       branch = cord-7.0-arm64
index 2d1646e..0efca97 100644 (file)
@@ -25,10 +25,12 @@ CONFIG_CPUSETS=y
 CONFIG_CGROUP_DEVICE=y
 CONFIG_CGROUP_CPUACCT=y
 CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
 CONFIG_USER_NS=y
 CONFIG_SCHED_AUTOGROUP=y
 CONFIG_BLK_DEV_INITRD=y
 CONFIG_KALLSYMS_ALL=y
+CONFIG_BPF_JIT_ALWAYS_ON=y
 # CONFIG_COMPAT_BRK is not set
 CONFIG_PROFILING=y
 CONFIG_JUMP_LABEL=y
@@ -78,10 +80,13 @@ CONFIG_XFRM_USER=y
 CONFIG_XFRM_SUB_POLICY=y
 CONFIG_INET=y
 CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
 CONFIG_IP_PNP=y
 CONFIG_IP_PNP_DHCP=y
 CONFIG_IP_PNP_BOOTP=y
 CONFIG_NET_IPIP=y
+CONFIG_IPV6_ILA=y
 # CONFIG_INET6_XFRM_MODE_TRANSPORT is not set
 # CONFIG_INET6_XFRM_MODE_TUNNEL is not set
 # CONFIG_INET6_XFRM_MODE_BEET is not set
@@ -124,13 +129,16 @@ CONFIG_NFT_QUOTA=y
 CONFIG_NFT_REJECT=y
 CONFIG_NFT_HASH=y
 CONFIG_NETFILTER_XT_SET=y
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y
 CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
 CONFIG_NETFILTER_XT_TARGET_LOG=y
 CONFIG_NETFILTER_XT_TARGET_MARK=y
 CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
 CONFIG_NETFILTER_XT_TARGET_NOTRACK=y
+CONFIG_NETFILTER_XT_TARGET_TPROXY=y
 CONFIG_NETFILTER_XT_TARGET_TCPMSS=y
 CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
+CONFIG_NETFILTER_XT_MATCH_BPF=m
 CONFIG_NETFILTER_XT_MATCH_COMMENT=y
 CONFIG_NETFILTER_XT_MATCH_CONNBYTES=y
 CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
@@ -152,6 +160,7 @@ CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
 CONFIG_NETFILTER_XT_MATCH_REALM=y
 CONFIG_NETFILTER_XT_MATCH_RECENT=y
 CONFIG_NETFILTER_XT_MATCH_SCTP=y
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
 CONFIG_NETFILTER_XT_MATCH_STATE=y
 CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
 CONFIG_NETFILTER_XT_MATCH_STRING=y
@@ -217,6 +226,7 @@ CONFIG_IP_NF_RAW=y
 CONFIG_IP_NF_ARPTABLES=y
 CONFIG_IP_NF_ARPFILTER=y
 CONFIG_IP_NF_ARP_MANGLE=y
+CONFIG_NF_SOCKET_IPV6=m
 CONFIG_IP6_NF_IPTABLES=y
 CONFIG_BRIDGE=y
 CONFIG_BRIDGE_VLAN_FILTERING=y
@@ -225,7 +235,11 @@ CONFIG_VLAN_8021Q=y
 CONFIG_VLAN_8021Q_GVRP=y
 CONFIG_VLAN_8021Q_MVRP=y
 CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_INGRESS=y
 CONFIG_NET_CLS_CGROUP=y
+CONFIG_NET_CLS_BPF=y
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_BPF=y
 CONFIG_VSOCKETS=y
 CONFIG_VIRTIO_VSOCKETS=y
 CONFIG_NETLINK_DIAG=y
@@ -234,6 +248,8 @@ CONFIG_NET_MPLS_GSO=y
 CONFIG_NET_L3_MASTER_DEV=y
 CONFIG_CGROUP_NET_PRIO=y
 CONFIG_BPF_JIT=y
+CONFIG_BPF_STREAM_PARSER=y
+CONFIG_AF_KCM=y
 CONFIG_RFKILL=y
 CONFIG_NET_9P=y
 CONFIG_NET_9P_VIRTIO=y
@@ -555,12 +571,10 @@ CONFIG_VHOST_NET=y
 CONFIG_VHOST_VSOCK=y
 CONFIG_PRINTK_TIME=y
 CONFIG_DEBUG_INFO=y
-CONFIG_DEBUG_FS=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_DEBUG_KERNEL=y
 # CONFIG_SCHED_DEBUG is not set
 # CONFIG_DEBUG_PREEMPT is not set
-# CONFIG_FTRACE is not set
 CONFIG_MEMTEST=y
 CONFIG_CORESIGHT=y
 CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
diff --git a/src/foundation/api/revel/.codebeatsettings b/src/foundation/api/revel/.codebeatsettings
new file mode 100644 (file)
index 0000000..f921ed8
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "GOLANG": {
+  "ABC":[15, 25, 50, 70],
+  "BLOCK_NESTING":[5, 6, 7, 8],
+  "CYCLO":[20, 30, 45, 60],
+    "TOO_MANY_IVARS": [15, 18, 20, 25],
+    "TOO_MANY_FUNCTIONS": [20, 30, 40, 50],
+    "TOTAL_COMPLEXITY": [150, 250, 400, 500],
+    "LOC": [50, 75, 90, 120]
+  }
+}
\ No newline at end of file
diff --git a/src/foundation/api/revel/.travis.yml b/src/foundation/api/revel/.travis.yml
new file mode 100644 (file)
index 0000000..2c81536
--- /dev/null
@@ -0,0 +1,67 @@
+language: go
+
+go:
+  - "1.8.x"
+  - "1.9.x"
+  - "1.10.x"
+  - "1.11.x"
+  - "tip"
+
+os:
+  - linux
+  - osx
+  - windows
+
+sudo: false
+
+branches:
+  only:
+    - master
+    - develop
+
+services:
+  # github.com/revel/revel/cache
+  - memcache
+  - redis-server
+
+before_install:
+  # TRAVIS_OS_NAME - linux and osx
+  - echo $TRAVIS_OS_NAME
+  - echo $PATH
+  - |
+    if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+      brew update && brew install memcached redis && brew services start redis && brew services start memcached
+    fi
+  - |
+    if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then
+      redis-server --daemonize yes
+      redis-cli info
+    else
+      # redis-server.exe
+      # redis-cli.exe info
+      echo $PATH
+    fi
+
+install:
+  # Setting environments variables
+  - export PATH=$PATH:$HOME/gopath/bin
+  - export REVEL_BRANCH="develop"
+  - 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi'
+  - 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"'
+  - git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/
+  - git clone -b $REVEL_BRANCH git://github.com/revel/cmd ../cmd/
+  - git clone -b $REVEL_BRANCH git://github.com/revel/config ../config/
+  - git clone -b $REVEL_BRANCH git://github.com/revel/cron ../cron/
+  - git clone -b $REVEL_BRANCH git://github.com/revel/examples ../examples/
+  - go get -t -v github.com/revel/revel/...
+  - go get -t -v github.com/revel/cmd/revel
+
+script:
+  - go test -v github.com/revel/revel/...
+
+matrix:
+  allow_failures:
+    - go: tip
+    - os: windows
+    - go: 1.6
+      os: osx
diff --git a/src/foundation/api/revel/AUTHORS b/src/foundation/api/revel/AUTHORS
new file mode 100644 (file)
index 0000000..d265544
--- /dev/null
@@ -0,0 +1 @@
+# TODO Revel Framework Authors Information
diff --git a/src/foundation/api/revel/CHANGELOG.md b/src/foundation/api/revel/CHANGELOG.md
new file mode 100644 (file)
index 0000000..5273460
--- /dev/null
@@ -0,0 +1,623 @@
+# CHANGELOG
+
+## v0.19.0
+# Release 0.19.0
+
+# Maintenance Release
+
+This release is focused on improving the security and resolving some issues. 
+
+**There are no breaking changes from version 0.18**
+
+[[revel/cmd](https://github.com/revel/cmd)]
+* Improved vendor folder detection revel/cmd#117
+* Added ordering of controllers so order remains consistent in main.go revel/cmd#112
+* Generate same value of `AppVersion` regardless of where Revel is run revel/cmd#108
+* Added referrer policy security header revel/cmd#114
+
+[[revel/modules](https://github.com/revel/modules)]
+* Added directory representation to static module revel/modules#46
+* Gorp enhancements (added abstraction layer for transactions and database connection so both can be used), Added security fix for CSRF module revel/modules#68
+* Added authorization configuration options to job page revel/modules#44
+
+[[revel/examples](https://github.com/revel/examples)]
+* General improvements and examples added revel/examples#39  revel/examples#40
+
+## v0.18
+# Release 0.18
+
+## Upgrade path
+The main breaking change is the removal of `http.Request` from the `revel.Request` object.
+Everything else should just work....
+
+## New items
+* Server Engine revel/revel#998
+The server engine implementation is described in the [docs](http://revel.github.io/manual/server-engine.html)
+* Allow binding to a structured map. revel/revel#998 
+Have a structure inside a map object which will be realized properly from params
+* Gorm module revel/modules/#51
+Added transaction controller
+* Gorp module revel/modules/#52
+* Autorun on startup in develop mode revel/cmd#95
+Start the application without doing a request first using revel run ....
+* Logger update revel/revel#1213
+Configurable logger and added context logging on controller via controller.Log
+* Before after finally panic controller method detection revel/revel#1211 
+Controller methods will be automatically detected and called - similar to interceptors but without the extra code
+* Float validation revel/revel#1209
+Added validation for floats
+* Timeago template function revel/revel#1207
+Added timeago function to Revel template functions
+* Authorization to jobs module revel/module#44
+Added ability to specify authorization to access the jobs module routes
+* Add MessageKey, ErrorKey methods to ValidationResult object revel/revel#1215
+This allows the message translator to translate the keys added. So model objects can send out validation codes
+* Vendor friendlier - Revel recognizes and uses `deps` (to checkout go libraries) if a vendor folder exists in the project root. 
+* Updated examples to use Gorp modules and new loggers
+
+
+### Breaking Changes
+
+* `http.Request` is no longer contained in `revel.Request` revel.Request remains functionally the same but 
+you cannot extract the `http.Request` from it. You can get the `http.Request` from `revel.Controller.Request.In.GetRaw().(*http.Request)`
+* `http.Response.Out` Is not the http.Response and is deprecated, you can get the output writer by doing `http.Response.GetWriter()`. You can get the `http.Response` from revel.Controller.Response.Out.Server.GetRaw().(*http.Response)`
+
+* `Websocket` changes. `revel.ServerWebsocket` is the new type of object you need to declare for controllers 
+which should need to attach to websockets. Implementation of these objects have been simplified
+
+Old
+```
+
+func (c WebSocket) RoomSocket(user string, ws *websocket.Conn) revel.Result {
+       // Join the room.
+       subscription := chatroom.Subscribe()
+       defer subscription.Cancel()
+
+       chatroom.Join(user)
+       defer chatroom.Leave(user)
+
+       // Send down the archive.
+       for _, event := range subscription.Archive {
+               if websocket.JSON.Send(ws, &event) != nil {
+                       // They disconnected
+                       return nil
+               }
+       }
+
+       // In order to select between websocket messages and subscription events, we
+       // need to stuff websocket events into a channel.
+       newMessages := make(chan string)
+       go func() {
+               var msg string
+               for {
+                       err := websocket.Message.Receive(ws, &msg)
+                       if err != nil {
+                               close(newMessages)
+                               return
+                       }
+                       newMessages <- msg
+               }
+       }()
+```
+New
+```
+func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result {
+       // Join the room.
+       subscription := chatroom.Subscribe()
+       defer subscription.Cancel()
+
+       chatroom.Join(user)
+       defer chatroom.Leave(user)
+
+       // Send down the archive.
+       for _, event := range subscription.Archive {
+               if ws.MessageSendJSON(&event) != nil {
+                       // They disconnected
+                       return nil
+               }
+       }
+
+       // In order to select between websocket messages and subscription events, we
+       // need to stuff websocket events into a channel.
+       newMessages := make(chan string)
+       go func() {
+               var msg string
+               for {
+                       err := ws.MessageReceiveJSON(&msg)
+                       if err != nil {
+                               close(newMessages)
+                               return
+                       }
+                       newMessages <- msg
+               }
+       }()
+```
+* GORM module has been refactored into modules/orm/gorm 
+
+
+### Deprecated methods
+* `revel.Request.FormValue()` Is deprecated, you should use methods in the controller.Params to access this data
+* `revel.Request.PostFormValue()` Is deprecated, you should use methods in the controller.Params.Form to access this data
+* `revel.Request.ParseForm()` Is deprecated - not needed
+* `revel.Request.ParseMultipartForm()` Is deprecated - not needed
+* `revel.Request.Form` Is deprecated, you should use the controller.Params.Form to access this data
+* `revel.Request.MultipartForm` Is deprecated, you should use the controller.Params.Form to access this data
+* `revel.TRACE`, `revel.INFO` `revel.WARN` `revel.ERROR` are deprecated. Use new application logger `revel.AppLog` and the controller logger `controller.Log`. See [logging](http://revel.github.io/manual/logging.html) for more details.
+
+### Features
+
+* Pluggable server engine support. You can now implement **your own server engine**. This means if you need to listen to more then 1 IP address or port you can implement a custom server engine to do this. By default Revel uses GO http server, but also available is fasthttp server in the revel/modules repository. See the docs for more information on how to implement your own engine.
+
+### Enhancements
+* Controller instances are cached for reuse. This speeds up the request response time and prevents unnecessary garbage collection cycles.  
+
+### Bug fixes
+
+
+
+
+## v0.17
+
+[[revel/revel](https://github.com/revel/revel)]
+
+* add-validation
+* i18-lang-by-param
+* Added namespace to routes, controllers
+* Added go 1.6 to testing
+* Adds the ability to set the language by a url parameter. The route file will need to specify the parameter so that it will be picked up
+* Changed url validation logic to regex
+* Added new validation mehtods (IPAddr,MacAddr,Domain,URL,PureText)
+
+[[revel/cmd](https://github.com/revel/cmd)]
+
+* no changes
+
+[[revel/config](https://github.com/revel/config)]
+
+* no changes
+
+[[revel/modules](https://github.com/revel/modules)]
+
+* Added Gorm module
+
+[[revel/cron](https://github.com/revel/cron)]
+
+* Updated cron task manager
+* Added ability to run a specific job, reschedules job if cron is running.
+
+[[revel/examples](https://github.com/revel/examples)]
+
+* Gorm module (Example)
+
+# v0.16.0
+
+Deprecating support for golang versions prior to 1.6
+### Breaking Changes
+
+* `CurrentLocaleRenderArg` to `CurrentLocaleViewArg` for consistency
+* JSON requests are now parsed by Revel, if the content type is `text/json` or `application/json`. The raw data is available in `Revel.Controller.Params.JSON`. But you can also use the automatic controller operation to load the data like you would any structure or map. See [here](http://revel.github.io/manual/parameters.html) for more details
+
+### Features
+
+* Modular Template Engine #1170 
+* Pongo2 engine driver added revel/modules#39
+* Ace engine driver added revel/modules#40
+* Added i18n template support #746 
+
+### Enhancements
+
+* JSON request binding #1161 
+* revel.SetSecretKey function added #1127 
+* ResolveFormat now looks at the extension as well (this sets the content type) #936 
+* Updated command to run tests using the configuration revel/cmd#61
+
+### Bug fixes
+
+* Updated documentation typos revel/modules#37
+* Updated order of parameter map assignment #1155 
+* Updated cookie lifetime for firefox #1174 
+* Added test path for modules, so modules will run tests as well #1162 
+* Fixed go profiler module revel/modules#20
+
+
+# v0.15.0
+@shawncatz released this on 2017-05-11
+
+Deprecating support for golang versions prior to 1.7
+
+### Breaking Changes
+
+* None
+
+### Features
+
+* None
+
+### Enhancements
+
+* Update and improve docs revel/examples#17 revel/cmd#85
+
+### Bug fixes
+
+* Prevent XSS revel/revel#1153
+* Improve error checking for go version detection revel/cmd#86
+
+# v0.14.0
+@notzippy released this on 2017-03-24
+
+## Changes since v0.13.0
+
+#### Breaking Changes
+- `revel/revel`:
+  - change RenderArgs to ViewArgs PR #1135
+  - change RenderJson to RenderJSON PR #1057
+  - change RenderHtml to RenderHTML PR #1057
+  - change RenderXml to RenderXML PR #1057
+
+#### Features
+- `revel/revel`:
+
+#### Enhancements
+- `revel/revel`:
+
+
+#### Bug Fixes
+- `revel/revel`:
+
+
+# v0.13.1
+@jeevatkm released this on 2016-06-07
+
+**Bug fix:**
+- Windows path fix #1064
+
+
+# v0.13.0
+@jeevatkm released this on 2016-06-06
+
+## Changes since v0.12.0
+
+#### Breaking Changes
+- `revel/revel`:
+  - Application Config name changed from `watcher.*` to `watch.*`  PR #992, PR #991
+
+#### Features
+- `revel/revel`:
+  - Request access log PR #1059, PR #913, #1055
+  - Messages loaded from modules too PR #828
+- `revel/cmd`:
+  - Added `revel version` command emits the revel version and go version revel/cmd#19
+
+#### Enhancements
+- `revel/revel`:
+  - Creates log directory if missing PR #1039
+  - Added `application/javascript` to accepted headers PR #1022
+  - You can change `Server.Addr` value via hook function PR #999
+  - Improved deflate/gzip compressor PR #995
+  - Consistent config name `watch.*` PR #992, PR #991
+  - Defaults to HttpOnly and always secure cookies for non-dev mode #942, PR #943
+  - Configurable server Read and Write Timeout via app config #936, PR #940
+  - `OnAppStart` hook now supports order param too PR #935
+  - Added `PutForm` and `PutFormCustom` helper method in `testing.TestSuite` #898
+  - Validator supports UTF-8 string too PR #891, #841
+  - Added `InitServer` method that returns `http.HandlerFunc` PR #879
+  - Symlink aware processing Views, Messages and Watch mode PR #867, #673
+  - Added i18n settings support unknown format PR #852
+  - i18n: Make Message Translation pluggable PR #768
+  - jQuery `min-2.2.4` & Bootstrap `min-3.3.6` version updated in `skeleton/public` #1063
+- `revel/cmd`:
+  - Revel identifies current `GOPATH` and performs `new` command; relative to directory revel/revel#1004
+  - Installs package dependencies during a build PR revel/cmd#43
+  - Non-200 response of test case request will correctly result into error PR revel/cmd#38
+  - Websockets SSL support in `dev` mode PR revel/cmd#32
+  - Won't yell about non-existent directory while cleaning PR revel/cmd#31, #908
+    - [x] non-fatal errors when building #908
+  - Improved warnings about route generation PR revel/cmd#25
+  - Command is Symlink aware PR revel/cmd#20
+  - `revel package` & `revel build` now supports environment mode PR revel/cmd#14
+  - `revel clean` now cleans generated routes too PR revel/cmd#6
+- `revel/config`:
+  - Upstream `robfig/config` refresh and import path updated from `github.com/revel/revel/config` to `github.com/revel/config`, PR #868
+  - Config loading order and external configuration to override application configuration revel/config#4 [commit](https://github.com/revel/revel/commit/f3a422c228994978ae0a5dd837afa97248b26b41)
+  - Application config error will produce insight on error PR revel/config#3 [commit](https://github.com/revel/config/commit/85a123061070899a82f59c5ef6187e8fb4457f64)
+- `revel/modules`:
+  - Testrunner enhancements
+    - Minor improvement on testrunner module PR #820, #895
+    - Add Test Runner panels per test group PR revel/modules#12
+- `revel/revel.github.io`:
+  - Update `index.md` and homepage (change how samples repo is installed) PR [#85](https://github.com/revel/revel.github.io/pull/85)
+  - Couple of UI improvements PR [#93](https://github.com/revel/revel.github.io/pull/93)
+  - Updated techempower benchmarks Round 11 [URL](http://www.techempower.com/benchmarks/#section=data-r11)
+  - Docs updated for v0.13 release
+- Cross-Platform Support
+  - Slashes should be normalized in paths #260, PR #1028, PR #928
+
+#### Bug Fixes
+- `revel/revel`:
+  - Binder: Multipart `io.Reader` parameters needs to be closed #756
+  - Default Date & Time Format correct in skeleton PR #1062, #878
+  - Addressed with alternative for `json: unsupported type: <-chan struct {}` on Go 1.6 revel/revel#1037
+  - Addressed one edge case, invalid Accept-Encoding header causes panic revel/revel#914
+
+
+# v0.11.3
+@brendensoares released this on 2015-01-04
+
+This is a minor release to address a critical bug (#824) in v0.11.2.
+
+Everybody is strongly encouraged to rebuild their projects with the latest version of Revel. To do it, execute the commands:
+
+``` sh
+$ go get -u github.com/revel/cmd/revel
+
+$ revel build github.com/myusername/myproject /path/to/destination/folder
+```
+
+
+# v0.11.2
+on 2014-11-23
+
+This is a minor release to address a critical bug in v0.11.0.
+
+Everybody is strongly encouraged to rebuild their projects with the latest version of Revel. To do it, execute the commands:
+
+``` sh
+$ go get -u github.com/revel/cmd/revel
+
+$ revel build github.com/myusername/myproject /path/to/destination/folder
+```
+
+
+# v0.11.1
+@pushrax released this on 2014-10-27
+
+This is a minor release to address a compilation error in v0.11.0.
+
+
+# v0.12.0
+@brendensoares released this on 2015-03-25
+
+Changes since v0.11.3:
+
+## Breaking Changes
+1. Add import path to new `testing` sub-package for all Revel tests. For example:
+
+``` go
+package tests
+
+import "github.com/revel/revel/testing"
+
+type AppTest struct {
+    testing.TestSuite
+}
+```
+1. We've relocated modules to a dedicated repo. Make sure you update your `conf/app.conf`. For example, change:
+
+``` ini
+module.static=github.com/revel/revel/modules/static
+module.testrunner = github.com/revel/revel/modules/testrunner
+```
+
+to the new paths:
+
+``` ini
+module.static=github.com/revel/modules/static
+module.testrunner = github.com/revel/modules/testrunner
+```
+
+## [ROADMAP] Focus: Improve Internal Organization
+
+The majority of our effort here is increasing the modularity of the code within Revel so that further development can be done more productively while keeping documentation up to date.
+- `revel/revel.github.io`
+  - [x] Improve docs #[43](https://github.com/revel/revel.github.io/pull/43)
+- `revel/revel`:
+  - [x] Move the `revel/revel/harness` to the `revel/cmd` repo since it's only used during build time. #[714](https://github.com/revel/revel/issues/714)
+  - [x] Move `revel/revel/modules` to the `revel/modules` repo #[785](https://github.com/revel/revel/issues/785)
+  - [x] Move `revel/revel/samples` to the `revel/samples` repo #[784](https://github.com/revel/revel/issues/784)
+  - [x] `testing` TestSuite #[737](https://github.com/revel/revel/issues/737) #[810](https://github.com/revel/revel/issues/810)
+  - [x] Feature/sane http timeout defaults #[837](https://github.com/revel/revel/issues/837) PR#[843](https://github.com/revel/revel/issues/843) Bug Fix PR#[860](https://github.com/revel/revel/issues/860)
+  - [x] Eagerly load templates in dev mode #[353](https://github.com/revel/revel/issues/353) PR#[844](https://github.com/revel/revel/pull/844)
+  - [x] Add an option to trim whitespace from rendered HTML #[800](https://github.com/revel/revel/issues/800)
+  - [x] Remove built-in mailer in favor of 3rd party package #[783](https://github.com/revel/revel/issues/783)
+  - [x] Allow local reverse proxy access to jobs module status page for IPv4/6 #[481](https://github.com/revel/revel/issues/481) PR#[6](https://github.com/revel/modules/pull/6) PR#[7](https://github.com/revel/modules/pull/7)
+  - [x] Add default http.Status code for render methods. #[728](https://github.com/revel/revel/issues/728)
+  - [x] add domain for cookie #[770](https://github.com/revel/revel/issues/770) PR#[882](https://github.com/revel/revel/pull/882)
+  - [x] production mode panic bug #[831](https://github.com/revel/revel/issues/831) PR#[881](https://github.com/revel/revel/pull/881)
+  - [x] Fixes template loading order whether watcher is enabled or not #[844](https://github.com/revel/revel/issues/844)
+  - [x] Fixes reverse routing wildcard bug PR#[886](https://github.com/revel/revel/pull/886) #[869](https://github.com/revel/revel/issues/869)
+  - [x] Fixes router app start bug without routes. PR #[855](https://github.com/revel/revel/pull/855)
+  - [x] Friendly URL template errors; Fixes template `url` func "index out of range" when param is `undefined` #[811](https://github.com/revel/revel/issues/811) PR#[880](https://github.com/revel/revel/pull/880)
+  - [x] Make result compression conditional PR#[888](https://github.com/revel/revel/pull/888)
+  - [x] ensure routes are loaded before returning from OnAppStart callback PR#[884](https://github.com/revel/revel/pull/884)
+  - [x] Use "302 Found" HTTP code for redirect PR#[900](https://github.com/revel/revel/pull/900)
+  - [x] Fix broken fake app tests PR#[899](https://github.com/revel/revel/pull/899)
+  - [x] Optimize search of template names PR#[885](https://github.com/revel/revel/pull/885)
+- `revel/cmd`:
+  - [x] track current Revel version #[418](https://github.com/revel/revel/issues/418) PR#[858](https://github.com/revel/revel/pull/858)
+  - [x] log path error After revel build? #[763](https://github.com/revel/revel/issues/763)
+  - [x] Use a separate directory for revel project binaries #[17](https://github.com/revel/cmd/pull/17) #[819](https://github.com/revel/revel/issues/819)
+  - [x] Overwrite generated app files instead of deleting directory #[551](https://github.com/revel/revel/issues/551) PR#[23](https://github.com/revel/cmd/pull/23)
+- `revel/modules`:
+  - [x] Adds runtime pprof/trace support #[9](https://github.com/revel/modules/pull/9)
+- Community Goals:
+  - [x] Issue labels #[545](https://github.com/revel/revel/issues/545)
+    - [x] Sync up labels/milestones in other repos #[721](https://github.com/revel/revel/issues/721)
+  - [x] Update the Revel Manual to reflect current features
+    - [x] [revel/revel.github.io/32](https://github.com/revel/revel.github.io/issues/32)
+    - [x] [revel/revel.github.io/39](https://github.com/revel/revel.github.io/issues/39)
+    - [x] Docs are obsolete, inaccessible TestRequest.testSuite #[791](https://github.com/revel/revel/issues/791)
+    - [x] Some questions about revel & go docs #[793](https://github.com/revel/revel/issues/793)
+  - [x] RFCs to organize features #[827](https://github.com/revel/revel/issues/827)
+
+[Full list of commits](https://github.com/revel/revel/compare/v0.11.3...v0.12.0)
+
+
+# v0.11.0
+@brendensoares released this on 2014-10-26
+
+Note, Revel 0.11 requires Go 1.3 or higher.
+
+Changes since v0.10:
+
+[BUG]   #729    Adding define inside the template results in an error (Changes how template file name case insensitivity is handled)
+
+[ENH]   #769    Add swap files to gitignore
+[ENH]   #766    Added passing in build flags to the go build command
+[ENH]   #761    Fixing cross-compiling issue #456 setting windows path from linux
+[ENH]   #759    Include upload sample's tests in travis
+[ENH]   #755    Changes c.Action to be the action method name's letter casing per #635
+[ENH]   #754    Adds call stack display to runtime panic in browser to match console
+[ENH]   #740    Redis Cache: Add timeouts.
+[ENH]   #734    watcher: treat fsnotify Op as a bitmask
+[ENH]   #731    Second struct in type revel fails to find the controller
+[ENH]   #725    Testrunner: show response info
+[ENH]   #723    Improved compilation errors and open file from error page
+[ENH]   #720    Get testrunner path from config file
+[ENH]   #707    Add log.colorize option to enable/disable colorize
+[ENH]   #696    Revel file upload testing
+[ENH]   #694    Install dependencies at build time
+[ENH]   #693    Prefer extension over Accept header
+[ENH]   #692    Update fsnotify to v1 API
+[ENH]   #690    Support zero downtime restarts
+[ENH]   #687    Tests: request override
+[ENH]   #685    Persona sample tests and bugfix
+[ENH]   #598    Added README file to Revel skeleton
+[ENH]   #591    Realtime rebuild
+[ENH]   #573    Add AppRoot to allow changing the root path of an application
+
+[FTR]   #606    CSRF Support
+
+[Full list of commits](https://github.com/revel/revel/compare/v0.10.0...v0.11.0)
+
+
+# v0.10.0
+@brendensoares released this on 2014-08-10
+
+Changes since v0.9.1:
+- [FTR] #641 - Add "X-HTTP-Method-Override" to router
+- [FTR] #583 - Added HttpMethodOverride filter to routes
+- [FTR] #540 - watcher flag for refresh on app start
+- [BUG] #681 - Case insensitive comparison for websocket upgrades (Fixes IE Websockets ...
+- [BUG] #668 - Compression: Properly close gzip/deflate
+- [BUG] #667 - Fix redis GetMulti and improve test coverage
+- [BUG] #664 - Is compression working correct?
+- [BUG] #657 - Redis Cache: panic when testing Ge
+- [BUG] #637 - RedisCache: fix Get/GetMulti error return
+- [BUG] #621 - Bugfix/router csv error
+- [BUG] #618 - Router throws exception when parsing line with multiple default string arguments
+- [BUG] #604 - Compression: Properly close gzip/deflate.
+- [BUG] #567 - Fixed regex pattern to properly require message files to have a dot in filename
+- [BUG] #566 - Compression fails ("unexpected EOF" in tests)
+- [BUG] #287 - Don't remove the parent folders containing generated code.
+- [BUG] #556 - fix for #534, also added url path to not found message
+- [BUG] #534 - Websocket route not found
+- [BUG] #343 - validation.Required(funtionCall).Key(...) - reflect.go:715: Failed to generate name for field.
+- [ENH] #643 - Documentation Fix in Skeleton for OnAppStart
+- [ENH] #674 - Removes custom `eq` template function
+- [ENH] #669 - Develop compress closenotifier
+- [ENH] #663 - fix for static content type not being set and defaulting to OS
+- [ENH] #658 - Minor: fix niggle with import statement
+- [ENH] #652 - Update the contributing guidelines
+- [ENH] #651 - Use upstream gomemcache again
+- [ENH] #650 - Go back to upstream memcached library
+- [ENH] #612 - Fix CI package error
+- [ENH] #611 - Fix "go vet" problems
+- [ENH] #610 - Added MakeMultipartRequest() to the TestSuite
+- [ENH] #608 - Develop compress closenotifier
+- [ENH] #596 - Expose redis cache options to config
+- [ENH] #581 - Make the option template tag type agnostic.
+- [ENH] #576 - Defer session instantiation to first set
+- [ENH] #565 - Fix #563 -- Some custom template funcs cannot be used in JavaScript cont...
+- [ENH] #563 - TemplateFuncs cannot be used in JavaScript context
+- [ENH] #561 - Fix missing extension from message file causing panic
+- [ENH] #560 - enhancement / templateFunc `firstof`
+- [ENH] #555 - adding symlink handling to the template loader and watcher processes
+- [ENH] #531 - Update app.conf.template
+- [ENH] #520 - Respect controller's Response.Status when action returns nil
+- [ENH] #519 - Link to issues
+- [ENH] #486 - Support for json compress
+- [ENH] #480 - Eq implementation in template.go still necessary ?
+- [ENH] #461 - Cron jobs not started until I pull a page
+- [ENH] #323 - disable session/set-cookie for `Static.Serve()`
+
+[Full list of commits](https://github.com/revel/revel/compare/v0.9.1...v0.10.0)
+
+
+# v0.9.1
+@pushrax released this on 2014-03-02
+
+Minor patch release to address a couple bugs.
+
+Changes since v0.9.0:
+- [BUG] #529 - Wrong path was used to determine existence of `.git`
+- [BUG] #532 - Fix typo for new type `ValidEmail`
+
+The full list of commits can be found [here](https://github.com/revel/revel/compare/v0.9.0...v0.9.1).
+
+
+# v0.9.0
+@pushrax released this on 2014-02-26
+
+## Revel GitHub Organization
+
+We've moved development of the framework to the @revel GitHub organization, to help manage the project as Revel grows. The old import path is still valid, but will not be updated in the future.
+
+You'll need to manually update your apps to work with the new import path. This can be done by replacing all instances of `github.com/robfig/revel` with `github.com/revel/revel` in your app, and running:
+
+```
+$ cd your_app_folder
+$ go get -u github.com/howeyc/fsnotify  # needs updating
+$ go get github.com/revel/revel
+$ go get github.com/revel/cmd/revel     # command line tools have moved
+```
+
+**Note:** if you have references to `github.com/robfig/revel/revel` in any files, you need to replace them with `github.com/revel/cmd/revel` _before_ replacing `github.com/robfig/revel`! (note the prefix collision)
+
+If you have any trouble upgrading or notice something we missed, feel free to hop in the IRC channel (#revel on Freenode) or send the mailing list a message.
+
+Also note, the documentation is now at [revel.github.io](http://revel.github.io)!
+
+Changes since v0.8:
+- [BUG] #522 - `revel new` bug
+- [BUG] - Booking sample error
+- [BUG] #504 - File access via URL security issue
+- [BUG] #489 - Email validator bug
+- [BUG] #475 - File watcher infinite loop
+- [BUG] #333 - Extensions in routes break parameters
+- [FTR] #472 - Support for 3rd part app skeletons
+- [ENH] #512 - Per session expiration methods
+- [ENH] #496 - Type check renderArgs[CurrentLocalRenderArg]
+- [ENH] #490 - App.conf manual typo
+- [ENH] #487 - Make files executable on `revel build`
+- [ENH] #482 - Retain input values after form valdiation
+- [ENH] #473 - OnAppStart documentation
+- [ENH] #466 - JSON error template quoting fix
+- [ENH] #464 - Remove unneeded trace statement
+- [ENH] #457 - Remove unneeded trace
+- [ENH] #508 - Support arbitrary network types
+- [ENH] #516 - Add Date and Message-Id mail headers
+
+The full list of commits can be found [here](https://github.com/revel/revel/compare/v0.8...v0.9.0).
+
+
+# v0.8
+@pushrax released this on 2014-01-06
+
+Changes since v0.7:
+- [BUG] #379 - HTTP 500 error for not found public path files
+- [FTR] #424 - HTTP pprof support
+- [FTR] #346 - Redis Cache support
+- [FTR] #292 - SMTP Mailer
+- [ENH] #443 - Validator constructors to improve `v.Check()` usage
+- [ENH] #439 - Basic terminal output coloring
+- [ENH] #428 - Improve error message for missing `RenderArg`
+- [ENH] #422 - Route embedding for modules
+- [ENH] #413 - App version variable
+- [ENH] #153 - $GOPATH-wide file watching aka hot loading
+
+
+# v0.6
+@robfig released this on 2013-09-16
+
+
+
diff --git a/src/foundation/api/revel/CONTRIBUTING.md b/src/foundation/api/revel/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..5f699ca
--- /dev/null
@@ -0,0 +1,162 @@
+## Contributing to Revel
+
+This describes how developers may contribute to Revel.
+
+## Mission
+
+Revel's mission is to provide a batteries-included framework for making large
+scale web application development as efficient and maintainable as possible.
+
+The design should be configurable and modular so that it can grow with the
+developer. However, it should provide a wonderful un-boxing experience and
+default configuration that can woo new developers and make simple web apps
+straight forward. The framework should have an opinion about how to do all of the
+common tasks in web development to reduce unnecessary cognitive load.
+
+Perhaps most important of all, Revel should be a joy to use. We want to reduce
+the time spent on tedious boilerplate functionality and increase the time
+available for creating polished solutions for your application's target users.
+
+## How to Contribute
+
+### Join the Community
+
+The first step to making Revel better is joining the community! You can find the
+community on:
+
+* [Google Groups](https://groups.google.com/forum/#!forum/revel-framework) via [revel-framework@googlegroups.com](mailto:revel-framework@googlegroups.com)
+* [GitHub Issues](https://github.com/revel/revel/issues)
+* [StackOverflow Questions](http://stackoverflow.com/questions/tagged/revel)
+* [IRC](http://webchat.freenode.net/?channels=%23revel&uio=d4) via #revel on Freenode
+
+Once you've joined, there are many ways to contribute to Revel:
+
+* Report bugs (via GitHub)
+* Answer questions of other community members (via Google Groups or IRC)
+* Give feedback on new feature discussions (via GitHub and Google Groups)
+* Propose your own ideas (via Google Groups or GitHub)
+
+### How Revel is Developed
+
+We have begun to formalize the development process by adopting pragmatic
+practices such as:
+
+* Developing on the `develop` branch
+* Merging `develop` branch to `master` branch in 6 week iterations
+* Tagging releases with MAJOR.MINOR syntax (e.g. v0.8)
+** We may also tag MAJOR.MINOR.HOTFIX releases as needed (e.g. v0.8.1) to
+address urgent bugs. Such releases will not introduce or change functionality
+* Managing bugs, enhancements, features and release milestones via GitHub's Issue Tracker
+* Using feature branches to create pull requests
+* Discussing new features **before** hacking away at it
+
+
+### How to Correctly Fork
+
+Go uses the repository URL to import packages, so forking and `go get`ing the
+forked project **will not work**.
+
+Instead, follow these steps:
+
+1. Install Revel normally
+2. Fork Revel on GitHub
+3. Add your fork as a git remote
+
+Here's the commands to do so:
+```
+$ go get github.com/revel/revel                        # Install Revel
+$ cd $GOPATH/src/github.com/revel/revel                # Change directory to revel repo
+$ git remote add fork git@github.com:$USER/revel.git  # Add your fork as a remote, where $USER is your GitHub username
+```
+
+### Create a Feature Branch & Code Away!
+
+Now that you've properly installed and forked Revel, you are ready to start coding (assuming
+you have a validated your ideas with other community members)!
+
+In order to have your pull requests accepted, we recommend you make your changes to Revel on a
+new git branch. For example,
+```
+$ git checkout -b feature/useful-new-thing origin/develop    # Create a new branch based on develop and switch to it
+$ ...                                                        # Make your changes and commit them
+$ git push fork feature/useful-new-thing                     # After new commits, push to your fork
+```
+
+### Format Your Code
+
+Remember to run `go fmt` before committing your changes.
+Many Go developers opt to have their editor run `go fmt` automatically when
+saving Go files.
+
+Additionally, follow the [core Go style conventions](https://code.google.com/p/go-wiki/wiki/CodeReviewComments)
+to have your pull requests accepted.
+
+### Write Tests (and Benchmarks for Bonus Points)
+
+Significant new features require tests. Besides unit tests, it is also possible
+to test a feature by exercising it in one of the sample apps and verifying its
+operation using that app's test suite. This has the added benefit of providing
+example code for developers to refer to.
+
+Benchmarks are helpful but not required.
+
+### Run the Tests
+
+Typically running the main set of unit tests will be sufficient:
+
+```
+$ go test github.com/revel/revel
+```
+
+Refer to the
+[Travis configuration](https://github.com/revel/revel/blob/master/.travis.yml)
+for the full set of tests.  They take less than a minute to run.
+
+### Document Your Feature
+
+Due to the wide audience and shared nature of Revel, documentation is an essential
+addition to your new code. **Pull requests risk not being accepted** until proper
+documentation is created to detail how to make use of new functionality.
+
+The [Revel web site](http://revel.github.io/) is hosted on GitHub Pages and
+[built with Jekyll](https://help.github.com/articles/using-jekyll-with-pages).
+
+To develop the Jekyll site locally:
+
+    # Clone the documentation repository
+    $ git clone git@github.com:revel/revel.github.io
+    $ cd revel.github.io
+
+    # Install and run Jekyll to generate and serve the site
+    $ gem install jekyll kramdown
+    $ jekyll serve --watch
+
+    # Now load in your browser
+    $ open http://localhost:4000/
+
+Any changes you make to the site should be reflected within a few seconds.
+
+### Submit Pull Request
+
+Once you've done all of the above & pushed your changes to your fork, you can create a pull request for review and acceptance.
+
+## Potential Projects
+
+These are outstanding feature requests, roughly ordered by priority.
+Additionally, there are frequently smaller feature requests or items in the
+[issues](https://github.com/revel/revel/issues?labels=contributor+ready&page=1&state=open).
+
+1.     Better ORM support.  Provide more samples (or modules) and better documentation for setting up common situations like SQL database, Mongo, LevelDB, etc.
+1.     Support for other templating languages (e.g. mustache, HAML).  Make TemplateLoader pluggable.  Use Pongo instead of vanilla Go templates (and update the samples)
+1.     Test Fixtures
+1.     Authenticity tokens for CSRF protection
+1.     Coffeescript pre-processor.  Could potentially use [otto](https://github.com/robertkrimen/otto) as a native Go method to compiling.
+1.     SCSS/LESS pre-processor.
+1.     GAE support.  Some progress made in the 'appengine' branch -- the remaining piece is running the appengine services in development.
+1.     More Form helpers (template funcs).
+1.     A Mongo module (perhaps with a sample app)
+1.     Deployment to OpenShift (support, documentation, etc)
+1.     Improve the logging situation.  The configuration is a little awkward and not very powerful.  Integrating something more powerful would be good. (like [seelog](https://github.com/cihub/seelog) or [log4go](https://code.google.com/p/log4go/))
+1.     ETags, cache controls
+1.     A module or plugins for adding HTTP Basic Auth
+1.     Allowing the app to hook into the source code processing step
diff --git a/src/foundation/api/revel/LICENSE b/src/foundation/api/revel/LICENSE
new file mode 100644 (file)
index 0000000..92246e3
--- /dev/null
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (C) 2012-2018 The Revel Framework Authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/foundation/api/revel/README.md b/src/foundation/api/revel/README.md
new file mode 100644 (file)
index 0000000..c89e193
--- /dev/null
@@ -0,0 +1,56 @@
+# Revel Framework
+
+[![Build Status](https://secure.travis-ci.org/revel/revel.svg?branch=master)](http://travis-ci.org/revel/revel) 
+[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
+[![Go Report Card](https://goreportcard.com/badge/github.com/revel/revel)](https://goreportcard.com/report/github.com/revel/revel)
+
+A high productivity, full-stack web framework for the [Go language](http://www.golang.org).
+
+Current Version: 0.21.0 (2018-10-30)
+
+**Because of Default HTTP Server's graceful shutdown, Go 1.8+ is required.**
+
+## Quick Start
+
+Enter Go's path (format varies based on OS):
+
+       cd $GOPATH
+
+Install Revel:
+
+       go get -u github.com/revel/cmd/revel
+
+Create & Run your app:
+
+       revel new -a my-app -r
+
+Open http://localhost:9000 in your browser and you should see "It works!"
+
+
+## Community
+
+* [Gitter](https://gitter.im/revel/community)
+* [StackOverflow](http://stackoverflow.com/questions/tagged/revel)
+
+
+## Learn More
+
+* [Manual, Samples, Godocs, etc](http://revel.github.io)
+* [Apps using Revel](https://github.com/revel/revel/wiki/Apps-in-the-Wild)
+* [Articles Featuring Revel](https://github.com/revel/revel/wiki/Articles)
+
+## Contributing
+
+* [Contributing Code Guidelines](https://github.com/revel/revel/blob/master/CONTRIBUTING.md)
+* [Revel Contributors](https://github.com/revel/revel/graphs/contributors)
+
+## Contributors
+
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/0)](https://sourcerer.io/fame/notzippy/revel/revel/links/0)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/1)](https://sourcerer.io/fame/notzippy/revel/revel/links/1)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/2)](https://sourcerer.io/fame/notzippy/revel/revel/links/2)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/3)](https://sourcerer.io/fame/notzippy/revel/revel/links/3)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/4)](https://sourcerer.io/fame/notzippy/revel/revel/links/4)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/5)](https://sourcerer.io/fame/notzippy/revel/revel/links/5)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/6)](https://sourcerer.io/fame/notzippy/revel/revel/links/6)
+[![](https://sourcerer.io/fame/notzippy/revel/revel/images/7)](https://sourcerer.io/fame/notzippy/revel/revel/links/7)
diff --git a/src/foundation/api/revel/before_after_filter.go b/src/foundation/api/revel/before_after_filter.go
new file mode 100644 (file)
index 0000000..e9af1d3
--- /dev/null
@@ -0,0 +1,60 @@
+package revel
+
+import (
+       "reflect"
+)
+
+// Autocalls any defined before and after methods on the target controller
+// If either calls returns a value then the result is returned
+func BeforeAfterFilter(c *Controller, fc []Filter) {
+       defer func() {
+               if resultValue := beforeAfterFilterInvoke(FINALLY, c); resultValue != nil && !resultValue.IsNil() {
+                       c.Result = resultValue.Interface().(Result)
+               }
+       }()
+       defer func() {
+               if err := recover(); err != nil {
+                       if resultValue := beforeAfterFilterInvoke(PANIC, c); resultValue != nil && !resultValue.IsNil() {
+                               c.Result = resultValue.Interface().(Result)
+                       }
+                       panic(err)
+               }
+       }()
+       if resultValue := beforeAfterFilterInvoke(BEFORE, c); resultValue != nil && !resultValue.IsNil() {
+               c.Result = resultValue.Interface().(Result)
+       }
+       fc[0](c, fc[1:])
+       if resultValue := beforeAfterFilterInvoke(AFTER, c); resultValue != nil && !resultValue.IsNil() {
+               c.Result = resultValue.Interface().(Result)
+       }
+}
+
+func beforeAfterFilterInvoke(method When, c *Controller) (r *reflect.Value) {
+
+       if c.Type == nil {
+               return
+       }
+       var index []*ControllerFieldPath
+       switch method {
+       case BEFORE:
+               index = c.Type.ControllerEvents.Before
+       case AFTER:
+               index = c.Type.ControllerEvents.After
+       case FINALLY:
+               index = c.Type.ControllerEvents.Finally
+       case PANIC:
+               index = c.Type.ControllerEvents.Panic
+       }
+
+       if len(index) == 0 {
+               return
+       }
+       for _, function := range index {
+               result := function.Invoke(reflect.ValueOf(c.AppController), nil)[0]
+               if !result.IsNil() {
+                       return &result
+               }
+       }
+
+       return
+}
diff --git a/src/foundation/api/revel/binder.go b/src/foundation/api/revel/binder.go
new file mode 100644 (file)
index 0000000..a42ed21
--- /dev/null
@@ -0,0 +1,543 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "mime/multipart"
+       "os"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// A Binder translates between string parameters and Go data structures.
+type Binder struct {
+       // Bind takes the name and type of the desired parameter and constructs it
+       // from one or more values from Params.
+       //
+       // Example
+       //
+       // Request:
+       //   url?id=123&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=rob
+       //
+       // Action:
+       //   Example.Action(id int, ol []int, ul []string, user User)
+       //
+       // Calls:
+       //   Bind(params, "id", int): 123
+       //   Bind(params, "ol", []int): {1, 2}
+       //   Bind(params, "ul", []string): {"str", "array"}
+       //   Bind(params, "user", User): User{Name:"rob"}
+       //
+       // Note that only exported struct fields may be bound.
+       Bind func(params *Params, name string, typ reflect.Type) reflect.Value
+
+       // Unbind serializes a given value to one or more URL parameters of the given
+       // name.
+       Unbind func(output map[string]string, name string, val interface{})
+}
+
+var binderLog = RevelLog.New("section", "binder")
+
+// ValueBinder is adapter for easily making one-key-value binders.
+func ValueBinder(f func(value string, typ reflect.Type) reflect.Value) func(*Params, string, reflect.Type) reflect.Value {
+       return func(params *Params, name string, typ reflect.Type) reflect.Value {
+               vals, ok := params.Values[name]
+               if !ok || len(vals) == 0 {
+                       return reflect.Zero(typ)
+               }
+               return f(vals[0], typ)
+       }
+}
+
+// Revel's default date and time constants
+const (
+       DefaultDateFormat     = "2006-01-02"
+       DefaultDateTimeFormat = "2006-01-02 15:04"
+)
+
+// Binders type and kind definition
+var (
+       // These are the lookups to find a Binder for any type of data.
+       // The most specific binder found will be used (Type before Kind)
+       TypeBinders = make(map[reflect.Type]Binder)
+       KindBinders = make(map[reflect.Kind]Binder)
+
+       // Applications can add custom time formats to this array, and they will be
+       // automatically attempted when binding a time.Time.
+       TimeFormats = []string{}
+
+       DateFormat     string
+       DateTimeFormat string
+       TimeZone       = time.UTC
+
+       IntBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       if len(val) == 0 {
+                               return reflect.Zero(typ)
+                       }
+                       intValue, err := strconv.ParseInt(val, 10, 64)
+                       if err != nil {
+                               binderLog.Warn("IntBinder Conversion Error", "error", err)
+                               return reflect.Zero(typ)
+                       }
+                       pValue := reflect.New(typ)
+                       pValue.Elem().SetInt(intValue)
+                       return pValue.Elem()
+               }),
+               Unbind: func(output map[string]string, key string, val interface{}) {
+                       output[key] = fmt.Sprintf("%d", val)
+               },
+       }
+
+       UintBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       if len(val) == 0 {
+                               return reflect.Zero(typ)
+                       }
+                       uintValue, err := strconv.ParseUint(val, 10, 64)
+                       if err != nil {
+                               binderLog.Warn("UintBinder Conversion Error", "error", err)
+                               return reflect.Zero(typ)
+                       }
+                       pValue := reflect.New(typ)
+                       pValue.Elem().SetUint(uintValue)
+                       return pValue.Elem()
+               }),
+               Unbind: func(output map[string]string, key string, val interface{}) {
+                       output[key] = fmt.Sprintf("%d", val)
+               },
+       }
+
+       FloatBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       if len(val) == 0 {
+                               return reflect.Zero(typ)
+                       }
+                       floatValue, err := strconv.ParseFloat(val, 64)
+                       if err != nil {
+                               binderLog.Warn("FloatBinder Conversion Error", "error", err)
+                               return reflect.Zero(typ)
+                       }
+                       pValue := reflect.New(typ)
+                       pValue.Elem().SetFloat(floatValue)
+                       return pValue.Elem()
+               }),
+               Unbind: func(output map[string]string, key string, val interface{}) {
+                       output[key] = fmt.Sprintf("%f", val)
+               },
+       }
+
+       StringBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       return reflect.ValueOf(val)
+               }),
+               Unbind: func(output map[string]string, name string, val interface{}) {
+                       output[name] = val.(string)
+               },
+       }
+
+       // Booleans support a various value formats,
+       // refer `revel.Atob` method.
+       BoolBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       return reflect.ValueOf(Atob(val))
+               }),
+               Unbind: func(output map[string]string, name string, val interface{}) {
+                       output[name] = fmt.Sprintf("%t", val)
+               },
+       }
+
+       PointerBinder = Binder{
+               Bind: func(params *Params, name string, typ reflect.Type) reflect.Value {
+                       v := Bind(params, name, typ.Elem())
+                       if v.CanAddr() {
+                               return v.Addr()
+                       }
+
+                       return v
+               },
+               Unbind: func(output map[string]string, name string, val interface{}) {
+                       Unbind(output, name, reflect.ValueOf(val).Elem().Interface())
+               },
+       }
+
+       TimeBinder = Binder{
+               Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
+                       for _, f := range TimeFormats {
+                               if r, err := time.ParseInLocation(f, val, TimeZone); err == nil {
+                                       return reflect.ValueOf(r)
+                               }
+                       }
+                       return reflect.Zero(typ)
+               }),
+               Unbind: func(output map[string]string, name string, val interface{}) {
+                       var (
+                               t       = val.(time.Time)
+                               format  = DateTimeFormat
+                               h, m, s = t.Clock()
+                       )
+                       if h == 0 && m == 0 && s == 0 {
+                               format = DateFormat
+                       }
+                       output[name] = t.Format(format)
+               },
+       }
+
+       MapBinder = Binder{
+               Bind:   bindMap,
+               Unbind: unbindMap,
+       }
+)
+
+// Used to keep track of the index for individual keyvalues.
+type sliceValue struct {
+       index int           // Index extracted from brackets.  If -1, no index was provided.
+       value reflect.Value // the bound value for this slice element.
+}
+
+// This function creates a slice of the given type, Binds each of the individual
+// elements, and then sets them to their appropriate location in the slice.
+// If elements are provided without an explicit index, they are added (in
+// unspecified order) to the end of the slice.
+func bindSlice(params *Params, name string, typ reflect.Type) reflect.Value {
+       // Collect an array of slice elements with their indexes (and the max index).
+       maxIndex := -1
+       numNoIndex := 0
+       sliceValues := []sliceValue{}
+
+       // Factor out the common slice logic (between form values and files).
+       processElement := func(key string, vals []string, files []*multipart.FileHeader) {
+               if !strings.HasPrefix(key, name+"[") {
+                       return
+               }
+
+               // Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey)
+               index := -1
+               leftBracket, rightBracket := len(name), strings.Index(key[len(name):], "]")+len(name)
+               if rightBracket > leftBracket+1 {
+                       index, _ = strconv.Atoi(key[leftBracket+1 : rightBracket])
+               }
+               subKeyIndex := rightBracket + 1
+
+               // Handle the indexed case.
+               if index > -1 {
+                       if index > maxIndex {
+                               maxIndex = index
+                       }
+                       sliceValues = append(sliceValues, sliceValue{
+                               index: index,
+                               value: Bind(params, key[:subKeyIndex], typ.Elem()),
+                       })
+                       return
+               }
+
+               // It's an un-indexed element.  (e.g. element[])
+               numNoIndex += len(vals) + len(files)
+               for _, val := range vals {
+                       // Unindexed values can only be direct-bound.
+                       sliceValues = append(sliceValues, sliceValue{
+                               index: -1,
+                               value: BindValue(val, typ.Elem()),
+                       })
+               }
+
+               for _, fileHeader := range files {
+                       sliceValues = append(sliceValues, sliceValue{
+                               index: -1,
+                               value: BindFile(fileHeader, typ.Elem()),
+                       })
+               }
+       }
+
+       for key, vals := range params.Values {
+               processElement(key, vals, nil)
+       }
+       for key, fileHeaders := range params.Files {
+               processElement(key, nil, fileHeaders)
+       }
+
+       resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex)
+       for _, sv := range sliceValues {
+               if sv.index != -1 {
+                       resultArray.Index(sv.index).Set(sv.value)
+               } else {
+                       resultArray = reflect.Append(resultArray, sv.value)
+               }
+       }
+
+       return resultArray
+}
+
+// Break on dots and brackets.
+// e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar"
+func nextKey(key string) string {
+       fieldLen := strings.IndexAny(key, ".[")
+       if fieldLen == -1 {
+               return key
+       }
+       return key[:fieldLen]
+}
+
+func unbindSlice(output map[string]string, name string, val interface{}) {
+       v := reflect.ValueOf(val)
+       for i := 0; i < v.Len(); i++ {
+               Unbind(output, fmt.Sprintf("%s[%d]", name, i), v.Index(i).Interface())
+       }
+}
+
+func bindStruct(params *Params, name string, typ reflect.Type) reflect.Value {
+       resultPointer := reflect.New(typ)
+       result := resultPointer.Elem()
+       if params.JSON != nil {
+               // Try to inject the response as a json into the created result
+               if err := json.Unmarshal(params.JSON, resultPointer.Interface()); err != nil {
+                       binderLog.Error("bindStruct Unable to unmarshal request", "name", name, "error", err, "data", string(params.JSON))
+               }
+               return result
+       }
+       fieldValues := make(map[string]reflect.Value)
+       for key := range params.Values {
+               if !strings.HasPrefix(key, name+".") {
+                       continue
+               }
+
+               // Get the name of the struct property.
+               // Strip off the prefix. e.g. foo.bar.baz => bar.baz
+               suffix := key[len(name)+1:]
+               fieldName := nextKey(suffix)
+               fieldLen := len(fieldName)
+
+               if _, ok := fieldValues[fieldName]; !ok {
+                       // Time to bind this field.  Get it and make sure we can set it.
+                       fieldValue := result.FieldByName(fieldName)
+                       if !fieldValue.IsValid() {
+                               binderLog.Warn("bindStruct Field not found", "name", fieldName)
+                               continue
+                       }
+                       if !fieldValue.CanSet() {
+                               binderLog.Warn("bindStruct Field not settable", "name", fieldName)
+                               continue
+                       }
+                       boundVal := Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type())
+                       fieldValue.Set(boundVal)
+                       fieldValues[fieldName] = boundVal
+               }
+       }
+
+       return result
+}
+
+func unbindStruct(output map[string]string, name string, iface interface{}) {
+       val := reflect.ValueOf(iface)
+       typ := val.Type()
+       for i := 0; i < val.NumField(); i++ {
+               structField := typ.Field(i)
+               fieldValue := val.Field(i)
+
+               // PkgPath is specified to be empty exactly for exported fields.
+               if structField.PkgPath == "" {
+                       Unbind(output, fmt.Sprintf("%s.%s", name, structField.Name), fieldValue.Interface())
+               }
+       }
+}
+
+// Helper that returns an upload of the given name, or nil.
+func getMultipartFile(params *Params, name string) multipart.File {
+       for _, fileHeader := range params.Files[name] {
+               file, err := fileHeader.Open()
+               if err == nil {
+                       return file
+               }
+               binderLog.Warn("getMultipartFile: Failed to open uploaded file", "name", name, "error", err)
+       }
+       return nil
+}
+
+func bindFile(params *Params, name string, typ reflect.Type) reflect.Value {
+       reader := getMultipartFile(params, name)
+       if reader == nil {
+               return reflect.Zero(typ)
+       }
+
+       // If it's already stored in a temp file, just return that.
+       if osFile, ok := reader.(*os.File); ok {
+               return reflect.ValueOf(osFile)
+       }
+
+       // Otherwise, have to store it.
+       tmpFile, err := ioutil.TempFile("", "revel-upload")
+       if err != nil {
+               binderLog.Warn("bindFile: Failed to create a temp file to store upload", "name", name, "error", err)
+               return reflect.Zero(typ)
+       }
+
+       // Register it to be deleted after the request is done.
+       params.tmpFiles = append(params.tmpFiles, tmpFile)
+
+       _, err = io.Copy(tmpFile, reader)
+       if err != nil {
+               binderLog.Warn("bindFile: Failed to copy upload to temp file", "name", name, "error", err)
+               return reflect.Zero(typ)
+       }
+
+       _, err = tmpFile.Seek(0, 0)
+       if err != nil {
+               binderLog.Warn("bindFile: Failed to seek to beginning of temp file", "name", name, "error", err)
+               return reflect.Zero(typ)
+       }
+
+       return reflect.ValueOf(tmpFile)
+}
+
+func bindByteArray(params *Params, name string, typ reflect.Type) reflect.Value {
+       if reader := getMultipartFile(params, name); reader != nil {
+               b, err := ioutil.ReadAll(reader)
+               if err == nil {
+                       return reflect.ValueOf(b)
+               }
+               binderLog.Warn("bindByteArray: Error reading uploaded file contents", "name", name, "error", err)
+       }
+       return reflect.Zero(typ)
+}
+
+func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value {
+       if reader := getMultipartFile(params, name); reader != nil {
+               return reflect.ValueOf(reader.(io.ReadSeeker))
+       }
+       return reflect.Zero(typ)
+}
+
+// bindMap converts parameters using map syntax into the corresponding map. e.g.:
+//   params["a[5]"]=foo, name="a", typ=map[int]string => map[int]string{5: "foo"}
+func bindMap(params *Params, name string, typ reflect.Type) reflect.Value {
+       var (
+               keyType   = typ.Key()
+               valueType = typ.Elem()
+               resultPtr = reflect.New(reflect.MapOf(keyType, valueType))
+               result    = resultPtr.Elem()
+       )
+       result.Set(reflect.MakeMap(typ))
+       if params.JSON != nil {
+               // Try to inject the response as a json into the created result
+               if err := json.Unmarshal(params.JSON, resultPtr.Interface()); err != nil {
+                       binderLog.Warn("bindMap: Unable to unmarshal request", "name", name, "error", err)
+               }
+               return result
+       }
+
+       for paramName := range params.Values {
+               // The paramName string must start with the value in the "name" parameter,
+               // otherwise there is no way the parameter is part of the map
+               if !strings.HasPrefix(paramName, name) {
+                       continue
+               }
+
+               suffix := paramName[len(name)+1:]
+               fieldName := nextKey(suffix)
+               if fieldName != "" {
+                       fieldName = fieldName[:len(fieldName)-1]
+               }
+               if !strings.HasPrefix(paramName, name+"["+fieldName+"]") {
+                       continue
+               }
+
+               result.SetMapIndex(BindValue(fieldName, keyType), Bind(params, name+"["+fieldName+"]", valueType))
+       }
+       return result
+}
+
+func unbindMap(output map[string]string, name string, iface interface{}) {
+       mapValue := reflect.ValueOf(iface)
+       for _, key := range mapValue.MapKeys() {
+               Unbind(output, name+"["+fmt.Sprintf("%v", key.Interface())+"]",
+                       mapValue.MapIndex(key).Interface())
+       }
+}
+
+// Bind takes the name and type of the desired parameter and constructs it
+// from one or more values from Params.
+// Returns the zero value of the type upon any sort of failure.
+func Bind(params *Params, name string, typ reflect.Type) reflect.Value {
+       if binder, found := binderForType(typ); found {
+               return binder.Bind(params, name, typ)
+       }
+       return reflect.Zero(typ)
+}
+
+func BindValue(val string, typ reflect.Type) reflect.Value {
+       return Bind(&Params{Values: map[string][]string{"": {val}}}, "", typ)
+}
+
+func BindFile(fileHeader *multipart.FileHeader, typ reflect.Type) reflect.Value {
+       return Bind(&Params{Files: map[string][]*multipart.FileHeader{"": {fileHeader}}}, "", typ)
+}
+
+func Unbind(output map[string]string, name string, val interface{}) {
+       if binder, found := binderForType(reflect.TypeOf(val)); found {
+               if binder.Unbind != nil {
+                       binder.Unbind(output, name, val)
+               } else {
+                       binderLog.Error("Unbind: Unable to unmarshal request", "name", name, "value", val)
+               }
+       }
+}
+
+func binderForType(typ reflect.Type) (Binder, bool) {
+       binder, ok := TypeBinders[typ]
+       if !ok {
+               binder, ok = KindBinders[typ.Kind()]
+               if !ok {
+                       binderLog.Error("binderForType: no binder for type", "type", typ)
+                       return Binder{}, false
+               }
+       }
+       return binder, true
+}
+
+// Sadly, the binder lookups can not be declared initialized -- that results in
+// an "initialization loop" compile error.
+func init() {
+       KindBinders[reflect.Int] = IntBinder
+       KindBinders[reflect.Int8] = IntBinder
+       KindBinders[reflect.Int16] = IntBinder
+       KindBinders[reflect.Int32] = IntBinder
+       KindBinders[reflect.Int64] = IntBinder
+
+       KindBinders[reflect.Uint] = UintBinder
+       KindBinders[reflect.Uint8] = UintBinder
+       KindBinders[reflect.Uint16] = UintBinder
+       KindBinders[reflect.Uint32] = UintBinder
+       KindBinders[reflect.Uint64] = UintBinder
+
+       KindBinders[reflect.Float32] = FloatBinder
+       KindBinders[reflect.Float64] = FloatBinder
+
+       KindBinders[reflect.String] = StringBinder
+       KindBinders[reflect.Bool] = BoolBinder
+       KindBinders[reflect.Slice] = Binder{bindSlice, unbindSlice}
+       KindBinders[reflect.Struct] = Binder{bindStruct, unbindStruct}
+       KindBinders[reflect.Ptr] = PointerBinder
+       KindBinders[reflect.Map] = MapBinder
+
+       TypeBinders[reflect.TypeOf(time.Time{})] = TimeBinder
+
+       // Uploads
+       TypeBinders[reflect.TypeOf(&os.File{})] = Binder{bindFile, nil}
+       TypeBinders[reflect.TypeOf([]byte{})] = Binder{bindByteArray, nil}
+       TypeBinders[reflect.TypeOf((*io.Reader)(nil)).Elem()] = Binder{bindReadSeeker, nil}
+       TypeBinders[reflect.TypeOf((*io.ReadSeeker)(nil)).Elem()] = Binder{bindReadSeeker, nil}
+
+       OnAppStart(func() {
+               DateTimeFormat = Config.StringDefault("format.datetime", DefaultDateTimeFormat)
+               DateFormat = Config.StringDefault("format.date", DefaultDateFormat)
+               TimeFormats = append(TimeFormats, DateTimeFormat, DateFormat)
+       })
+}
diff --git a/src/foundation/api/revel/binder_test.go b/src/foundation/api/revel/binder_test.go
new file mode 100644 (file)
index 0000000..5ffd5cd
--- /dev/null
@@ -0,0 +1,419 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "reflect"
+       "sort"
+       "strings"
+       "testing"
+       "time"
+)
+
+type A struct {
+       ID      int
+       Name    string
+       B       B
+       private int
+}
+
+type B struct {
+       Extra string
+}
+
+var (
+       ParamTestValues = map[string][]string{
+               "int":                            {"1"},
+               "int8":                           {"1"},
+               "int16":                          {"1"},
+               "int32":                          {"1"},
+               "int64":                          {"1"},
+               "uint":                           {"1"},
+               "uint8":                          {"1"},
+               "uint16":                         {"1"},
+               "uint32":                         {"1"},
+               "uint64":                         {"1"},
+               "float32":                        {"1.000000"},
+               "float64":                        {"1.000000"},
+               "str":                            {"hello"},
+               "bool-true":                      {"true"},
+               "bool-1":                         {"1"},
+               "bool-on":                        {"on"},
+               "bool-false":                     {"false"},
+               "bool-0":                         {"0"},
+               "bool-0.0":                       {"0.0"},
+               "bool-off":                       {"off"},
+               "bool-f":                         {"f"},
+               "date":                           {"1982-07-09"},
+               "datetime":                       {"1982-07-09 21:30"},
+               "customDate":                     {"07/09/1982"},
+               "arr[0]":                         {"1"},
+               "arr[1]":                         {"2"},
+               "arr[3]":                         {"3"},
+               "uarr[]":                         {"1", "2"},
+               "arruarr[0][]":                   {"1", "2"},
+               "arruarr[1][]":                   {"3", "4"},
+               "2darr[0][0]":                    {"0"},
+               "2darr[0][1]":                    {"1"},
+               "2darr[1][0]":                    {"10"},
+               "2darr[1][1]":                    {"11"},
+               "A.ID":                           {"123"},
+               "A.Name":                         {"rob"},
+               "B.ID":                           {"123"},
+               "B.Name":                         {"rob"},
+               "B.B.Extra":                      {"hello"},
+               "pB.ID":                          {"123"},
+               "pB.Name":                        {"rob"},
+               "pB.B.Extra":                     {"hello"},
+               "priv.private":                   {"123"},
+               "arrC[0].ID":                     {"5"},
+               "arrC[0].Name":                   {"rob"},
+               "arrC[0].B.Extra":                {"foo"},
+               "arrC[1].ID":                     {"8"},
+               "arrC[1].Name":                   {"bill"},
+               "m[a]":                           {"foo"},
+               "m[b]":                           {"bar"},
+               "m2[1]":                          {"foo"},
+               "m2[2]":                          {"bar"},
+               "m3[a]":                          {"1"},
+               "m3[b]":                          {"2"},
+               "m4[a].ID":                       {"1"},
+               "m4[a].Name":                     {"foo"},
+               "m4[b].ID":                       {"2"},
+               "m4[b].Name":                     {"bar"},
+               "mapWithAMuchLongerName[a].ID":   {"1"},
+               "mapWithAMuchLongerName[a].Name": {"foo"},
+               "mapWithAMuchLongerName[b].ID":   {"2"},
+               "mapWithAMuchLongerName[b].Name": {"bar"},
+               "invalidInt":                     {"xyz"},
+               "invalidInt2":                    {""},
+               "invalidBool":                    {"xyz"},
+               "invalidArr":                     {"xyz"},
+               "int8-overflow":                  {"1024"},
+               "uint8-overflow":                 {"1024"},
+       }
+
+       testDate     = time.Date(1982, time.July, 9, 0, 0, 0, 0, time.UTC)
+       testDatetime = time.Date(1982, time.July, 9, 21, 30, 0, 0, time.UTC)
+)
+
+var binderTestCases = map[string]interface{}{
+       "int":        1,
+       "int8":       int8(1),
+       "int16":      int16(1),
+       "int32":      int32(1),
+       "int64":      int64(1),
+       "uint":       1,
+       "uint8":      uint8(1),
+       "uint16":     uint16(1),
+       "uint32":     uint32(1),
+       "uint64":     uint64(1),
+       "float32":    float32(1.0),
+       "float64":    float64(1.0),
+       "str":        "hello",
+       "bool-true":  true,
+       "bool-1":     true,
+       "bool-on":    true,
+       "bool-false": false,
+       "bool-0":     false,
+       "bool-0.0":   false,
+       "bool-off":   false,
+       "bool-f":     false,
+       "date":       testDate,
+       "datetime":   testDatetime,
+       "customDate": testDate,
+       "arr":        []int{1, 2, 0, 3},
+       "uarr":       []int{1, 2},
+       "arruarr":    [][]int{{1, 2}, {3, 4}},
+       "2darr":      [][]int{{0, 1}, {10, 11}},
+       "A":          A{ID: 123, Name: "rob"},
+       "B":          A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+       "pB":         &A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+       "arrC": []A{
+               {
+                       ID:   5,
+                       Name: "rob",
+                       B:    B{"foo"},
+               },
+               {
+                       ID:   8,
+                       Name: "bill",
+               },
+       },
+       "m":  map[string]string{"a": "foo", "b": "bar"},
+       "m2": map[int]string{1: "foo", 2: "bar"},
+       "m3": map[string]int{"a": 1, "b": 2},
+       "m4": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}},
+
+       // NOTE: We also include a map with a longer name than the others since this has caused problems
+       // described in github issue #1285, resolved in pull request #1344. This test case should
+       // prevent regression.
+       "mapWithAMuchLongerName": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}},
+
+       // TODO: Tests that use TypeBinders
+
+       // Invalid value tests (the result should always be the zero value for that type)
+       // The point of these is to ensure that invalid user input does not cause panics.
+       "invalidInt":     0,
+       "invalidInt2":    0,
+       "invalidBool":    true,
+       "invalidArr":     []int{},
+       "priv":           A{},
+       "int8-overflow":  int8(0),
+       "uint8-overflow": uint8(0),
+}
+
+// Types that files may be bound to, and a func that can read the content from
+// that type.
+// TODO: Is there any way to create a slice, given only the element Type?
+var fileBindings = []struct{ val, arrval, f interface{} }{
+       {(**os.File)(nil), []*os.File{}, ioutil.ReadAll},
+       {(*[]byte)(nil), [][]byte{}, func(b []byte) []byte { return b }},
+       {(*io.Reader)(nil), []io.Reader{}, ioutil.ReadAll},
+       {(*io.ReadSeeker)(nil), []io.ReadSeeker{}, ioutil.ReadAll},
+}
+
+func TestJsonBinder(t *testing.T) {
+       // create a structure to be populated
+       {
+               d, _ := json.Marshal(map[string]int{"a": 1})
+               params := &Params{JSON: d}
+               foo := struct{ A int }{}
+               c := NewTestController(nil, getMultipartRequest())
+
+               ParseParams(params, NewRequest(c.Request.In))
+               actual := Bind(params, "test", reflect.TypeOf(foo))
+               valEq(t, "TestJsonBinder", reflect.ValueOf(actual.Interface().(struct{ A int }).A), reflect.ValueOf(1))
+       }
+       {
+               d, _ := json.Marshal(map[string]interface{}{"a": map[string]int{"b": 45}})
+               params := &Params{JSON: d}
+               testMap := map[string]interface{}{}
+               actual := Bind(params, "test", reflect.TypeOf(testMap)).Interface().(map[string]interface{})
+               if actual["a"].(map[string]interface{})["b"].(float64) != 45 {
+                       t.Errorf("Failed to fetch map value %#v", actual["a"])
+               }
+               // Check to see if a named map works
+               actualb := Bind(params, "test", reflect.TypeOf(map[string]map[string]float64{})).Interface().(map[string]map[string]float64)
+               if actualb["a"]["b"] != 45 {
+                       t.Errorf("Failed to fetch map value %#v", actual["a"])
+               }
+
+       }
+}
+
+func TestBinder(t *testing.T) {
+       // Reuse the mvc_test.go multipart request to test the binder.
+       params := &Params{}
+       c := NewTestController(nil, getMultipartRequest())
+       ParseParams(params, NewRequest(c.Request.In))
+       params.Values = ParamTestValues
+
+       // Values
+       for k, v := range binderTestCases {
+               actual := Bind(params, k, reflect.TypeOf(v))
+               expected := reflect.ValueOf(v)
+               valEq(t, k, actual, expected)
+       }
+
+       // Files
+
+       // Get the keys in sorted order to make the expectation right.
+       keys := []string{}
+       for k := range expectedFiles {
+               keys = append(keys, k)
+       }
+       sort.Strings(keys)
+
+       expectedBoundFiles := make(map[string][]fh)
+       for _, k := range keys {
+               fhs := expectedFiles[k]
+               k := nextKey(k)
+               expectedBoundFiles[k] = append(expectedBoundFiles[k], fhs...)
+       }
+
+       for k, fhs := range expectedBoundFiles {
+
+               if len(fhs) == 1 {
+                       // Test binding single files to: *os.File, []byte, io.Reader, io.ReadSeeker
+                       for _, binding := range fileBindings {
+                               typ := reflect.TypeOf(binding.val).Elem()
+                               actual := Bind(params, k, typ)
+                               if !actual.IsValid() || (actual.Kind() == reflect.Interface && actual.IsNil()) {
+                                       t.Errorf("%s (%s) - Returned nil.", k, typ)
+                                       continue
+                               }
+                               returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual})
+                               valEq(t, k, returns[0], reflect.ValueOf(fhs[0].content))
+                       }
+
+               } else {
+                       // Test binding multi to:
+                       // []*os.File, [][]byte, []io.Reader, []io.ReadSeeker
+                       for _, binding := range fileBindings {
+                               typ := reflect.TypeOf(binding.arrval)
+                               actual := Bind(params, k, typ)
+                               if actual.Len() != len(fhs) {
+                                       t.Fatalf("%s (%s) - Number of files: (expected) %d != %d (actual)",
+                                               k, typ, len(fhs), actual.Len())
+                               }
+                               for i := range fhs {
+                                       returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual.Index(i)})
+                                       if !returns[0].IsValid() {
+                                               t.Errorf("%s (%s) - Returned nil.", k, typ)
+                                               continue
+                                       }
+                                       valEq(t, k, returns[0], reflect.ValueOf(fhs[i].content))
+                               }
+                       }
+               }
+       }
+}
+
+// Unbinding tests
+
+var unbinderTestCases = map[string]interface{}{
+       "int":        1,
+       "int8":       int8(1),
+       "int16":      int16(1),
+       "int32":      int32(1),
+       "int64":      int64(1),
+       "uint":       1,
+       "uint8":      uint8(1),
+       "uint16":     uint16(1),
+       "uint32":     uint32(1),
+       "uint64":     uint64(1),
+       "float32":    float32(1.0),
+       "float64":    float64(1.0),
+       "str":        "hello",
+       "bool-true":  true,
+       "bool-false": false,
+       "date":       testDate,
+       "datetime":   testDatetime,
+       "arr":        []int{1, 2, 0, 3},
+       "2darr":      [][]int{{0, 1}, {10, 11}},
+       "A":          A{ID: 123, Name: "rob"},
+       "B":          A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+       "pB":         &A{ID: 123, Name: "rob", B: B{Extra: "hello"}},
+       "arrC": []A{
+               {
+                       ID:   5,
+                       Name: "rob",
+                       B:    B{"foo"},
+               },
+               {
+                       ID:   8,
+                       Name: "bill",
+               },
+       },
+       "m":  map[string]string{"a": "foo", "b": "bar"},
+       "m2": map[int]string{1: "foo", 2: "bar"},
+       "m3": map[string]int{"a": 1, "b": 2},
+}
+
+// Some of the unbinding results are not exactly what is in ParamTestValues, since it
+// serializes implicit zero values explicitly.
+var unbinderOverrideAnswers = map[string]map[string]string{
+       "arr": {
+               "arr[0]": "1",
+               "arr[1]": "2",
+               "arr[2]": "0",
+               "arr[3]": "3",
+       },
+       "A": {
+               "A.ID":      "123",
+               "A.Name":    "rob",
+               "A.B.Extra": "",
+       },
+       "arrC": {
+               "arrC[0].ID":      "5",
+               "arrC[0].Name":    "rob",
+               "arrC[0].B.Extra": "foo",
+               "arrC[1].ID":      "8",
+               "arrC[1].Name":    "bill",
+               "arrC[1].B.Extra": "",
+       },
+       "m":  {"m[a]": "foo", "m[b]": "bar"},
+       "m2": {"m2[1]": "foo", "m2[2]": "bar"},
+       "m3": {"m3[a]": "1", "m3[b]": "2"},
+}
+
+func TestUnbinder(t *testing.T) {
+       for k, v := range unbinderTestCases {
+               actual := make(map[string]string)
+               Unbind(actual, k, v)
+
+               // Get the expected key/values.
+               expected, ok := unbinderOverrideAnswers[k]
+               if !ok {
+                       expected = make(map[string]string)
+                       for k2, v2 := range ParamTestValues {
+                               if k == k2 || strings.HasPrefix(k2, k+".") || strings.HasPrefix(k2, k+"[") {
+                                       expected[k2] = v2[0]
+                               }
+                       }
+               }
+
+               // Compare length and values.
+               if len(actual) != len(expected) {
+                       t.Errorf("Length mismatch\nExpected length %d, actual %d\nExpected: %s\nActual: %s",
+                               len(expected), len(actual), expected, actual)
+               }
+               for k, v := range actual {
+                       if expected[k] != v {
+                               t.Errorf("Value mismatch.\nExpected: %s\nActual: %s", expected, actual)
+                       }
+               }
+       }
+}
+
+// Helpers
+
+func valEq(t *testing.T, name string, actual, expected reflect.Value) {
+       switch expected.Kind() {
+       case reflect.Slice:
+               // Check the type/length/element type
+               if !eq(t, name+" (type)", actual.Kind(), expected.Kind()) ||
+                       !eq(t, name+" (len)", actual.Len(), expected.Len()) ||
+                       !eq(t, name+" (elem)", actual.Type().Elem(), expected.Type().Elem()) {
+                       return
+               }
+
+               // Check value equality for each element.
+               for i := 0; i < actual.Len(); i++ {
+                       valEq(t, fmt.Sprintf("%s[%d]", name, i), actual.Index(i), expected.Index(i))
+               }
+
+       case reflect.Ptr:
+               // Check equality on the element type.
+               valEq(t, name, actual.Elem(), expected.Elem())
+       case reflect.Map:
+               if !eq(t, name+" (len)", actual.Len(), expected.Len()) {
+                       return
+               }
+               for _, key := range expected.MapKeys() {
+                       expectedValue := expected.MapIndex(key)
+                       actualValue := actual.MapIndex(key)
+                       if actualValue.IsValid() {
+                               valEq(t, fmt.Sprintf("%s[%s]", name, key), actualValue, expectedValue)
+                       } else {
+                               t.Errorf("Expected key %s not found", key)
+                       }
+               }
+       default:
+               eq(t, name, actual.Interface(), expected.Interface())
+       }
+}
+
+func init() {
+       DateFormat = DefaultDateFormat
+       DateTimeFormat = DefaultDateTimeFormat
+       TimeFormats = append(TimeFormats, DefaultDateFormat, DefaultDateTimeFormat, "01/02/2006")
+}
diff --git a/src/foundation/api/revel/cache/cache.go b/src/foundation/api/revel/cache/cache.go
new file mode 100644 (file)
index 0000000..7406004
--- /dev/null
@@ -0,0 +1,145 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "errors"
+       "time"
+)
+
+// Length of time to cache an item.
+const (
+       DefaultExpiryTime  = time.Duration(0)
+       ForEverNeverExpiry = time.Duration(-1)
+)
+
+// Getter is an interface for getting / decoding an element from a cache.
+type Getter interface {
+       // Get the content associated with the given key. decoding it into the given
+       // pointer.
+       //
+       // Returns:
+       //   - nil if the value was successfully retrieved and ptrValue set
+       //   - ErrCacheMiss if the value was not in the cache
+       //   - an implementation specific error otherwise
+       Get(key string, ptrValue interface{}) error
+}
+
+// Cache is an interface to an expiring cache.  It behaves (and is modeled) like
+// the Memcached interface.  It is keyed by strings (250 bytes at most).
+//
+// Many callers will make exclusive use of Set and Get, but more exotic
+// functions are also available.
+//
+// Example
+//
+// Here is a typical Get/Set interaction:
+//
+//   var items []*Item
+//   if err := cache.Get("items", &items); err != nil {
+//     items = loadItems()
+//     go cache.Set("items", items, cache.DefaultExpiryTime)
+//   }
+//
+// Note that the caller will frequently not wait for Set() to complete.
+//
+// Errors
+//
+// It is assumed that callers will infrequently check returned errors, since any
+// request should be fulfillable without finding anything in the cache.  As a
+// result, all errors other than ErrCacheMiss and ErrNotStored will be logged to
+// revel.ERROR, so that the developer does not need to check the return value to
+// discover things like deserialization or connection errors.
+type Cache interface {
+       // The Cache implements a Getter.
+       Getter
+
+       // Set the given key/value in the cache, overwriting any existing value
+       // associated with that key.  Keys may be at most 250 bytes in length.
+       //
+       // Returns:
+       //   - nil on success
+       //   - an implementation specific error otherwise
+       Set(key string, value interface{}, expires time.Duration) error
+
+       // Get the content associated multiple keys at once.  On success, the caller
+       // may decode the values one at a time from the returned Getter.
+       //
+       // Returns:
+       //   - the value getter, and a nil error if the operation completed.
+       //   - an implementation specific error otherwise
+       GetMulti(keys ...string) (Getter, error)
+
+       // Delete the given key from the cache.
+       //
+       // Returns:
+       //   - nil on a successful delete
+       //   - ErrCacheMiss if the value was not in the cache
+       //   - an implementation specific error otherwise
+       Delete(key string) error
+
+       // Add the given key/value to the cache ONLY IF the key does not already exist.
+       //
+       // Returns:
+       //   - nil if the value was added to the cache
+       //   - ErrNotStored if the key was already present in the cache
+       //   - an implementation-specific error otherwise
+       Add(key string, value interface{}, expires time.Duration) error
+
+       // Set the given key/value in the cache ONLY IF the key already exists.
+       //
+       // Returns:
+       //   - nil if the value was replaced
+       //   - ErrNotStored if the key does not exist in the cache
+       //   - an implementation specific error otherwise
+       Replace(key string, value interface{}, expires time.Duration) error
+
+       // Increment the value stored at the given key by the given amount.
+       // The value silently wraps around upon exceeding the uint64 range.
+       //
+       // Returns the new counter value if the operation was successful, or:
+       //   - ErrCacheMiss if the key was not found in the cache
+       //   - an implementation specific error otherwise
+       Increment(key string, n uint64) (newValue uint64, err error)
+
+       // Decrement the value stored at the given key by the given amount.
+       // The value is capped at 0 on underflow, with no error returned.
+       //
+       // Returns the new counter value if the operation was successful, or:
+       //   - ErrCacheMiss if the key was not found in the cache
+       //   - an implementation specific error otherwise
+       Decrement(key string, n uint64) (newValue uint64, err error)
+
+       // Expire all cache entries immediately.
+       // This is not implemented for the memcached cache (intentionally).
+       // Returns an implementation specific error if the operation failed.
+       Flush() error
+}
+
+var (
+       Instance Cache
+
+       ErrCacheMiss    = errors.New("revel/cache: key not found")
+       ErrNotStored    = errors.New("revel/cache: not stored")
+       ErrInvalidValue = errors.New("revel/cache: invalid value")
+)
+
+// The package implements the Cache interface (as sugar).
+
+func Get(key string, ptrValue interface{}) error                  { return Instance.Get(key, ptrValue) }
+func GetMulti(keys ...string) (Getter, error)                     { return Instance.GetMulti(keys...) }
+func Delete(key string) error                                     { return Instance.Delete(key) }
+func Increment(key string, n uint64) (newValue uint64, err error) { return Instance.Increment(key, n) }
+func Decrement(key string, n uint64) (newValue uint64, err error) { return Instance.Decrement(key, n) }
+func Flush() error                                                { return Instance.Flush() }
+func Set(key string, value interface{}, expires time.Duration) error {
+       return Instance.Set(key, value, expires)
+}
+func Add(key string, value interface{}, expires time.Duration) error {
+       return Instance.Add(key, value, expires)
+}
+func Replace(key string, value interface{}, expires time.Duration) error {
+       return Instance.Replace(key, value, expires)
+}
diff --git a/src/foundation/api/revel/cache/cache_test.go b/src/foundation/api/revel/cache/cache_test.go
new file mode 100644 (file)
index 0000000..bc54617
--- /dev/null
@@ -0,0 +1,253 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "math"
+       "testing"
+       "time"
+)
+
+// Tests against a generic Cache interface.
+// They should pass for all implementations.
+type cacheFactory func(*testing.T, time.Duration) Cache
+
+// Test typical cache interactions
+func typicalGetSet(t *testing.T, newCache cacheFactory) {
+       var err error
+       cache := newCache(t, time.Hour)
+
+       value := "foo"
+       if err = cache.Set("value", value, DefaultExpiryTime); err != nil {
+               t.Errorf("Error setting a value: %s", err)
+       }
+
+       value = ""
+       err = cache.Get("value", &value)
+       if err != nil {
+               t.Errorf("Error getting a value: %s", err)
+       }
+       if value != "foo" {
+               t.Errorf("Expected to get foo back, got %s", value)
+       }
+}
+
+// Test the increment-decrement cases
+func incrDecr(t *testing.T, newCache cacheFactory) {
+       var err error
+       cache := newCache(t, time.Hour)
+
+       // Normal increment / decrement operation.
+       if err = cache.Set("int", 10, ForEverNeverExpiry); err != nil {
+               t.Errorf("Error setting int: %s", err)
+       }
+       time.Sleep(time.Second)
+       newValue, err := cache.Increment("int", 50)
+       if err != nil {
+               t.Errorf("Error incrementing int: %s", err)
+       }
+       if newValue != 60 {
+               t.Errorf("Expected 60, was %d", newValue)
+       }
+
+       if newValue, err = cache.Decrement("int", 50); err != nil {
+               t.Errorf("Error decrementing: %s", err)
+       }
+       if newValue != 10 {
+               t.Errorf("Expected 10, was %d", newValue)
+       }
+
+       // Increment wraparound
+       newValue, err = cache.Increment("int", math.MaxUint64-5)
+       if err != nil {
+               t.Errorf("Error wrapping around: %s", err)
+       }
+       if newValue != 4 {
+               t.Errorf("Expected wraparound 4, got %d", newValue)
+       }
+
+       // Decrement capped at 0
+       newValue, err = cache.Decrement("int", 25)
+       if err != nil {
+               t.Errorf("Error decrementing below 0: %s", err)
+       }
+       if newValue != 0 {
+               t.Errorf("Expected capped at 0, got %d", newValue)
+       }
+}
+
+func expiration(t *testing.T, newCache cacheFactory) {
+       // memcached does not support expiration times less than 1 second.
+       var err error
+       cache := newCache(t, time.Second)
+       // Test Set w/ DefaultExpiryTime
+       value := 10
+       if err = cache.Set("int", value, DefaultExpiryTime); err != nil {
+               t.Errorf("Set failed: %s", err)
+       }
+       time.Sleep(2 * time.Second)
+       if err = cache.Get("int", &value); err != ErrCacheMiss {
+               t.Errorf("Expected CacheMiss, but got: %s", err)
+       }
+
+       // Test Set w/ short time
+       if err = cache.Set("int", value, time.Second); err != nil {
+               t.Errorf("Set failed: %s", err)
+       }
+       time.Sleep(2 * time.Second)
+       if err = cache.Get("int", &value); err != ErrCacheMiss {
+               t.Errorf("Expected CacheMiss, but got: %s", err)
+       }
+
+       // Test Set w/ longer time.
+       if err = cache.Set("int", value, time.Hour); err != nil {
+               t.Errorf("Set failed: %s", err)
+       }
+       time.Sleep(2 * time.Second)
+       if err = cache.Get("int", &value); err != nil {
+               t.Errorf("Expected to get the value, but got: %s", err)
+       }
+
+       // Test Set w/ forever.
+       if err = cache.Set("int", value, ForEverNeverExpiry); err != nil {
+               t.Errorf("Set failed: %s", err)
+       }
+       time.Sleep(2 * time.Second)
+       if err = cache.Get("int", &value); err != nil {
+               t.Errorf("Expected to get the value, but got: %s", err)
+       }
+}
+
+func emptyCache(t *testing.T, newCache cacheFactory) {
+       var err error
+       cache := newCache(t, time.Hour)
+
+       err = cache.Get("notexist", 0)
+       if err == nil {
+               t.Errorf("Error expected for non-existent key")
+       }
+       if err != ErrCacheMiss {
+               t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err)
+       }
+
+       err = cache.Delete("notexist")
+       if err != ErrCacheMiss {
+               t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err)
+       }
+
+       _, err = cache.Increment("notexist", 1)
+       if err != ErrCacheMiss {
+               t.Errorf("Expected cache miss incrementing non-existent key: %s", err)
+       }
+
+       _, err = cache.Decrement("notexist", 1)
+       if err != ErrCacheMiss {
+               t.Errorf("Expected cache miss decrementing non-existent key: %s", err)
+       }
+}
+
+func testReplace(t *testing.T, newCache cacheFactory) {
+       var err error
+       cache := newCache(t, time.Hour)
+
+       // Replace in an empty cache.
+       if err = cache.Replace("notexist", 1, ForEverNeverExpiry); err != ErrNotStored {
+               t.Errorf("Replace in empty cache: expected ErrNotStored, got: %s", err)
+       }
+
+       // Set a value of 1, and replace it with 2
+       if err = cache.Set("int", 1, time.Second); err != nil {
+               t.Errorf("Unexpected error: %s", err)
+       }
+
+       if err = cache.Replace("int", 2, time.Second); err != nil {
+               t.Errorf("Unexpected error: %s", err)
+       }
+       var i int
+       if err = cache.Get("int", &i); err != nil {
+               t.Errorf("Unexpected error getting a replaced item: %s", err)
+       }
+       if i != 2 {
+               t.Errorf("Expected 2, got %d", i)
+       }
+
+       // Wait for it to expire and replace with 3 (unsuccessfully).
+       time.Sleep(2 * time.Second)
+       if err = cache.Replace("int", 3, time.Second); err != ErrNotStored {
+               t.Errorf("Expected ErrNotStored, got: %s", err)
+       }
+       if err = cache.Get("int", &i); err != ErrCacheMiss {
+               t.Errorf("Expected cache miss, got: %s", err)
+       }
+}
+
+func testAdd(t *testing.T, newCache cacheFactory) {
+       var err error
+       cache := newCache(t, time.Hour)
+       // Add to an empty cache.
+       if err = cache.Add("int", 1, time.Second*3); err != nil {
+               t.Errorf("Unexpected error adding to empty cache: %s", err)
+       }
+
+       // Try to add again. (fail)
+       if err = cache.Add("int", 2, time.Second*3); err != nil {
+               if err != ErrNotStored {
+                       t.Errorf("Expected ErrNotStored adding dupe to cache: %s", err)
+               }
+       }
+
+       // Wait for it to expire, and add again.
+       time.Sleep(8 * time.Second)
+       if err = cache.Add("int", 3, time.Second*5); err != nil {
+               t.Errorf("Unexpected error adding to cache: %s", err)
+       }
+
+       // Get and verify the value.
+       var i int
+       if err = cache.Get("int", &i); err != nil {
+               t.Errorf("Unexpected error: %s", err)
+       }
+       if i != 3 {
+               t.Errorf("Expected 3, got: %d", i)
+       }
+}
+
+func testGetMulti(t *testing.T, newCache cacheFactory) {
+       cache := newCache(t, time.Hour)
+
+       m := map[string]interface{}{
+               "str": "foo",
+               "num": 42,
+               "foo": struct{ Bar string }{"baz"},
+       }
+
+       var keys []string
+       for key, value := range m {
+               keys = append(keys, key)
+               if err := cache.Set(key, value, time.Second*30); err != nil {
+                       t.Errorf("Error setting a value: %s", err)
+               }
+       }
+
+       g, err := cache.GetMulti(keys...)
+       if err != nil {
+               t.Errorf("Error in get-multi: %s", err)
+       }
+
+       var str string
+       if err = g.Get("str", &str); err != nil || str != "foo" {
+               t.Errorf("Error getting str: %s / %s", err, str)
+       }
+
+       var num int
+       if err = g.Get("num", &num); err != nil || num != 42 {
+               t.Errorf("Error getting num: %s / %v", err, num)
+       }
+
+       var foo struct{ Bar string }
+       if err = g.Get("foo", &foo); err != nil || foo.Bar != "baz" {
+               t.Errorf("Error getting foo: %s / %v", err, foo)
+       }
+}
diff --git a/src/foundation/api/revel/cache/init.go b/src/foundation/api/revel/cache/init.go
new file mode 100644 (file)
index 0000000..0008a6a
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "strings"
+       "time"
+
+       "github.com/revel/revel"
+)
+
+var cacheLog = revel.RevelLog.New("section", "cache")
+
+func init() {
+       revel.OnAppStart(func() {
+               // Set the default expiration time.
+               defaultExpiration := time.Hour // The default for the default is one hour.
+               if expireStr, found := revel.Config.String("cache.expires"); found {
+                       var err error
+                       if defaultExpiration, err = time.ParseDuration(expireStr); err != nil {
+                               cacheLog.Panic("Could not parse default cache expiration duration " + expireStr + ": " + err.Error())
+                       }
+               }
+
+               // make sure you aren't trying to use both memcached and redis
+               if revel.Config.BoolDefault("cache.memcached", false) && revel.Config.BoolDefault("cache.redis", false) {
+                       cacheLog.Panic("You've configured both memcached and redis, please only include configuration for one cache!")
+               }
+
+               // Use memcached?
+               if revel.Config.BoolDefault("cache.memcached", false) {
+                       hosts := strings.Split(revel.Config.StringDefault("cache.hosts", ""), ",")
+                       if len(hosts) == 0 {
+                               cacheLog.Panic("Memcache enabled but no memcached hosts specified!")
+                       }
+
+                       Instance = NewMemcachedCache(hosts, defaultExpiration)
+                       return
+               }
+
+               // Use Redis (share same config as memcached)?
+               if revel.Config.BoolDefault("cache.redis", false) {
+                       hosts := strings.Split(revel.Config.StringDefault("cache.hosts", ""), ",")
+                       if len(hosts) == 0 {
+                               cacheLog.Panic("Redis enabled but no Redis hosts specified!")
+                       }
+                       if len(hosts) > 1 {
+                               cacheLog.Panic("Redis currently only supports one host!")
+                       }
+                       password := revel.Config.StringDefault("cache.redis.password", "")
+                       Instance = NewRedisCache(hosts[0], password, defaultExpiration)
+                       return
+               }
+
+               // By default, use the in-memory cache.
+               Instance = NewInMemoryCache(defaultExpiration)
+       })
+}
diff --git a/src/foundation/api/revel/cache/inmemory.go b/src/foundation/api/revel/cache/inmemory.go
new file mode 100644 (file)
index 0000000..8257f97
--- /dev/null
@@ -0,0 +1,163 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "fmt"
+       "reflect"
+       "time"
+
+       "github.com/patrickmn/go-cache"
+       "sync"
+)
+
+type InMemoryCache struct {
+       cache cache.Cache  // Only expose the methods we want to make available
+       mu    sync.RWMutex // For increment / decrement prevent reads and writes
+}
+
+func NewInMemoryCache(defaultExpiration time.Duration) InMemoryCache {
+       return InMemoryCache{cache: *cache.New(defaultExpiration, time.Minute), mu: sync.RWMutex{}}
+}
+
+func (c InMemoryCache) Get(key string, ptrValue interface{}) error {
+       c.mu.RLock()
+       defer c.mu.RUnlock()
+
+       value, found := c.cache.Get(key)
+       if !found {
+               return ErrCacheMiss
+       }
+
+       v := reflect.ValueOf(ptrValue)
+       if v.Type().Kind() == reflect.Ptr && v.Elem().CanSet() {
+               v.Elem().Set(reflect.ValueOf(value))
+               return nil
+       }
+
+       err := fmt.Errorf("revel/cache: attempt to get %s, but can not set value %v", key, v)
+       cacheLog.Error(err.Error())
+       return err
+}
+
+func (c InMemoryCache) GetMulti(keys ...string) (Getter, error) {
+       return c, nil
+}
+
+func (c InMemoryCache) Set(key string, value interface{}, expires time.Duration) error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       // NOTE: go-cache understands the values of DefaultExpiryTime and ForEverNeverExpiry
+       c.cache.Set(key, value, expires)
+       return nil
+}
+
+func (c InMemoryCache) Add(key string, value interface{}, expires time.Duration) error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       err := c.cache.Add(key, value, expires)
+       if err != nil {
+               return ErrNotStored
+       }
+       return err
+}
+
+func (c InMemoryCache) Replace(key string, value interface{}, expires time.Duration) error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       if err := c.cache.Replace(key, value, expires); err != nil {
+               return ErrNotStored
+       }
+       return nil
+}
+
+func (c InMemoryCache) Delete(key string) error {
+       c.mu.RLock()
+       defer c.mu.RUnlock()
+       if _, found := c.cache.Get(key); !found {
+               return ErrCacheMiss
+       }
+       c.cache.Delete(key)
+       return nil
+}
+
+func (c InMemoryCache) Increment(key string, n uint64) (newValue uint64, err error) {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       if _, found := c.cache.Get(key); !found {
+               return 0, ErrCacheMiss
+       }
+       if err = c.cache.Increment(key, int64(n)); err != nil {
+               return
+       }
+
+       return c.convertTypeToUint64(key)
+}
+
+func (c InMemoryCache) Decrement(key string, n uint64) (newValue uint64, err error) {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+       if nv, err := c.convertTypeToUint64(key); err != nil {
+               return 0, err
+       } else {
+               // Stop from going below zero
+               if n > nv {
+                       n = nv
+               }
+       }
+       if err = c.cache.Decrement(key, int64(n)); err != nil {
+               return
+       }
+
+       return c.convertTypeToUint64(key)
+}
+
+func (c InMemoryCache) Flush() error {
+       c.mu.Lock()
+       defer c.mu.Unlock()
+
+       c.cache.Flush()
+       return nil
+}
+
+// Fetches and returns the converted type to a uint64
+func (c InMemoryCache) convertTypeToUint64(key string) (newValue uint64, err error) {
+       v, found := c.cache.Get(key)
+       if !found {
+               return newValue, ErrCacheMiss
+       }
+
+       switch v.(type) {
+       case int:
+               newValue = uint64(v.(int))
+       case int8:
+               newValue = uint64(v.(int8))
+       case int16:
+               newValue = uint64(v.(int16))
+       case int32:
+               newValue = uint64(v.(int32))
+       case int64:
+               newValue = uint64(v.(int64))
+       case uint:
+               newValue = uint64(v.(uint))
+       case uintptr:
+               newValue = uint64(v.(uintptr))
+       case uint8:
+               newValue = uint64(v.(uint8))
+       case uint16:
+               newValue = uint64(v.(uint16))
+       case uint32:
+               newValue = uint64(v.(uint32))
+       case uint64:
+               newValue = uint64(v.(uint64))
+       case float32:
+               newValue = uint64(v.(float32))
+       case float64:
+               newValue = uint64(v.(float64))
+       default:
+               err = ErrInvalidValue
+       }
+       return
+}
diff --git a/src/foundation/api/revel/cache/inmemory_test.go b/src/foundation/api/revel/cache/inmemory_test.go
new file mode 100644 (file)
index 0000000..1f9cf1f
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "testing"
+       "time"
+)
+
+var newInMemoryCache = func(_ *testing.T, defaultExpiration time.Duration) Cache {
+       return NewInMemoryCache(defaultExpiration)
+}
+
+// Test typical cache interactions
+func TestInMemoryCache_TypicalGetSet(t *testing.T) {
+       typicalGetSet(t, newInMemoryCache)
+}
+
+// Test the increment-decrement cases
+func TestInMemoryCache_IncrDecr(t *testing.T) {
+       incrDecr(t, newInMemoryCache)
+}
+
+func TestInMemoryCache_Expiration(t *testing.T) {
+       expiration(t, newInMemoryCache)
+}
+
+func TestInMemoryCache_EmptyCache(t *testing.T) {
+       emptyCache(t, newInMemoryCache)
+}
+
+func TestInMemoryCache_Replace(t *testing.T) {
+       testReplace(t, newInMemoryCache)
+}
+
+func TestInMemoryCache_Add(t *testing.T) {
+       testAdd(t, newInMemoryCache)
+}
+
+func TestInMemoryCache_GetMulti(t *testing.T) {
+       testGetMulti(t, newInMemoryCache)
+}
diff --git a/src/foundation/api/revel/cache/memcached.go b/src/foundation/api/revel/cache/memcached.go
new file mode 100644 (file)
index 0000000..fbc7ece
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "errors"
+       "time"
+
+       "github.com/bradfitz/gomemcache/memcache"
+       "github.com/revel/revel/logger"
+)
+
+// MemcachedCache wraps the Memcached client to meet the Cache interface.
+type MemcachedCache struct {
+       *memcache.Client
+       defaultExpiration time.Duration
+}
+
+func NewMemcachedCache(hostList []string, defaultExpiration time.Duration) MemcachedCache {
+       return MemcachedCache{memcache.New(hostList...), defaultExpiration}
+}
+
+func (c MemcachedCache) Set(key string, value interface{}, expires time.Duration) error {
+       return c.invoke((*memcache.Client).Set, key, value, expires)
+}
+
+func (c MemcachedCache) Add(key string, value interface{}, expires time.Duration) error {
+       return c.invoke((*memcache.Client).Add, key, value, expires)
+}
+
+func (c MemcachedCache) Replace(key string, value interface{}, expires time.Duration) error {
+       return c.invoke((*memcache.Client).Replace, key, value, expires)
+}
+
+func (c MemcachedCache) Get(key string, ptrValue interface{}) error {
+       item, err := c.Client.Get(key)
+       if err != nil {
+               return convertMemcacheError(err)
+       }
+       return Deserialize(item.Value, ptrValue)
+}
+
+func (c MemcachedCache) GetMulti(keys ...string) (Getter, error) {
+       items, err := c.Client.GetMulti(keys)
+       if err != nil {
+               return nil, convertMemcacheError(err)
+       }
+       return ItemMapGetter(items), nil
+}
+
+func (c MemcachedCache) Delete(key string) error {
+       return convertMemcacheError(c.Client.Delete(key))
+}
+
+func (c MemcachedCache) Increment(key string, delta uint64) (newValue uint64, err error) {
+       newValue, err = c.Client.Increment(key, delta)
+       return newValue, convertMemcacheError(err)
+}
+
+func (c MemcachedCache) Decrement(key string, delta uint64) (newValue uint64, err error) {
+       newValue, err = c.Client.Decrement(key, delta)
+       return newValue, convertMemcacheError(err)
+}
+
+func (c MemcachedCache) Flush() error {
+       err := errors.New("Flush: can not flush memcached")
+       cacheLog.Error(err.Error())
+       return err
+}
+
+func (c MemcachedCache) invoke(f func(*memcache.Client, *memcache.Item) error,
+       key string, value interface{}, expires time.Duration) error {
+
+       switch expires {
+       case DefaultExpiryTime:
+               expires = c.defaultExpiration
+       case ForEverNeverExpiry:
+               expires = time.Duration(0)
+       }
+
+       b, err := Serialize(value)
+       if err != nil {
+               return err
+       }
+       return convertMemcacheError(f(c.Client, &memcache.Item{
+               Key:        key,
+               Value:      b,
+               Expiration: int32(expires / time.Second),
+       }))
+}
+
+// ItemMapGetter implements a Getter on top of the returned item map.
+type ItemMapGetter map[string]*memcache.Item
+
+func (g ItemMapGetter) Get(key string, ptrValue interface{}) error {
+       item, ok := g[key]
+       if !ok {
+               return ErrCacheMiss
+       }
+
+       return Deserialize(item.Value, ptrValue)
+}
+
+func convertMemcacheError(err error) error {
+       switch err {
+       case nil:
+               return nil
+       case memcache.ErrCacheMiss:
+               return ErrCacheMiss
+       case memcache.ErrNotStored:
+               return ErrNotStored
+       }
+
+       cacheLog.Error("convertMemcacheError:", "error", err, "trace", logger.NewCallStack())
+       return err
+}
diff --git a/src/foundation/api/revel/cache/memcached_test.go b/src/foundation/api/revel/cache/memcached_test.go
new file mode 100644 (file)
index 0000000..75376e0
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "net"
+       "testing"
+       "time"
+)
+
+// These tests require memcached running on localhost:11211 (the default)
+const testServer = "localhost:11211"
+
+var newMemcachedCache = func(t *testing.T, defaultExpiration time.Duration) Cache {
+       c, err := net.Dial("tcp", testServer)
+       if err == nil {
+               if _, err = c.Write([]byte("flush_all\r\n")); err != nil {
+                       t.Errorf("Write failed: %s", err)
+               }
+               _ = c.Close()
+               return NewMemcachedCache([]string{testServer}, defaultExpiration)
+       }
+       t.Errorf("couldn't connect to memcached on %s", testServer)
+       t.FailNow()
+       panic("")
+}
+
+func TestMemcachedCache_TypicalGetSet(t *testing.T) {
+       typicalGetSet(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_IncrDecr(t *testing.T) {
+       incrDecr(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_Expiration(t *testing.T) {
+       expiration(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_EmptyCache(t *testing.T) {
+       emptyCache(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_Replace(t *testing.T) {
+       testReplace(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_Add(t *testing.T) {
+       testAdd(t, newMemcachedCache)
+}
+
+func TestMemcachedCache_GetMulti(t *testing.T) {
+       testGetMulti(t, newMemcachedCache)
+}
diff --git a/src/foundation/api/revel/cache/redis.go b/src/foundation/api/revel/cache/redis.go
new file mode 100644 (file)
index 0000000..62aafc4
--- /dev/null
@@ -0,0 +1,273 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "time"
+
+       "github.com/garyburd/redigo/redis"
+       "github.com/revel/revel"
+)
+
+// RedisCache wraps the Redis client to meet the Cache interface.
+type RedisCache struct {
+       pool              *redis.Pool
+       defaultExpiration time.Duration
+}
+
+// NewRedisCache returns a new RedisCache with given parameters
+// until redigo supports sharding/clustering, only one host will be in hostList
+func NewRedisCache(host string, password string, defaultExpiration time.Duration) RedisCache {
+       var pool = &redis.Pool{
+               MaxIdle:     revel.Config.IntDefault("cache.redis.maxidle", 5),
+               MaxActive:   revel.Config.IntDefault("cache.redis.maxactive", 0),
+               IdleTimeout: time.Duration(revel.Config.IntDefault("cache.redis.idletimeout", 240)) * time.Second,
+               Dial: func() (redis.Conn, error) {
+                       protocol := revel.Config.StringDefault("cache.redis.protocol", "tcp")
+                       toc := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.connect", 10000))
+                       tor := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.read", 5000))
+                       tow := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.write", 5000))
+                       c, err := redis.Dial(protocol, host,
+                               redis.DialConnectTimeout(toc),
+                               redis.DialReadTimeout(tor),
+                               redis.DialWriteTimeout(tow))
+                       if err != nil {
+                               return nil, err
+                       }
+                       if len(password) > 0 {
+                               if _, err = c.Do("AUTH", password); err != nil {
+                                       _ = c.Close()
+                                       return nil, err
+                               }
+                       } else {
+                               // check with PING
+                               if _, err = c.Do("PING"); err != nil {
+                                       _ = c.Close()
+                                       return nil, err
+                               }
+                       }
+                       return c, err
+               },
+               // custom connection test method
+               TestOnBorrow: func(c redis.Conn, t time.Time) error {
+                       _, err := c.Do("PING")
+                       return err
+               },
+       }
+       return RedisCache{pool, defaultExpiration}
+}
+
+func (c RedisCache) Set(key string, value interface{}, expires time.Duration) error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       return c.invoke(conn.Do, key, value, expires)
+}
+
+func (c RedisCache) Add(key string, value interface{}, expires time.Duration) error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+
+       existed, err := exists(conn, key)
+       if err != nil {
+               return err
+       } else if existed {
+               return ErrNotStored
+       }
+       return c.invoke(conn.Do, key, value, expires)
+}
+
+func (c RedisCache) Replace(key string, value interface{}, expires time.Duration) error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+
+       existed, err := exists(conn, key)
+       if err != nil {
+               return err
+       } else if !existed {
+               return ErrNotStored
+       }
+
+       err = c.invoke(conn.Do, key, value, expires)
+       if value == nil {
+               return ErrNotStored
+       }
+       return err
+}
+
+func (c RedisCache) Get(key string, ptrValue interface{}) error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       raw, err := conn.Do("GET", key)
+       if err != nil {
+               return err
+       } else if raw == nil {
+               return ErrCacheMiss
+       }
+       item, err := redis.Bytes(raw, err)
+       if err != nil {
+               return err
+       }
+       return Deserialize(item, ptrValue)
+}
+
+func generalizeStringSlice(strs []string) []interface{} {
+       ret := make([]interface{}, len(strs))
+       for i, str := range strs {
+               ret[i] = str
+       }
+       return ret
+}
+
+func (c RedisCache) GetMulti(keys ...string) (Getter, error) {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+
+       items, err := redis.Values(conn.Do("MGET", generalizeStringSlice(keys)...))
+       if err != nil {
+               return nil, err
+       } else if items == nil {
+               return nil, ErrCacheMiss
+       }
+
+       m := make(map[string][]byte)
+       for i, key := range keys {
+               m[key] = nil
+               if i < len(items) && items[i] != nil {
+                       s, ok := items[i].([]byte)
+                       if ok {
+                               m[key] = s
+                       }
+               }
+       }
+       return RedisItemMapGetter(m), nil
+}
+
+func exists(conn redis.Conn, key string) (bool, error) {
+       return redis.Bool(conn.Do("EXISTS", key))
+}
+
+func (c RedisCache) Delete(key string) error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       existed, err := redis.Bool(conn.Do("DEL", key))
+       if err == nil && !existed {
+               err = ErrCacheMiss
+       }
+       return err
+}
+
+func (c RedisCache) Increment(key string, delta uint64) (uint64, error) {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       // Check for existence *before* increment as per the cache contract.
+       // redis will auto create the key, and we don't want that. Since we need to do increment
+       // ourselves instead of natively via INCRBY (redis doesn't support wrapping), we get the value
+       // and do the exists check this way to minimize calls to Redis
+       val, err := conn.Do("GET", key)
+       if err != nil {
+               return 0, err
+       } else if val == nil {
+               return 0, ErrCacheMiss
+       }
+       currentVal, err := redis.Int64(val, nil)
+       if err != nil {
+               return 0, err
+       }
+       sum := currentVal + int64(delta)
+       _, err = conn.Do("SET", key, sum)
+       if err != nil {
+               return 0, err
+       }
+       return uint64(sum), nil
+}
+
+func (c RedisCache) Decrement(key string, delta uint64) (newValue uint64, err error) {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       // Check for existence *before* increment as per the cache contract.
+       // redis will auto create the key, and we don't want that, hence the exists call
+       existed, err := exists(conn, key)
+       if err != nil {
+               return 0, err
+       } else if !existed {
+               return 0, ErrCacheMiss
+       }
+       // Decrement contract says you can only go to 0
+       // so we go fetch the value and if the delta is greater than the amount,
+       // 0 out the value
+       currentVal, err := redis.Int64(conn.Do("GET", key))
+       if err != nil {
+               return 0, err
+       }
+       if delta > uint64(currentVal) {
+               var tempint int64
+               tempint, err = redis.Int64(conn.Do("DECRBY", key, currentVal))
+               return uint64(tempint), err
+       }
+       tempint, err := redis.Int64(conn.Do("DECRBY", key, delta))
+       return uint64(tempint), err
+}
+
+func (c RedisCache) Flush() error {
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       _, err := conn.Do("FLUSHALL")
+       return err
+}
+
+func (c RedisCache) invoke(f func(string, ...interface{}) (interface{}, error),
+       key string, value interface{}, expires time.Duration) error {
+
+       switch expires {
+       case DefaultExpiryTime:
+               expires = c.defaultExpiration
+       case ForEverNeverExpiry:
+               expires = time.Duration(0)
+       }
+
+       b, err := Serialize(value)
+       if err != nil {
+               return err
+       }
+       conn := c.pool.Get()
+       defer func() {
+               _ = conn.Close()
+       }()
+       if expires > 0 {
+               _, err = f("SETEX", key, int32(expires/time.Second), b)
+               return err
+       }
+       _, err = f("SET", key, b)
+       return err
+}
+
+// RedisItemMapGetter implements a Getter on top of the returned item map.
+type RedisItemMapGetter map[string][]byte
+
+func (g RedisItemMapGetter) Get(key string, ptrValue interface{}) error {
+       item, ok := g[key]
+       if !ok {
+               return ErrCacheMiss
+       }
+       return Deserialize(item, ptrValue)
+}
diff --git a/src/foundation/api/revel/cache/redis_test.go b/src/foundation/api/revel/cache/redis_test.go
new file mode 100644 (file)
index 0000000..ad0b006
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "net"
+       "testing"
+       "time"
+
+       "github.com/revel/config"
+       "github.com/revel/revel"
+)
+
+// These tests require redis server running on localhost:6379 (the default)
+const redisTestServer = "localhost:6379"
+
+var newRedisCache = func(t *testing.T, defaultExpiration time.Duration) Cache {
+       revel.Config = config.NewContext()
+
+       c, err := net.Dial("tcp", redisTestServer)
+       if err == nil {
+               if _, err = c.Write([]byte("flush_all\r\n")); err != nil {
+                       t.Errorf("Write failed: %s", err)
+               }
+               _ = c.Close()
+
+               redisCache := NewRedisCache(redisTestServer, "", defaultExpiration)
+               if err = redisCache.Flush(); err != nil {
+                       t.Errorf("Flush failed: %s", err)
+               }
+               return redisCache
+       }
+       t.Errorf("couldn't connect to redis on %s", redisTestServer)
+       t.FailNow()
+       panic("")
+}
+
+func TestRedisCache_TypicalGetSet(t *testing.T) {
+       typicalGetSet(t, newRedisCache)
+}
+
+func TestRedisCache_IncrDecr(t *testing.T) {
+       incrDecr(t, newRedisCache)
+}
+
+func TestRedisCache_Expiration(t *testing.T) {
+       expiration(t, newRedisCache)
+}
+
+func TestRedisCache_EmptyCache(t *testing.T) {
+       emptyCache(t, newRedisCache)
+}
+
+func TestRedisCache_Replace(t *testing.T) {
+       testReplace(t, newRedisCache)
+}
+
+func TestRedisCache_Add(t *testing.T) {
+       testAdd(t, newRedisCache)
+}
+
+func TestRedisCache_GetMulti(t *testing.T) {
+       testGetMulti(t, newRedisCache)
+}
diff --git a/src/foundation/api/revel/cache/serialization.go b/src/foundation/api/revel/cache/serialization.go
new file mode 100644 (file)
index 0000000..de7ab65
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "bytes"
+       "encoding/gob"
+       "reflect"
+       "strconv"
+)
+
+// Serialize transforms the given value into bytes following these rules:
+//   - If value is a byte array, it is returned as-is.
+//   - If value is an int or uint type, it is returned as the ASCII representation
+//   - Else, encoding/gob is used to serialize
+func Serialize(value interface{}) ([]byte, error) {
+       if data, ok := value.([]byte); ok {
+               return data, nil
+       }
+
+       switch v := reflect.ValueOf(value); v.Kind() {
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return []byte(strconv.FormatInt(v.Int(), 10)), nil
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+               return []byte(strconv.FormatUint(v.Uint(), 10)), nil
+       }
+
+       var b bytes.Buffer
+       encoder := gob.NewEncoder(&b)
+       if err := encoder.Encode(value); err != nil {
+               cacheLog.Error("Serialize: gob encoding failed", "value", value, "error", err)
+               return nil, err
+       }
+       return b.Bytes(), nil
+}
+
+// Deserialize transforms bytes produced by Serialize back into a Go object,
+// storing it into "ptr", which must be a pointer to the value type.
+func Deserialize(byt []byte, ptr interface{}) (err error) {
+       if data, ok := ptr.(*[]byte); ok {
+               *data = byt
+               return
+       }
+
+       if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr {
+               switch p := v.Elem(); p.Kind() {
+               case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+                       var i int64
+                       i, err = strconv.ParseInt(string(byt), 10, 64)
+                       if err != nil {
+                               cacheLog.Error("Deserialize: failed to parse int", "value", string(byt), "error", err)
+                       } else {
+                               p.SetInt(i)
+                       }
+                       return
+
+               case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+                       var i uint64
+                       i, err = strconv.ParseUint(string(byt), 10, 64)
+                       if err != nil {
+                               cacheLog.Error("Deserialize: failed to parse uint", "value", string(byt), "error", err)
+                       } else {
+                               p.SetUint(i)
+                       }
+                       return
+               }
+       }
+
+       b := bytes.NewBuffer(byt)
+       decoder := gob.NewDecoder(b)
+       if err = decoder.Decode(ptr); err != nil {
+               cacheLog.Error("Deserialize: glob decoding failed", "error", err)
+               return
+       }
+       return
+}
diff --git a/src/foundation/api/revel/cache/serialization_test.go b/src/foundation/api/revel/cache/serialization_test.go
new file mode 100644 (file)
index 0000000..a2bb6e9
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+       "reflect"
+       "testing"
+)
+
+type Struct1 struct {
+       X int
+}
+
+func (s Struct1) Method1() {}
+
+type Interface1 interface {
+       Method1()
+}
+
+var (
+       struct1                    = Struct1{1}
+       ptrStruct                  = &Struct1{2}
+       emptyIface     interface{} = Struct1{3}
+       iface1         Interface1  = Struct1{4}
+       sliceStruct                = []Struct1{{5}, {6}, {7}}
+       ptrSliceStruct             = []*Struct1{{8}, {9}, {10}}
+
+       valueMap = map[string]interface{}{
+               "bytes":          []byte{0x61, 0x62, 0x63, 0x64},
+               "string":         "string",
+               "bool":           true,
+               "int":            5,
+               "int8":           int8(5),
+               "int16":          int16(5),
+               "int32":          int32(5),
+               "int64":          int64(5),
+               "uint":           uint(5),
+               "uint8":          uint8(5),
+               "uint16":         uint16(5),
+               "uint32":         uint32(5),
+               "uint64":         uint64(5),
+               "float32":        float32(5),
+               "float64":        float64(5),
+               "array":          [5]int{1, 2, 3, 4, 5},
+               "slice":          []int{1, 2, 3, 4, 5},
+               "emptyIf":        emptyIface,
+               "Iface1":         iface1,
+               "map":            map[string]string{"foo": "bar"},
+               "ptrStruct":      ptrStruct,
+               "struct1":        struct1,
+               "sliceStruct":    sliceStruct,
+               "ptrSliceStruct": ptrSliceStruct,
+       }
+)
+
+// Test passing all kinds of data between serialize and deserialize.
+func TestRoundTrip(t *testing.T) {
+       for _, expected := range valueMap {
+               bytes, err := Serialize(expected)
+               if err != nil {
+                       t.Error(err)
+                       continue
+               }
+
+               ptrActual := reflect.New(reflect.TypeOf(expected)).Interface()
+               err = Deserialize(bytes, ptrActual)
+               if err != nil {
+                       t.Error(err)
+                       continue
+               }
+
+               actual := reflect.ValueOf(ptrActual).Elem().Interface()
+               if !reflect.DeepEqual(expected, actual) {
+                       t.Errorf("(expected) %T %v != %T %v (actual)", expected, expected, actual, actual)
+               }
+       }
+}
+
+func zeroMap(arg map[string]interface{}) map[string]interface{} {
+       result := map[string]interface{}{}
+       for key, value := range arg {
+               result[key] = reflect.Zero(reflect.TypeOf(value)).Interface()
+       }
+       return result
+}
diff --git a/src/foundation/api/revel/compress.go b/src/foundation/api/revel/compress.go
new file mode 100644 (file)
index 0000000..a44053d
--- /dev/null
@@ -0,0 +1,395 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "compress/gzip"
+       "compress/zlib"
+       "io"
+       "net/http"
+       "strconv"
+       "strings"
+)
+
+var compressionTypes = [...]string{
+       "gzip",
+       "deflate",
+}
+
+var compressableMimes = [...]string{
+       "text/plain",
+       "text/html",
+       "text/xml",
+       "text/css",
+       "application/json",
+       "application/xml",
+       "application/xhtml+xml",
+       "application/rss+xml",
+       "application/javascript",
+       "application/x-javascript",
+}
+
+// Local log instance for this class
+var compressLog = RevelLog.New("section", "compress")
+
+// WriteFlusher interface for compress writer
+type WriteFlusher interface {
+       io.Writer // An IO Writer
+       io.Closer // A closure
+       Flush() error /// A flush function
+}
+
+// The compressed writer
+type CompressResponseWriter struct {
+       Header             *BufferedServerHeader // The header
+       ControllerResponse *Response // The response
+       OriginalWriter     io.Writer // The writer
+       compressWriter     WriteFlusher // The flushed writer
+       compressionType    string // The compression type
+       headersWritten     bool // True if written
+       closeNotify        chan bool // The notify channel to close
+       parentNotify       <-chan bool // The parent chanel to receive the closed event
+       closed             bool // True if closed
+}
+
+// CompressFilter does compression of response body in gzip/deflate if
+// `results.compressed=true` in the app.conf
+func CompressFilter(c *Controller, fc []Filter) {
+       if c.Response.Out.internalHeader.Server != nil && Config.BoolDefault("results.compressed", false) {
+               if c.Response.Status != http.StatusNoContent && c.Response.Status != http.StatusNotModified {
+                       if found, compressType, compressWriter := detectCompressionType(c.Request, c.Response); found {
+                               writer := CompressResponseWriter{
+                                       ControllerResponse: c.Response,
+                                       OriginalWriter:     c.Response.GetWriter(),
+                                       compressWriter:     compressWriter,
+                                       compressionType:    compressType,
+                                       headersWritten:     false,
+                                       closeNotify:        make(chan bool, 1),
+                                       closed:             false,
+                               }
+                               // Swap out the header with our own
+                               writer.Header = NewBufferedServerHeader(c.Response.Out.internalHeader.Server)
+                               c.Response.Out.internalHeader.Server = writer.Header
+                               if w, ok := c.Response.GetWriter().(http.CloseNotifier); ok {
+                                       writer.parentNotify = w.CloseNotify()
+                               }
+                               c.Response.SetWriter(&writer)
+                       }
+               } else {
+                       compressLog.Debug("CompressFilter: Compression disabled for response ", "status", c.Response.Status)
+               }
+       }
+       fc[0](c, fc[1:])
+}
+
+// Called to notify the writer is closing
+func (c CompressResponseWriter) CloseNotify() <-chan bool {
+       if c.parentNotify != nil {
+               return c.parentNotify
+       }
+       return c.closeNotify
+}
+
+// Cancel the writer
+func (c *CompressResponseWriter) cancel() {
+       c.closed = true
+}
+
+// Prepare the headers
+func (c *CompressResponseWriter) prepareHeaders() {
+       if c.compressionType != "" {
+               responseMime := ""
+               if t := c.Header.Get("Content-Type"); len(t) > 0 {
+                       responseMime = t[0]
+               }
+               responseMime = strings.TrimSpace(strings.SplitN(responseMime, ";", 2)[0])
+               shouldEncode := false
+
+               if len(c.Header.Get("Content-Encoding")) == 0 {
+                       for _, compressableMime := range compressableMimes {
+                               if responseMime == compressableMime {
+                                       shouldEncode = true
+                                       c.Header.Set("Content-Encoding", c.compressionType)
+                                       c.Header.Del("Content-Length")
+                                       break
+                               }
+                       }
+               }
+
+               if !shouldEncode {
+                       c.compressWriter = nil
+                       c.compressionType = ""
+               }
+       }
+       c.Header.Release()
+}
+
+// Write the headers
+func (c *CompressResponseWriter) WriteHeader(status int) {
+       if c.closed {
+               return
+       }
+       c.headersWritten = true
+       c.prepareHeaders()
+       c.Header.SetStatus(status)
+}
+
+// Close the writer
+func (c *CompressResponseWriter) Close() error {
+       if c.closed {
+               return nil
+       }
+       if !c.headersWritten {
+               c.prepareHeaders()
+       }
+       if c.compressionType != "" {
+               c.Header.Del("Content-Length")
+               if err := c.compressWriter.Close(); err != nil {
+                       // TODO When writing directly to stream, an error will be generated
+                       compressLog.Error("Close: Error closing compress writer", "type", c.compressionType, "error", err)
+               }
+
+       }
+       // Non-blocking write to the closenotifier, if we for some reason should
+       // get called multiple times
+       select {
+       case c.closeNotify <- true:
+       default:
+       }
+       c.closed = true
+       return nil
+}
+
+// Write to the underling buffer
+func (c *CompressResponseWriter) Write(b []byte) (int, error) {
+       if c.closed {
+               return 0, io.ErrClosedPipe
+       }
+       // Abort if parent has been closed
+       if c.parentNotify != nil {
+               select {
+               case <-c.parentNotify:
+                       return 0, io.ErrClosedPipe
+               default:
+               }
+       }
+       // Abort if we ourselves have been closed
+       if c.closed {
+               return 0, io.ErrClosedPipe
+       }
+
+       if !c.headersWritten {
+               c.prepareHeaders()
+               c.headersWritten = true
+       }
+       if c.compressionType != "" {
+               return c.compressWriter.Write(b)
+       }
+       return c.OriginalWriter.Write(b)
+}
+
+// DetectCompressionType method detects the compression type
+// from header "Accept-Encoding"
+func detectCompressionType(req *Request, resp *Response) (found bool, compressionType string, compressionKind WriteFlusher) {
+       if Config.BoolDefault("results.compressed", false) {
+               acceptedEncodings := strings.Split(req.GetHttpHeader("Accept-Encoding"), ",")
+
+               largestQ := 0.0
+               chosenEncoding := len(compressionTypes)
+
+               // I have fixed one edge case for issue #914
+               // But it's better to cover all possible edge cases or
+               // Adapt to https://github.com/golang/gddo/blob/master/httputil/header/header.go#L172
+               for _, encoding := range acceptedEncodings {
+                       encoding = strings.TrimSpace(encoding)
+                       encodingParts := strings.SplitN(encoding, ";", 2)
+
+                       // If we are the format "gzip;q=0.8"
+                       if len(encodingParts) > 1 {
+                               q := strings.TrimSpace(encodingParts[1])
+                               if len(q) == 0 || !strings.HasPrefix(q, "q=") {
+                                       continue
+                               }
+
+                               // Strip off the q=
+                               num, err := strconv.ParseFloat(q[2:], 32)
+                               if err != nil {
+                                       continue
+                               }
+
+                               if num >= largestQ && num > 0 {
+                                       if encodingParts[0] == "*" {
+                                               chosenEncoding = 0
+                                               largestQ = num
+                                               continue
+                                       }
+                                       for i, encoding := range compressionTypes {
+                                               if encoding == encodingParts[0] {
+                                                       if i < chosenEncoding {
+                                                               largestQ = num
+                                                               chosenEncoding = i
+                                                       }
+                                                       break
+                                               }
+                                       }
+                               }
+                       } else {
+                               // If we can accept anything, chose our preferred method.
+                               if encodingParts[0] == "*" {
+                                       chosenEncoding = 0
+                                       largestQ = 1
+                                       break
+                               }
+                               // This is for just plain "gzip"
+                               for i, encoding := range compressionTypes {
+                                       if encoding == encodingParts[0] {
+                                               if i < chosenEncoding {
+                                                       largestQ = 1.0
+                                                       chosenEncoding = i
+                                               }
+                                               break
+                                       }
+                               }
+                       }
+               }
+
+               if largestQ == 0 {
+                       return
+               }
+
+               compressionType = compressionTypes[chosenEncoding]
+
+               switch compressionType {
+               case "gzip":
+                       compressionKind = gzip.NewWriter(resp.GetWriter())
+                       found = true
+               case "deflate":
+                       compressionKind = zlib.NewWriter(resp.GetWriter())
+                       found = true
+               }
+       }
+       return
+}
+
+// BufferedServerHeader will not send content out until the Released is called, from that point on it will act normally
+// It implements all the ServerHeader
+type BufferedServerHeader struct {
+       cookieList []string // The cookie list
+       headerMap  map[string][]string // The header map
+       status     int // The status
+       released   bool // True if released
+       original   ServerHeader // The original header
+}
+
+// Creates a new instance based on the ServerHeader
+func NewBufferedServerHeader(o ServerHeader) *BufferedServerHeader {
+       return &BufferedServerHeader{original: o, headerMap: map[string][]string{}}
+}
+
+// Sets the cookie
+func (bsh *BufferedServerHeader) SetCookie(cookie string) {
+       if bsh.released {
+               bsh.original.SetCookie(cookie)
+       } else {
+               bsh.cookieList = append(bsh.cookieList, cookie)
+       }
+}
+
+// Returns a cookie
+func (bsh *BufferedServerHeader) GetCookie(key string) (ServerCookie, error) {
+       return bsh.original.GetCookie(key)
+}
+
+// Sets (replace) the header key
+func (bsh *BufferedServerHeader) Set(key string, value string) {
+       if bsh.released {
+               bsh.original.Set(key, value)
+       } else {
+               bsh.headerMap[key] = []string{value}
+       }
+}
+
+// Add (append) to a key this value
+func (bsh *BufferedServerHeader) Add(key string, value string) {
+       if bsh.released {
+               bsh.original.Set(key, value)
+       } else {
+               old := []string{}
+               if v, found := bsh.headerMap[key]; found {
+                       old = v
+               }
+               bsh.headerMap[key] = append(old, value)
+       }
+}
+
+// Delete this key
+func (bsh *BufferedServerHeader) Del(key string) {
+       if bsh.released {
+               bsh.original.Del(key)
+       } else {
+               delete(bsh.headerMap, key)
+       }
+}
+
+// Get this key
+func (bsh *BufferedServerHeader) Get(key string) (value []string) {
+       if bsh.released {
+               value = bsh.original.Get(key)
+       } else {
+               if v, found := bsh.headerMap[key]; found && len(v) > 0 {
+                       value = v
+               } else {
+                       value = bsh.original.Get(key)
+               }
+       }
+       return
+}
+
+// Get all header keys
+func (bsh *BufferedServerHeader) GetKeys() (value []string) {
+       if bsh.released {
+               value = bsh.original.GetKeys()
+       } else {
+               value = bsh.original.GetKeys()
+               for key := range bsh.headerMap {
+                       found := false
+                       for _,v := range value {
+                               if v==key {
+                                       found = true
+                                       break
+                               }
+                       }
+                       if !found {
+                               value = append(value,key)
+                       }
+               }
+       }
+       return
+}
+
+// Set the status
+func (bsh *BufferedServerHeader) SetStatus(statusCode int) {
+       if bsh.released {
+               bsh.original.SetStatus(statusCode)
+       } else {
+               bsh.status = statusCode
+       }
+}
+
+// Release the header and push the results to the original
+func (bsh *BufferedServerHeader) Release() {
+       bsh.released = true
+       for k, v := range bsh.headerMap {
+               for _, r := range v {
+                       bsh.original.Set(k, r)
+               }
+       }
+       for _, c := range bsh.cookieList {
+               bsh.original.SetCookie(c)
+       }
+       if bsh.status > 0 {
+               bsh.original.SetStatus(bsh.status)
+       }
+}
diff --git a/src/foundation/api/revel/compress_test.go b/src/foundation/api/revel/compress_test.go
new file mode 100644 (file)
index 0000000..8b8b2fb
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "net/http/httptest"
+       "strings"
+       "testing"
+)
+
+// Test that the render response is as expected.
+func TestBenchmarkCompressed(t *testing.T) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               t.Errorf("SetAction failed: %s", err)
+       }
+       Config.SetOption("results.compressed", "true")
+       result := Hotels{c}.Show(3)
+       result.Apply(c.Request, c.Response)
+       if !strings.Contains(resp.Body.String(), "300 Main St.") {
+               t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body)
+       }
+}
+
+func BenchmarkRenderCompressed(b *testing.B) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       resp.Body = nil
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               b.Errorf("SetAction failed: %s", err)
+       }
+       Config.SetOption("results.compressed", "true")
+       b.ResetTimer()
+
+       hotels := Hotels{c}
+       for i := 0; i < b.N; i++ {
+               hotels.Show(3).Apply(c.Request, c.Response)
+       }
+}
+
+func BenchmarkRenderUnCompressed(b *testing.B) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       resp.Body = nil
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               b.Errorf("SetAction failed: %s", err)
+       }
+       Config.SetOption("results.compressed", "false")
+       b.ResetTimer()
+
+       hotels := Hotels{c}
+       for i := 0; i < b.N; i++ {
+               hotels.Show(3).Apply(c.Request, c.Response)
+       }
+}
diff --git a/src/foundation/api/revel/conf/mime-types.conf b/src/foundation/api/revel/conf/mime-types.conf
new file mode 100644 (file)
index 0000000..43b0235
--- /dev/null
@@ -0,0 +1,545 @@
+3dm=x-world/x-3dmf
+3dmf=x-world/x-3dmf
+7z=application/x-7z-compressed
+a=application/octet-stream
+aab=application/x-authorware-bin
+aam=application/x-authorware-map
+aas=application/x-authorware-seg
+abc=text/vndabc
+ace=application/x-ace-compressed
+acgi=text/html
+afl=video/animaflex
+ai=application/postscript
+aif=audio/aiff
+aifc=audio/aiff
+aiff=audio/aiff
+aim=application/x-aim
+aip=text/x-audiosoft-intra
+alz=application/x-alz-compressed
+ani=application/x-navi-animation
+aos=application/x-nokia-9000-communicator-add-on-software
+aps=application/mime
+arc=application/x-arc-compressed
+arj=application/arj
+art=image/x-jg
+asf=video/x-ms-asf
+asm=text/x-asm
+asp=text/asp
+asx=application/x-mplayer2
+au=audio/basic
+avi=video/x-msvideo
+avs=video/avs-video
+bcpio=application/x-bcpio
+bin=application/mac-binary
+bmp=image/bmp
+boo=application/book
+book=application/book
+boz=application/x-bzip2
+bsh=application/x-bsh
+bz2=application/x-bzip2
+bz=application/x-bzip
+c++=text/plain
+c=text/x-c
+cab=application/vnd.ms-cab-compressed
+cat=application/vndms-pkiseccat
+cc=text/x-c
+ccad=application/clariscad
+cco=application/x-cocoa
+cdf=application/cdf
+cer=application/pkix-cert
+cha=application/x-chat
+chat=application/x-chat
+chrt=application/vnd.kde.kchart
+class=application/java
+# ? class=application/java-vm
+com=text/plain
+conf=text/plain
+cpio=application/x-cpio
+cpp=text/x-c
+cpt=application/mac-compactpro
+crl=application/pkcs-crl
+crt=application/pkix-cert
+crx=application/x-chrome-extension
+csh=text/x-scriptcsh
+css=text/css
+csv=text/csv
+cxx=text/plain
+dar=application/x-dar
+dcr=application/x-director
+deb=application/x-debian-package
+deepv=application/x-deepv
+def=text/plain
+der=application/x-x509-ca-cert
+dif=video/x-dv
+dir=application/x-director
+divx=video/divx
+dl=video/dl
+dmg=application/x-apple-diskimage
+doc=application/msword
+dot=application/msword
+dp=application/commonground
+drw=application/drafting
+dump=application/octet-stream
+dv=video/x-dv
+dvi=application/x-dvi
+dwf=drawing/x-dwf=(old)
+dwg=application/acad
+dxf=application/dxf
+dxr=application/x-director
+el=text/x-scriptelisp
+elc=application/x-bytecodeelisp=(compiled=elisp)
+eml=message/rfc822
+env=application/x-envoy
+eps=application/postscript
+es=application/x-esrehber
+etx=text/x-setext
+evy=application/envoy
+exe=application/octet-stream
+f77=text/x-fortran
+f90=text/x-fortran
+f=text/x-fortran
+fdf=application/vndfdf
+fif=application/fractals
+fli=video/fli
+flo=image/florian
+flv=video/x-flv
+flx=text/vndfmiflexstor
+fmf=video/x-atomic3d-feature
+for=text/x-fortran
+fpx=image/vndfpx
+frl=application/freeloader
+funk=audio/make
+g3=image/g3fax
+g=text/plain
+gif=image/gif
+gl=video/gl
+gsd=audio/x-gsm
+gsm=audio/x-gsm
+gsp=application/x-gsp
+gss=application/x-gss
+gtar=application/x-gtar
+gz=application/x-compressed
+gzip=application/x-gzip
+h=text/x-h
+hdf=application/x-hdf
+help=application/x-helpfile
+hgl=application/vndhp-hpgl
+hh=text/x-h
+hlb=text/x-script
+hlp=application/hlp
+hpg=application/vndhp-hpgl
+hpgl=application/vndhp-hpgl
+hqx=application/binhex
+hta=application/hta
+htc=text/x-component
+htm=text/html
+html=text/html
+htmls=text/html
+htt=text/webviewhtml
+htx=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ics=text/calendar
+icz=text/calendar
+idc=text/plain
+ief=image/ief
+iefs=image/ief
+iges=application/iges
+igs=application/iges
+ima=application/x-ima
+imap=application/x-httpd-imap
+inf=application/inf
+ins=application/x-internett-signup
+ip=application/x-ip2
+isu=video/x-isvideo
+it=audio/it
+iv=application/x-inventor
+ivr=i-world/i-vrml
+ivy=application/x-livescreen
+jam=audio/x-jam
+jav=text/x-java-source
+java=text/x-java-source
+jcm=application/x-java-commerce
+jfif-tbnl=image/jpeg
+jfif=image/jpeg
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+jps=image/x-jps
+js=application/javascript
+json=application/json
+jut=image/jutvision
+kar=audio/midi
+karbon=application/vnd.kde.karbon
+kfo=application/vnd.kde.kformula
+flw=application/vnd.kde.kivio
+kml=application/vnd.google-earth.kml+xml
+kmz=application/vnd.google-earth.kmz
+kon=application/vnd.kde.kontour
+kpr=application/vnd.kde.kpresenter
+kpt=application/vnd.kde.kpresenter
+ksp=application/vnd.kde.kspread
+kwd=application/vnd.kde.kword
+kwt=application/vnd.kde.kword
+ksh=text/x-scriptksh
+la=audio/nspaudio
+lam=audio/x-liveaudio
+latex=application/x-latex
+lha=application/lha
+lhx=application/octet-stream
+list=text/plain
+lma=audio/nspaudio
+log=text/plain
+lsp=text/x-scriptlisp
+lst=text/plain
+lsx=text/x-la-asf
+ltx=application/x-latex
+lzh=application/octet-stream
+lzx=application/lzx
+m1v=video/mpeg
+m2a=audio/mpeg
+m2v=video/mpeg
+m3u=audio/x-mpegurl
+m=text/x-m
+man=application/x-troff-man
+manifest=text/cache-manifest
+map=application/x-navimap
+mar=text/plain
+mbd=application/mbedlet
+mc$=application/x-magic-cap-package-10
+mcd=application/mcad
+mcf=text/mcf
+mcp=application/netmc
+me=application/x-troff-me
+mht=message/rfc822
+mhtml=message/rfc822
+mid=application/x-midi
+midi=application/x-midi
+mif=application/x-frame
+mime=message/rfc822
+mjf=audio/x-vndaudioexplosionmjuicemediafile
+mjpg=video/x-motion-jpeg
+mm=application/base64
+mme=application/base64
+mod=audio/mod
+moov=video/quicktime
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg3
+mp4=video/mp4
+mpa=audio/mpeg
+mpc=application/x-project
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+mpp=application/vndms-project
+mpt=application/x-project
+mpv=application/x-project
+mpx=application/x-project
+mrc=application/marc
+ms=application/x-troff-ms
+mv=video/x-sgi-movie
+my=audio/make
+mzz=application/x-vndaudioexplosionmzz
+nap=image/naplps
+naplps=image/naplps
+nc=application/x-netcdf
+ncm=application/vndnokiaconfiguration-message
+nif=image/x-niff
+niff=image/x-niff
+nix=application/x-mix-transfer
+nsc=application/x-conference
+nvd=application/x-navidoc
+o=application/octet-stream
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+oga=audio/ogg
+ogg=audio/ogg
+ogv=video/ogg
+omc=application/x-omc
+omcd=application/x-omcdatamaker
+omcr=application/x-omcregerator
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otm=application/vnd.oasis.opendocument.text-master
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+p10=application/pkcs10
+p12=application/pkcs-12
+p7a=application/x-pkcs7-signature
+p7c=application/pkcs7-mime
+p7m=application/pkcs7-mime
+p7r=application/x-pkcs7-certreqresp
+p7s=application/pkcs7-signature
+p=text/x-pascal
+part=application/pro_eng
+pas=text/pascal
+pbm=image/x-portable-bitmap
+pcl=application/vndhp-pcl
+pct=image/x-pict
+pcx=image/x-pcx
+pdb=chemical/x-pdb
+pdf=application/pdf
+pfunk=audio/make
+pgm=image/x-portable-graymap
+pic=image/pict
+pict=image/pict
+pkg=application/x-newton-compatible-pkg
+pko=application/vndms-pkipko
+pl=text/x-scriptperl
+plx=application/x-pixclscript
+pm4=application/x-pagemaker
+pm5=application/x-pagemaker
+pm=text/x-scriptperl-module
+png=image/png
+pnm=application/x-portable-anymap
+pot=application/mspowerpoint
+pov=model/x-pov
+ppa=application/vndms-powerpoint
+ppm=image/x-portable-pixmap
+pps=application/mspowerpoint
+ppt=application/mspowerpoint
+ppz=application/mspowerpoint
+pre=application/x-freelance
+prt=application/pro_eng
+ps=application/postscript
+psd=application/octet-stream
+pvu=paleovu/x-pv
+pwz=application/vndms-powerpoint
+py=text/x-scriptphyton
+pyc=applicaiton/x-bytecodepython
+qcp=audio/vndqcelp
+qd3=x-world/x-3dmf
+qd3d=x-world/x-3dmf
+qif=image/x-quicktime
+qt=video/quicktime
+qtc=video/x-qtc
+qti=image/x-quicktime
+qtif=image/x-quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+rar=application/x-rar-compressed
+ras=application/x-cmu-raster
+rast=image/cmu-raster
+rexx=text/x-scriptrexx
+rf=image/vndrn-realflash
+rgb=image/x-rgb
+rm=application/vndrn-realmedia
+rmi=audio/mid
+rmm=audio/x-pn-realaudio
+rmp=audio/x-pn-realaudio
+rng=application/ringing-tones
+rnx=application/vndrn-realplayer
+roff=application/x-troff
+rp=image/vndrn-realpix
+rpm=audio/x-pn-realaudio-plugin
+rt=text/vndrn-realtext
+rtf=text/richtext
+rtx=text/richtext
+rv=video/vndrn-realvideo
+s=text/x-asm
+s3m=audio/s3m
+s7z=application/x-7z-compressed
+saveme=application/octet-stream
+sbk=application/x-tbook
+scm=text/x-scriptscheme
+sdml=text/plain
+sdp=application/sdp
+sdr=application/sounder
+sea=application/sea
+set=application/set
+sgm=text/x-sgml
+sgml=text/x-sgml
+sh=text/x-scriptsh
+shar=application/x-bsh
+shtml=text/x-server-parsed-html
+sid=audio/x-psid
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+sit=application/x-stuffit
+sitx=application/x-stuffitx
+sl=application/x-seelogo
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+sol=application/solids
+spc=text/x-speech
+spl=application/futuresplash
+spr=application/x-sprite
+sprite=application/x-sprite
+spx=audio/ogg
+src=application/x-wais-source
+ssi=text/x-server-parsed-html
+ssm=application/streamingmedia
+sst=application/vndms-pkicertstore
+step=application/step
+stl=application/sla
+stp=application/step
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svf=image/vnddwg
+svg=image/svg+xml
+svr=application/x-world
+swf=application/x-shockwave-flash
+t=application/x-troff
+talk=text/x-speech
+tar=application/x-tar
+tbk=application/toolbook
+tcl=text/x-scripttcl
+tcsh=text/x-scripttcsh
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+text=text/plain
+tgz=application/gnutar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsi=audio/tsp-audio
+tsp=application/dsptype
+tsv=text/tab-separated-values
+turbot=image/florian
+txt=text/plain
+uil=text/x-uil
+uni=text/uri-list
+unis=text/uri-list
+unv=application/i-deas
+uri=text/uri-list
+uris=text/uri-list
+ustar=application/x-ustar
+uu=text/x-uuencode
+uue=text/x-uuencode
+vcd=application/x-cdlink
+vcf=text/x-vcard
+vcard=text/x-vcard
+vcs=text/x-vcalendar
+vda=application/vda
+vdo=video/vdo
+vew=application/groupwise
+viv=video/vivo
+vivo=video/vivo
+vmd=application/vocaltec-media-desc
+vmf=application/vocaltec-media-file
+voc=audio/voc
+vos=video/vosaic
+vox=audio/voxware
+vqe=audio/x-twinvq-plugin
+vqf=audio/x-twinvq
+vql=audio/x-twinvq-plugin
+vrml=application/x-vrml
+vrt=x-world/x-vrt
+vsd=application/x-visio
+vst=application/x-visio
+vsw=application/x-visio
+w60=application/wordperfect60
+w61=application/wordperfect61
+w6w=application/msword
+wav=audio/wav
+wb1=application/x-qpro
+wbmp=image/vnd.wap.wbmp
+web=application/vndxara
+wiz=application/msword
+wk1=application/x-123
+wmf=windows/metafile
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+word=application/msword
+wp5=application/wordperfect
+wp6=application/wordperfect
+wp=application/wordperfect
+wpd=application/wordperfect
+wq1=application/x-lotus
+wri=application/mswrite
+wrl=application/x-world
+wrz=model/vrml
+wsc=text/scriplet
+wsrc=application/x-wais-source
+wtk=application/x-wintalk
+x-png=image/png
+xbm=image/x-xbitmap
+xdr=video/x-amt-demorun
+xgz=xgl/drawing
+xif=image/vndxiff
+xl=application/excel
+xla=application/excel
+xlb=application/excel
+xlc=application/excel
+xld=application/excel
+xlk=application/excel
+xll=application/excel
+xlm=application/excel
+xls=application/excel
+xlt=application/excel
+xlv=application/excel
+xlw=application/excel
+xm=audio/xm
+xml=text/xml
+xmz=xgl/movie
+xpix=application/x-vndls-xpix
+xpm=image/x-xpixmap
+xsr=video/x-amt-showrun
+xwd=image/x-xwd
+xyz=chemical/x-pdb
+z=application/x-compress
+zip=application/zip
+zoo=application/octet-stream
+zsh=text/x-scriptzsh
+# Office 2007 mess - http://wdg.uncc.edu/Microsoft_Office_2007_MIME_Types_for_Apache_and_IIS
+docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document
+docm=application/vnd.ms-word.document.macroEnabled.12
+dotx=application/vnd.openxmlformats-officedocument.wordprocessingml.template
+dotm=application/vnd.ms-word.template.macroEnabled.12
+xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+xlsm=application/vnd.ms-excel.sheet.macroEnabled.12
+xltx=application/vnd.openxmlformats-officedocument.spreadsheetml.template
+xltm=application/vnd.ms-excel.template.macroEnabled.12
+xlsb=application/vnd.ms-excel.sheet.binary.macroEnabled.12
+xlam=application/vnd.ms-excel.addin.macroEnabled.12
+pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation
+pptm=application/vnd.ms-powerpoint.presentation.macroEnabled.12
+ppsx=application/vnd.openxmlformats-officedocument.presentationml.slideshow
+ppsm=application/vnd.ms-powerpoint.slideshow.macroEnabled.12
+potx=application/vnd.openxmlformats-officedocument.presentationml.template
+potm=application/vnd.ms-powerpoint.template.macroEnabled.12
+ppam=application/vnd.ms-powerpoint.addin.macroEnabled.12
+sldx=application/vnd.openxmlformats-officedocument.presentationml.slide
+sldm=application/vnd.ms-powerpoint.slide.macroEnabled.12
+thmx=application/vnd.ms-officetheme
+onetoc=application/onenote
+onetoc2=application/onenote
+onetmp=application/onenote
+onepkg=application/onenote
+# koffice
+
+# iWork
+key=application/x-iwork-keynote-sffkey
+kth=application/x-iwork-keynote-sffkth
+nmbtemplate=application/x-iwork-numbers-sfftemplate
+numbers=application/x-iwork-numbers-sffnumbers
+pages=application/x-iwork-pages-sffpages
+template=application/x-iwork-pages-sfftemplate
+
+# Extensions for Mozilla apps (Firefox and friends)
+xpi=application/x-xpinstall
+
+# Opera extensions
+oex=application/x-opera-extension
diff --git a/src/foundation/api/revel/controller.go b/src/foundation/api/revel/controller.go
new file mode 100644 (file)
index 0000000..29dbf7c
--- /dev/null
@@ -0,0 +1,546 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "errors"
+       "fmt"
+       "io"
+       "net/http"
+       "os"
+       "path/filepath"
+       "reflect"
+       "runtime"
+       "strings"
+       "time"
+
+       "github.com/revel/revel/logger"
+       "github.com/revel/revel/session"
+       "github.com/revel/revel/utils"
+)
+
+// Controller Revel's controller structure that gets embedded in user defined
+// controllers
+type Controller struct {
+       Name          string          // The controller name, e.g. "Application"
+       Type          *ControllerType // A description of the controller type.
+       MethodName    string          // The method name, e.g. "Index"
+       MethodType    *MethodType     // A description of the invoked action type.
+       AppController interface{}     // The controller that was instantiated. embeds revel.Controller
+       Action        string          // The fully qualified action name, e.g. "App.Index"
+       ClientIP      string          // holds IP address of request came from
+
+       Request  *Request
+       Response *Response
+       Result   Result
+
+       Flash      Flash                  // User cookie, cleared after 1 request.
+       Session    session.Session        // Session, stored using the session engine specified
+       Params     *Params                // Parameters from URL and form (including multipart).
+       Args       map[string]interface{} // Per-request scratch space.
+       ViewArgs   map[string]interface{} // Variables passed to the template.
+       Validation *Validation            // Data validation helpers
+       Log        logger.MultiLogger     // Context Logger
+}
+
+// The map of controllers, controllers are mapped by using the namespace|controller_name as the key
+var controllers = make(map[string]*ControllerType)
+var controllerLog = RevelLog.New("section", "controller")
+
+// NewController returns new controller instance for Request and Response
+func NewControllerEmpty() *Controller {
+       return &Controller{Request: NewRequest(nil), Response: NewResponse(nil)}
+}
+
+// New controller, creates a new instance wrapping the request and response in it
+func NewController(context ServerContext) *Controller {
+       c := NewControllerEmpty()
+       c.SetController(context)
+       return c
+}
+
+// Sets the request and the response for the controller
+func (c *Controller) SetController(context ServerContext) {
+
+       c.Request.SetRequest(context.GetRequest())
+       c.Response.SetResponse(context.GetResponse())
+       c.Request.controller = c
+       c.Params = new(Params)
+       c.Args = map[string]interface{}{}
+       c.ViewArgs = map[string]interface{}{
+               "RunMode": RunMode,
+               "DevMode": DevMode,
+       }
+
+}
+func (c *Controller) Destroy() {
+       // When the instantiated controller gets injected
+       // It inherits this method, so we need to
+       // check to see if the controller is nil before performing
+       // any actions
+       if c == nil {
+               return
+       }
+       if c.AppController != nil {
+               c.resetAppControllerFields()
+               // Return this instance to the pool
+               appController := c.AppController
+               c.AppController = nil
+               if RevelConfig.Controller.Reuse {
+                       RevelConfig.Controller.CachedMap[c.Name].Push(appController)
+               }
+               c.AppController = nil
+       }
+
+       c.Request.Destroy()
+       c.Response.Destroy()
+       c.Params = nil
+       c.Args = nil
+       c.ViewArgs = nil
+       c.Name = ""
+       c.Type = nil
+       c.MethodName = ""
+       c.MethodType = nil
+       c.Action = ""
+       c.ClientIP = ""
+       c.Result = nil
+       c.Flash = Flash{}
+       c.Session = session.NewSession()
+       c.Params = nil
+       c.Validation = nil
+       c.Log = nil
+}
+
+// FlashParams serializes the contents of Controller.Params to the Flash
+// cookie.
+func (c *Controller) FlashParams() {
+       for key, vals := range c.Params.Values {
+               c.Flash.Out[key] = strings.Join(vals, ",")
+       }
+}
+
+func (c *Controller) SetCookie(cookie *http.Cookie) {
+       c.Response.Out.internalHeader.SetCookie(cookie.String())
+}
+
+type ErrorCoder interface {
+       HTTPCode() int
+}
+
+func (c *Controller) RenderError(err error) Result {
+       if coder, ok := err.(ErrorCoder); ok {
+               c.setStatusIfNil(coder.HTTPCode())
+       } else {
+               c.setStatusIfNil(http.StatusInternalServerError)
+       }
+
+       return ErrorResult{c.ViewArgs, err}
+}
+
+func (c *Controller) setStatusIfNil(status int) {
+       if c.Response.Status == 0 {
+               c.Response.Status = status
+       }
+}
+
+// Render a template corresponding to the calling Controller method.
+// Arguments will be added to c.ViewArgs prior to rendering the template.
+// They are keyed on their local identifier.
+//
+// For example:
+//
+//     func (c Users) ShowUser(id int) revel.Result {
+//              user := loadUser(id)
+//              return c.Render(user)
+//     }
+//
+// This action will render views/Users/ShowUser.html, passing in an extra
+// key-value "user": (User).
+//
+// This is the slower magical version which uses the runtime
+// to determine
+// 1) Set c.ViewArgs to the arguments passed into this function
+// 2) How to call the RenderTemplate by building the following line
+// c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
+//
+// If you want your code to run faster it is recommended you add the template values directly
+// to the c.ViewArgs and call c.RenderTemplate directly
+func (c *Controller) Render(extraViewArgs ...interface{}) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       // Get the calling function line number.
+       _, _, line, ok := runtime.Caller(1)
+       if !ok {
+               controllerLog.Error("Render: Failed to get Caller information")
+       }
+
+       // Get the extra ViewArgs passed in.
+       if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
+               if len(renderArgNames) == len(extraViewArgs) {
+                       for i, extraRenderArg := range extraViewArgs {
+                               c.ViewArgs[renderArgNames[i]] = extraRenderArg
+                       }
+               } else {
+                       controllerLog.Error(fmt.Sprint(len(renderArgNames), "RenderArg names found for",
+                               len(extraViewArgs), "extra ViewArgs"))
+               }
+       } else {
+               controllerLog.Error(fmt.Sprint("No RenderArg names found for Render call on line", line,
+                       "(Action", c.Action, ")"),"stack",logger.NewCallStack())
+       }
+
+       return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
+}
+
+// RenderTemplate method does less magical way to render a template.
+// Renders the given template, using the current ViewArgs.
+func (c *Controller) RenderTemplate(templatePath string) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       // Get the Template.
+       lang, _ := c.ViewArgs[CurrentLocaleViewArg].(string)
+       template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
+       if err != nil {
+               return c.RenderError(err)
+       }
+
+       return &RenderTemplateResult{
+               Template: template,
+               ViewArgs: c.ViewArgs,
+       }
+}
+
+// TemplateOutput returns the result of the template rendered using the controllers ViewArgs.
+func (c *Controller) TemplateOutput(templatePath string) (data []byte, err error) {
+       return TemplateOutputArgs(templatePath, c.ViewArgs)
+}
+
+// RenderJSON uses encoding/json.Marshal to return JSON to the client.
+func (c *Controller) RenderJSON(o interface{}) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       return RenderJSONResult{o, ""}
+}
+
+// RenderJSONP renders JSONP result using encoding/json.Marshal
+func (c *Controller) RenderJSONP(callback string, o interface{}) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       return RenderJSONResult{o, callback}
+}
+
+// RenderXML uses encoding/xml.Marshal to return XML to the client.
+func (c *Controller) RenderXML(o interface{}) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       return RenderXMLResult{o}
+}
+
+// RenderText renders plaintext in response, printf style.
+func (c *Controller) RenderText(text string, objs ...interface{}) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       finalText := text
+       if len(objs) > 0 {
+               finalText = fmt.Sprintf(text, objs...)
+       }
+       return &RenderTextResult{finalText}
+}
+
+// RenderHTML renders html in response
+func (c *Controller) RenderHTML(html string) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       return &RenderHTMLResult{html}
+}
+
+// Todo returns an HTTP 501 Not Implemented "todo" indicating that the
+// action isn't done yet.
+func (c *Controller) Todo() Result {
+       c.Response.Status = http.StatusNotImplemented
+       controllerLog.Debug("Todo: Not implemented function", "action", c.Action)
+       return c.RenderError(&Error{
+               Title:       "TODO",
+               Description: "This action is not implemented",
+       })
+}
+
+// NotFound returns an HTTP 404 Not Found response whose body is the
+// formatted string of msg and objs.
+func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
+       finalText := msg
+       if len(objs) > 0 {
+               finalText = fmt.Sprintf(msg, objs...)
+       }
+       c.Response.Status = http.StatusNotFound
+       return c.RenderError(&Error{
+               Title:       "Not Found",
+               Description: finalText,
+       })
+}
+
+// Forbidden returns an HTTP 403 Forbidden response whose body is the
+// formatted string of msg and objs.
+func (c *Controller) Forbidden(msg string, objs ...interface{}) Result {
+       finalText := msg
+       if len(objs) > 0 {
+               finalText = fmt.Sprintf(msg, objs...)
+       }
+       c.Response.Status = http.StatusForbidden
+       return c.RenderError(&Error{
+               Title:       "Forbidden",
+               Description: finalText,
+       })
+}
+
+// RenderFileName returns a file indicated by the path as provided via the filename.
+// It can be either displayed inline or downloaded as an attachment.
+// The name and size are taken from the file info.
+func (c *Controller) RenderFileName(filename string, delivery ContentDisposition) Result {
+       f, err := os.Open(filename)
+       if err != nil {
+               c.Log.Errorf("Cant open file: %v", err)
+               return c.RenderError(err)
+       }
+       return c.RenderFile(f, delivery)
+}
+
+// RenderFile returns a file, either displayed inline or downloaded
+// as an attachment. The name and size are taken from the file info.
+func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       var (
+               modtime       = time.Now()
+               fileInfo, err = file.Stat()
+       )
+       if err != nil {
+               controllerLog.Error("RenderFile: error", "error", err)
+       }
+       if fileInfo != nil {
+               modtime = fileInfo.ModTime()
+       }
+       return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime)
+}
+
+// RenderBinary is like RenderFile() except that it instead of a file on disk,
+// it renders data from memory (which could be a file that has not been written,
+// the output from some function, or bytes streamed from somewhere else, as long
+// it implements io.Reader).  When called directly on something generated or
+// streamed, modtime should mostly likely be time.Now().
+func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result {
+       c.setStatusIfNil(http.StatusOK)
+
+       return &BinaryResult{
+               Reader:   memfile,
+               Name:     filename,
+               Delivery: delivery,
+               Length:   -1, // http.ServeContent gets the length itself unless memfile is a stream.
+               ModTime:  modtime,
+       }
+}
+
+// Redirect to an action or to a URL.
+//   c.Redirect(Controller.Action)
+//   c.Redirect("/controller/action")
+//   c.Redirect("/controller/%d/action", id)
+func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
+       c.setStatusIfNil(http.StatusFound)
+
+       if url, ok := val.(string); ok {
+               if len(args) == 0 {
+                       return &RedirectToURLResult{url}
+               }
+               return &RedirectToURLResult{fmt.Sprintf(url, args...)}
+       }
+       return &RedirectToActionResult{val, args}
+}
+
+// This stats returns some interesting stats based on what is cached in memory
+// and what is available directly
+func (c *Controller) Stats() map[string]interface{} {
+       result := CurrentEngine.Stats()
+       if RevelConfig.Controller.Reuse {
+               result["revel-controllers"] = RevelConfig.Controller.Stack.String()
+               for key, appStack := range RevelConfig.Controller.CachedMap {
+                       result["app-" + key] = appStack.String()
+               }
+       }
+       return result
+}
+
+// Message performs a lookup for the given message name using the given
+// arguments using the current language defined for this controller.
+//
+// The current language is set by the i18n plugin.
+func (c *Controller) Message(message string, args ...interface{}) string {
+       return MessageFunc(c.Request.Locale, message, args...)
+}
+
+// SetAction sets the action that is being invoked in the current request.
+// It sets the following properties: Name, Action, Type, MethodType
+func (c *Controller) SetAction(controllerName, methodName string) error {
+
+       return c.SetTypeAction(controllerName, methodName, nil)
+}
+
+// SetAction sets the assigns the Controller type, sets the action and initializes the controller
+func (c *Controller) SetTypeAction(controllerName, methodName string, typeOfController *ControllerType) error {
+
+       // Look up the controller and method types.
+       if typeOfController == nil {
+               if c.Type = ControllerTypeByName(controllerName, anyModule); c.Type == nil {
+                       return errors.New("revel/controller: failed to find controller " + controllerName)
+               }
+       } else {
+               c.Type = typeOfController
+       }
+
+       // Note method name is case insensitive search
+       if c.MethodType = c.Type.Method(methodName); c.MethodType == nil {
+               return errors.New("revel/controller: failed to find action " + controllerName + "." + methodName)
+       }
+
+       c.Name, c.MethodName = c.Type.Type.Name(), c.MethodType.Name
+       c.Action = c.Name + "." + c.MethodName
+
+       // Update Logger with controller and namespace
+       if c.Log != nil {
+               c.Log = c.Log.New("action", c.Action, "namespace", c.Type.Namespace)
+       }
+
+       if RevelConfig.Controller.Reuse {
+               if _, ok := RevelConfig.Controller.CachedMap[c.Name]; !ok {
+                       // Create a new stack for this controller
+                       localType := c.Type.Type
+                       RevelConfig.Controller.CachedMap[c.Name] = utils.NewStackLock(
+                               RevelConfig.Controller.CachedStackSize,
+                               RevelConfig.Controller.CachedStackMaxSize,
+                               func() interface{} {
+                                       return reflect.New(localType).Interface()
+                               })
+               }
+               // Instantiate the controller.
+               c.AppController = RevelConfig.Controller.CachedMap[c.Name].Pop()
+       } else {
+               c.AppController = reflect.New(c.Type.Type).Interface()
+       }
+       c.setAppControllerFields()
+
+       return nil
+}
+
+func ControllerTypeByName(controllerName string, moduleSource *Module) (c *ControllerType) {
+       var found bool
+       if c, found = controllers[controllerName]; !found {
+               // Backup, passed in controllerName should be in lower case, but may not be
+               if c, found = controllers[strings.ToLower(controllerName)]; !found {
+                       controllerLog.Debug("ControllerTypeByName: Cannot find controller in controllers map ", "controller", controllerName)
+                       // Search for the controller by name
+                       for _, cType := range controllers {
+                               testControllerName := strings.ToLower(cType.Type.Name())
+                               if testControllerName == strings.ToLower(controllerName) && (cType.ModuleSource == moduleSource || moduleSource == anyModule) {
+                                       controllerLog.Warn("ControllerTypeByName: Matched empty namespace controller ", "controller", controllerName, "namespace", cType.ModuleSource.Name)
+                                       c = cType
+                                       found = true
+                                       break
+                               }
+                       }
+               }
+       }
+       return
+}
+
+// Injects this instance (c) into the AppController instance
+func (c *Controller) setAppControllerFields() {
+       appController := reflect.ValueOf(c.AppController).Elem()
+       cValue := reflect.ValueOf(c)
+       for _, index := range c.Type.ControllerIndexes {
+               appController.FieldByIndex(index).Set(cValue)
+       }
+}
+
+// Removes this instance (c) from the AppController instance
+func (c *Controller) resetAppControllerFields() {
+       appController := reflect.ValueOf(c.AppController).Elem()
+       // Zero out controller
+       for _, index := range c.Type.ControllerIndexes {
+               appController.FieldByIndex(index).Set(reflect.Zero(reflect.TypeOf(c.AppController).Elem().FieldByIndex(index).Type))
+       }
+}
+
+func findControllers(appControllerType reflect.Type) (indexes [][]int) {
+       // It might be a multi-level embedding. To find the controllers, we follow
+       // every anonymous field, using breadth-first search.
+       type nodeType struct {
+               val   reflect.Value
+               index []int
+       }
+       appControllerPtr := reflect.New(appControllerType)
+       queue := []nodeType{{appControllerPtr, []int{}}}
+       for len(queue) > 0 {
+               // Get the next value and de-reference it if necessary.
+               var (
+                       node     = queue[0]
+                       elem     = node.val
+                       elemType = elem.Type()
+               )
+               if elemType.Kind() == reflect.Ptr {
+                       elem = elem.Elem()
+                       elemType = elem.Type()
+               }
+               queue = queue[1:]
+
+               // #944 if the type's Kind is not `Struct` move on,
+               // otherwise `elem.NumField()` will panic
+               if elemType.Kind() != reflect.Struct {
+                       continue
+               }
+
+               // Look at all the struct fields.
+               for i := 0; i < elem.NumField(); i++ {
+                       // If this is not an anonymous field, skip it.
+                       structField := elemType.Field(i)
+                       if !structField.Anonymous {
+                               continue
+                       }
+
+                       fieldValue := elem.Field(i)
+                       fieldType := structField.Type
+
+                       // If it's a Controller, record the field indexes to get here.
+                       if fieldType == controllerPtrType {
+                               indexes = append(indexes, append(node.index, i))
+                               continue
+                       }
+
+                       queue = append(queue,
+                               nodeType{fieldValue, append(append([]int{}, node.index...), i)})
+               }
+       }
+       return
+}
+
+// RegisterController registers a Controller and its Methods with Revel.
+func RegisterController(c interface{}, methods []*MethodType) {
+       // De-star the controller type
+       // (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
+       elem := reflect.TypeOf(c).Elem()
+
+       // De-star all of the method arg types too.
+       for _, m := range methods {
+               m.lowerName = strings.ToLower(m.Name)
+               for _, arg := range m.Args {
+                       arg.Type = arg.Type.Elem()
+               }
+       }
+
+       // Fetch module for controller, if none found controller must be part of the app
+       controllerModule := ModuleFromPath(elem.PkgPath(), true)
+
+       controllerType := AddControllerType(controllerModule, elem, methods)
+
+       controllerLog.Debug("RegisterController:Registered controller", "controller", controllerType.Name())
+}
diff --git a/src/foundation/api/revel/controller_type.go b/src/foundation/api/revel/controller_type.go
new file mode 100644 (file)
index 0000000..4ba2f33
--- /dev/null
@@ -0,0 +1,164 @@
+package revel
+
+import (
+       "reflect"
+       "strings"
+)
+
+// Controller registry and types.
+type ControllerType struct {
+       Namespace         string  // The namespace of the controller
+       ModuleSource      *Module // The module for the controller
+       Type              reflect.Type
+       Methods           []*MethodType
+       ControllerIndexes [][]int // FieldByIndex to all embedded *Controllers
+       ControllerEvents  *ControllerTypeEvents
+}
+type ControllerTypeEvents struct {
+       Before, After, Finally, Panic []*ControllerFieldPath
+}
+
+// The controller field path provides the caller the ability to invoke the call
+// directly
+type ControllerFieldPath struct {
+       IsPointer      bool
+       FieldIndexPath []int
+       FunctionCall   reflect.Value
+}
+
+type MethodType struct {
+       Name           string
+       Args           []*MethodArg
+       RenderArgNames map[int][]string
+       lowerName      string
+       Index          int
+}
+
+type MethodArg struct {
+       Name string
+       Type reflect.Type
+}
+
+// Adds the controller to the controllers map using its namespace, also adds it to the module list of controllers.
+// If the controller is in the main application it is added without its namespace as well.
+func AddControllerType(moduleSource *Module, controllerType reflect.Type, methods []*MethodType) (newControllerType *ControllerType) {
+       if moduleSource == nil {
+               moduleSource = appModule
+       }
+
+       newControllerType = &ControllerType{ModuleSource: moduleSource, Type: controllerType, Methods: methods, ControllerIndexes: findControllers(controllerType)}
+       newControllerType.ControllerEvents = NewControllerTypeEvents(newControllerType)
+       newControllerType.Namespace = moduleSource.Namespace()
+       controllerName := newControllerType.Name()
+
+       // Store the first controller only in the controllers map with the unmapped namespace.
+       if _, found := controllers[controllerName]; !found {
+               controllers[controllerName] = newControllerType
+               newControllerType.ModuleSource.AddController(newControllerType)
+               if newControllerType.ModuleSource == appModule {
+                       // Add the controller mapping into the global namespace
+                       controllers[newControllerType.ShortName()] = newControllerType
+               }
+       } else {
+               controllerLog.Errorf("Error, attempt to register duplicate controller as %s", controllerName)
+       }
+       controllerLog.Debugf("Registered controller: %s", controllerName)
+
+       return
+}
+
+// Method searches for a given exported method (case insensitive)
+func (ct *ControllerType) Method(name string) *MethodType {
+       lowerName := strings.ToLower(name)
+       for _, method := range ct.Methods {
+               if method.lowerName == lowerName {
+                       return method
+               }
+       }
+       return nil
+}
+
+// The controller name with the namespace
+func (ct *ControllerType) Name() string {
+       return ct.Namespace + ct.ShortName()
+}
+
+// The controller name without the namespace
+func (ct *ControllerType) ShortName() string {
+       return strings.ToLower(ct.Type.Name())
+}
+
+func NewControllerTypeEvents(c *ControllerType) (ce *ControllerTypeEvents) {
+       ce = &ControllerTypeEvents{}
+       // Parse the methods for the controller type, assign any control methods
+       checkType := c.Type
+       ce.check(checkType, []int{})
+       return
+}
+
+// Add in before after panic and finally, recursive call
+// Befores are ordered in revers, everything else is in order of first encountered
+func (cte *ControllerTypeEvents) check(theType reflect.Type, fieldPath []int) {
+       typeChecker := func(checkType reflect.Type) {
+               for index := 0; index < checkType.NumMethod(); index++ {
+                       m := checkType.Method(index)
+                       // Must be two arguments, the second returns the controller type
+                       // Go cannot differentiate between promoted methods and
+                       // embedded methods, this allows the embedded method to be run
+                       // https://github.com/golang/go/issues/21162
+                       if m.Type.NumOut() == 2 && m.Type.Out(1) == checkType {
+                               if checkType.Kind() == reflect.Ptr {
+                                       controllerLog.Debug("Found controller type event method pointer", "name", checkType.Elem().Name(), "methodname", m.Name)
+                               } else {
+                                       controllerLog.Debug("Found controller type event method", "name", checkType.Name(), "methodname", m.Name)
+                               }
+                               controllerFieldPath := newFieldPath(checkType.Kind() == reflect.Ptr, m.Func, fieldPath)
+                               switch strings.ToLower(m.Name) {
+                               case "before":
+                                       cte.Before = append([]*ControllerFieldPath{controllerFieldPath}, cte.Before...)
+                               case "after":
+                                       cte.After = append(cte.After, controllerFieldPath)
+                               case "panic":
+                                       cte.Panic = append(cte.Panic, controllerFieldPath)
+                               case "finally":
+                                       cte.Finally = append(cte.Finally, controllerFieldPath)
+                               }
+                       }
+               }
+       }
+
+       // Check methods of both types
+       typeChecker(theType)
+       typeChecker(reflect.PtrTo(theType))
+
+       // Check for any sub controllers, ignore any pointers to controllers revel.Controller
+       for i := 0; i < theType.NumField(); i++ {
+               v := theType.Field(i)
+
+               switch v.Type.Kind() {
+               case reflect.Struct:
+                       cte.check(v.Type, append(fieldPath, i))
+               }
+       }
+}
+func newFieldPath(isPointer bool, value reflect.Value, fieldPath []int) *ControllerFieldPath {
+       return &ControllerFieldPath{IsPointer: isPointer, FunctionCall: value, FieldIndexPath: fieldPath}
+}
+
+func (fieldPath *ControllerFieldPath) Invoke(value reflect.Value, input []reflect.Value) (result []reflect.Value) {
+       for _, index := range fieldPath.FieldIndexPath {
+               // You can only fetch fields from non pointers
+               if value.Type().Kind() == reflect.Ptr {
+                       value = value.Elem().Field(index)
+               } else {
+                       value = value.Field(index)
+               }
+       }
+       if fieldPath.IsPointer && value.Type().Kind() != reflect.Ptr {
+               value = value.Addr()
+       } else if !fieldPath.IsPointer && value.Type().Kind() == reflect.Ptr {
+               value = value.Elem()
+       }
+
+       return fieldPath.FunctionCall.Call(append([]reflect.Value{value}, input...))
+}
diff --git a/src/foundation/api/revel/errors.go b/src/foundation/api/revel/errors.go
new file mode 100644 (file)
index 0000000..dc3807a
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "path/filepath"
+       "runtime/debug"
+       "strconv"
+       "strings"
+)
+
+// Error description, used as an argument to the error template.
+type Error struct {
+       SourceType               string   // The type of source that failed to build.
+       Title, Path, Description string   // Description of the error, as presented to the user.
+       Line, Column             int      // Where the error was encountered.
+       SourceLines              []string // The entire source file, split into lines.
+       Stack                    string   // The raw stack trace string from debug.Stack().
+       MetaError                string   // Error that occurred producing the error page.
+       Link                     string   // A configurable link to wrap the error source in
+}
+
+// SourceLine structure to hold the per-source-line details.
+type SourceLine struct {
+       Source  string
+       Line    int
+       IsError bool
+}
+
+// NewErrorFromPanic method finds the deepest stack from in user code and
+// provide a code listing of that, on the line that eventually triggered
+// the panic.  Returns nil if no relevant stack frame can be found.
+func NewErrorFromPanic(err interface{}) *Error {
+
+       // Parse the filename and line from the originating line of app code.
+       // /Users/robfig/code/gocode/src/revel/examples/booking/app/controllers/hotels.go:191 (0x44735)
+       stack := string(debug.Stack())
+       frame, basePath := findRelevantStackFrame(stack)
+       if frame == -1 {
+               return nil
+       }
+
+       stack = stack[frame:]
+       stackElement := stack[:strings.Index(stack, "\n")]
+       colonIndex := strings.LastIndex(stackElement, ":")
+       filename := stackElement[:colonIndex]
+       var line int
+       fmt.Sscan(stackElement[colonIndex+1:], &line)
+
+       // Show an error page.
+       description := "Unspecified error"
+       if err != nil {
+               description = fmt.Sprint(err)
+       }
+       lines, readErr := ReadLines(filename)
+       if readErr != nil {
+               utilLog.Error("Unable to read file", "file", filename, "error", readErr)
+       }
+       return &Error{
+               Title:       "Runtime Panic",
+               Path:        filename[len(basePath):],
+               Line:        line,
+               Description: description,
+               SourceLines: lines,
+               Stack:       stack,
+       }
+}
+
+// Error method constructs a plaintext version of the error, taking
+// account that fields are optionally set. Returns e.g. Compilation Error
+// (in views/header.html:51): expected right delim in end; got "}"
+func (e *Error) Error() string {
+       loc := ""
+       if e.Path != "" {
+               line := ""
+               if e.Line != 0 {
+                       line = fmt.Sprintf(":%d", e.Line)
+               }
+               loc = fmt.Sprintf("(in %s%s)", e.Path, line)
+       }
+       header := loc
+       if e.Title != "" {
+               if loc != "" {
+                       header = fmt.Sprintf("%s %s: ", e.Title, loc)
+               } else {
+                       header = fmt.Sprintf("%s: ", e.Title)
+               }
+       }
+       return fmt.Sprintf("%s%s Stack: %s", header, e.Description, e.Stack)
+}
+
+// ContextSource method returns a snippet of the source around
+// where the error occurred.
+func (e *Error) ContextSource() []SourceLine {
+       if e.SourceLines == nil {
+               return nil
+       }
+       start := (e.Line - 1) - 5
+       if start < 0 {
+               start = 0
+       }
+       end := (e.Line - 1) + 5
+       if end > len(e.SourceLines) {
+               end = len(e.SourceLines)
+       }
+
+       lines := make([]SourceLine, end-start)
+       for i, src := range e.SourceLines[start:end] {
+               fileLine := start + i + 1
+               lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
+       }
+       return lines
+}
+
+// SetLink method prepares a link and assign to Error.Link attribute
+func (e *Error) SetLink(errorLink string) {
+       errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
+       errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
+
+       e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
+}
+
+// Return the character index of the first relevant stack frame, or -1 if none were found.
+// Additionally it returns the base path of the tree in which the identified code resides.
+func findRelevantStackFrame(stack string) (int, string) {
+       // Find first item in SourcePath that isn't in RevelPath.
+       // If first item is in RevelPath, keep track of position, trim and check again.
+       partialStack := stack
+       sourcePath := filepath.ToSlash(SourcePath)
+       revelPath := filepath.ToSlash(RevelPath)
+       sumFrame := 0
+       for {
+               frame := strings.Index(partialStack, sourcePath)
+               revelFrame := strings.Index(partialStack, revelPath)
+
+               if frame == -1 {
+                       break
+               } else if frame != revelFrame {
+                       return sumFrame + frame, SourcePath
+               } else {
+                       // Need to at least trim off the first character so this frame isn't caught again.
+                       partialStack = partialStack[frame+1:]
+                       sumFrame += frame + 1
+               }
+       }
+       for _, module := range Modules {
+               if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
+                       return frame, module.Path
+               }
+       }
+       return -1, ""
+}
diff --git a/src/foundation/api/revel/event.go b/src/foundation/api/revel/event.go
new file mode 100644 (file)
index 0000000..9c66fb0
--- /dev/null
@@ -0,0 +1,57 @@
+package revel
+
+type (
+       // The event type
+       Event int
+       // The event response
+       EventResponse int
+       // The handler signature
+       EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse)
+)
+
+const (
+       // Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
+       TEMPLATE_REFRESH_REQUESTED Event = iota
+       // Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
+       TEMPLATE_REFRESH_COMPLETED
+       // Event type before all module loads, events thrown to handlers added to AddInitEventHandler
+
+       // Event type before all module loads, events thrown to handlers added to AddInitEventHandler
+       REVEL_BEFORE_MODULES_LOADED
+       // Event type after all module loads, events thrown to handlers added to AddInitEventHandler
+       REVEL_AFTER_MODULES_LOADED
+
+       // Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
+       ENGINE_BEFORE_INITIALIZED
+       // Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
+       ENGINE_STARTED
+
+       // Event raised when the engine is told to shutdown
+       ENGINE_SHUTDOWN_REQUEST
+
+       // Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
+       ENGINE_SHUTDOWN
+
+       // Called before routes are refreshed
+       ROUTE_REFRESH_REQUESTED
+       // Called after routes have been refreshed
+       ROUTE_REFRESH_COMPLETED
+
+       // Fired when a panic is caught during the startup process
+       REVEL_FAILURE
+)
+
+// Fires system events from revel
+func RaiseEvent(key Event, value interface{}) (response EventResponse) {
+       utilLog.Info("Raising event", "len", len(initEventList))
+       for _, handler := range initEventList {
+               response |= handler(key, value)
+       }
+       return
+}
+
+// Add event handler to listen for all system events
+func AddInitEventHandler(handler EventHandler) {
+       initEventList = append(initEventList, handler)
+       return
+}
diff --git a/src/foundation/api/revel/event_test.go b/src/foundation/api/revel/event_test.go
new file mode 100644 (file)
index 0000000..0baadc0
--- /dev/null
@@ -0,0 +1,24 @@
+package revel_test
+
+import (
+       "github.com/revel/revel"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+// Test that the event handler can be attached and it dispatches the event received
+func TestEventHandler(t *testing.T) {
+       counter := 0
+       newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) {
+               if typeOf == revel.REVEL_FAILURE {
+                       counter++
+               }
+               return
+       }
+       // Attach the same handlder twice so we expect to see the response twice as well
+       revel.AddInitEventHandler(newListener)
+       revel.AddInitEventHandler(newListener)
+       revel.RaiseEvent(revel.REVEL_AFTER_MODULES_LOADED, nil)
+       revel.RaiseEvent(revel.REVEL_FAILURE, nil)
+       assert.Equal(t, counter, 2, "Expected event handler to have been called")
+}
diff --git a/src/foundation/api/revel/fakeapp_test.go b/src/foundation/api/revel/fakeapp_test.go
new file mode 100644 (file)
index 0000000..a2bcabd
--- /dev/null
@@ -0,0 +1,152 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "os"
+       "path/filepath"
+       "reflect"
+)
+
+type Hotel struct {
+       HotelID          int
+       Name, Address    string
+       City, State, Zip string
+       Country          string
+       Price            int
+}
+
+type Hotels struct {
+       *Controller
+}
+
+type Static struct {
+       *Controller
+}
+
+type Implicit struct {
+       *Controller
+}
+
+type Application struct {
+       *Controller
+}
+
+func (c Hotels) Show(id int) Result {
+       title := "View Hotel"
+       hotel := &Hotel{id, "A Hotel", "300 Main St.", "New York", "NY", "10010", "USA", 300}
+       // The line number below must match the one with the code : RenderArgNames: map[int][]string{43: {"title", "hotel"}},
+       return c.Render(title, hotel)
+}
+
+func (c Hotels) Book(id int) Result {
+       hotel := &Hotel{id, "A Hotel", "300 Main St.", "New York", "NY", "10010", "USA", 300}
+       return c.RenderJSON(hotel)
+}
+
+func (c Hotels) Index() Result {
+       return c.RenderText("Hello, World!")
+}
+
+func (c Static) Serve(prefix, path string) Result {
+       var basePath, dirName string
+
+       if !filepath.IsAbs(dirName) {
+               basePath = BasePath
+       }
+
+       fname := filepath.Join(basePath, prefix, path)
+       file, err := os.Open(fname)
+       if os.IsNotExist(err) {
+               return c.NotFound("")
+       } else if err != nil {
+               RevelLog.Errorf("Problem opening file (%s): %s ", fname, err)
+               return c.NotFound("This was found but not sure why we couldn't open it.")
+       }
+       return c.RenderFile(file, "")
+}
+
+// Register controllers is in its own function so the route test can use it as well
+func registerControllers() {
+       controllers = make(map[string]*ControllerType)
+       RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
+       RegisterController((*Hotels)(nil),
+               []*MethodType{
+                       {
+                               Name: "Index",
+                       },
+                       {
+                               Name: "Show",
+                               Args: []*MethodArg{
+                                       {"id", reflect.TypeOf((*int)(nil))},
+                               },
+                               RenderArgNames: map[int][]string{41: {"title", "hotel"}},
+                       },
+                       {
+                               Name: "Book",
+                               Args: []*MethodArg{
+                                       {"id", reflect.TypeOf((*int)(nil))},
+                               },
+                       },
+               })
+
+       RegisterController((*Static)(nil),
+               []*MethodType{
+                       {
+                               Name: "Serve",
+                               Args: []*MethodArg{
+                                       {Name: "prefix", Type: reflect.TypeOf((*string)(nil))},
+                                       {Name: "filepath", Type: reflect.TypeOf((*string)(nil))},
+                               },
+                               RenderArgNames: map[int][]string{},
+                       },
+               })
+       RegisterController((*Implicit)(nil),
+               []*MethodType{
+                       {
+                               Name: "Implicit",
+                               Args: []*MethodArg{
+                                       {Name: "prefix", Type: reflect.TypeOf((*string)(nil))},
+                                       {Name: "filepath", Type: reflect.TypeOf((*string)(nil))},
+                               },
+                               RenderArgNames: map[int][]string{},
+                       },
+               })
+       RegisterController((*Application)(nil),
+               []*MethodType{
+                       {
+                               Name: "Application",
+                               Args: []*MethodArg{
+                                       {Name: "prefix", Type: reflect.TypeOf((*string)(nil))},
+                                       {Name: "filepath", Type: reflect.TypeOf((*string)(nil))},
+                               },
+                               RenderArgNames: map[int][]string{},
+                       },
+                       {
+                               Name: "Index",
+                               Args: []*MethodArg{
+                                       {Name: "foo", Type: reflect.TypeOf((*string)(nil))},
+                                       {Name: "bar", Type: reflect.TypeOf((*string)(nil))},
+                               },
+                               RenderArgNames: map[int][]string{},
+                       },
+               })
+}
+func startFakeBookingApp() {
+       Init("prod", "github.com/revel/revel/testdata", "")
+
+       MainTemplateLoader = NewTemplateLoader([]string{ViewsPath, filepath.Join(RevelPath, "templates")})
+       if err := MainTemplateLoader.Refresh(); err != nil {
+               RevelLog.Fatal("Template error","error",err)
+       }
+
+       registerControllers()
+
+       InitServerEngine(9000, GO_NATIVE_SERVER_ENGINE)
+       RaiseEvent(ENGINE_BEFORE_INITIALIZED, nil)
+       InitServer()
+
+       RaiseEvent(ENGINE_STARTED, nil)
+}
diff --git a/src/foundation/api/revel/field.go b/src/foundation/api/revel/field.go
new file mode 100644 (file)
index 0000000..93f2b1c
--- /dev/null
@@ -0,0 +1,108 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "reflect"
+       "strings"
+)
+
+// Field represents a data field that may be collected in a web form.
+type Field struct {
+       Name       string
+       Error      *ValidationError
+       viewArgs   map[string]interface{}
+       controller *Controller
+}
+
+func NewField(name string, viewArgs map[string]interface{}) *Field {
+       err, _ := viewArgs["errors"].(map[string]*ValidationError)[name]
+       controller, _ := viewArgs["_controller"].(*Controller)
+       return &Field{
+               Name:       name,
+               Error:      err,
+               viewArgs:   viewArgs,
+               controller: controller,
+       }
+}
+
+// ID returns an identifier suitable for use as an HTML id.
+func (f *Field) ID() string {
+       return strings.Replace(f.Name, ".", "_", -1)
+}
+
+// Flash returns the flashed value of this Field.
+func (f *Field) Flash() string {
+       v, _ := f.viewArgs["flash"].(map[string]string)[f.Name]
+       return v
+}
+
+// Options returns the option list of this Field.
+func (f *Field) Options() []string {
+       if f.viewArgs["options"] == nil {
+               return nil
+       }
+       v, _ := f.viewArgs["options"].(map[string][]string)[f.Name]
+       return v
+}
+
+// FlashArray returns the flashed value of this Field as a list split on comma.
+func (f *Field) FlashArray() []string {
+       v := f.Flash()
+       if v == "" {
+               return []string{}
+       }
+       return strings.Split(v, ",")
+}
+
+// Value returns the current value of this Field.
+func (f *Field) Value() interface{} {
+       pieces := strings.Split(f.Name, ".")
+       answer, ok := f.viewArgs[pieces[0]]
+       if !ok {
+               return ""
+       }
+
+       val := reflect.ValueOf(answer)
+       for i := 1; i < len(pieces); i++ {
+               if val.Kind() == reflect.Ptr {
+                       val = val.Elem()
+               }
+               val = val.FieldByName(pieces[i])
+               if !val.IsValid() {
+                       return ""
+               }
+       }
+
+       return val.Interface()
+}
+
+// ErrorClass returns ErrorCSSClass if this field has a validation error, else empty string.
+func (f *Field) ErrorClass() string {
+       if f.Error != nil {
+               if errorClass, ok := f.viewArgs["ERROR_CLASS"]; ok {
+                       return errorClass.(string)
+               }
+               return ErrorCSSClass
+       }
+       return ""
+}
+
+// Get the short name and translate it
+func (f *Field) ShortName() string {
+       name := f.Name
+       if i := strings.LastIndex(name, "."); i > 0 {
+               name = name[i+1:]
+       }
+       return f.Translate(name)
+}
+
+// Translate the text
+func (f *Field) Translate(text string, args ...interface{}) string {
+       if f.controller != nil {
+               text = f.controller.Message(text, args...)
+       }
+       return text
+}
diff --git a/src/foundation/api/revel/filter.go b/src/foundation/api/revel/filter.go
new file mode 100644 (file)
index 0000000..2226dff
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+// Filter type definition for Revel's filter
+type Filter func(c *Controller, filterChain []Filter)
+
+// Filters is the default set of global filters.
+// It may be set by the application on initialization.
+var Filters = []Filter{
+       PanicFilter,             // Recover from panics and display an error page instead.
+       RouterFilter,            // Use the routing table to select the right Action.
+       FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
+       ParamsFilter,            // Parse parameters into Controller.Params.
+       SessionFilter,           // Restore and write the session cookie.
+       FlashFilter,             // Restore and write the flash cookie.
+       ValidationFilter,        // Restore kept validation errors and save new ones from cookie.
+       I18nFilter,              // Resolve the requested language.
+       InterceptorFilter,       // Run interceptors around the action.
+       CompressFilter,          // Compress the result.
+       BeforeAfterFilter,
+       ActionInvoker, // Invoke the action.
+}
+
+// NilFilter and NilChain are helpful in writing filter tests.
+var (
+       NilFilter = func(_ *Controller, _ []Filter) {}
+       NilChain  = []Filter{NilFilter}
+)
diff --git a/src/foundation/api/revel/filterconfig.go b/src/foundation/api/revel/filterconfig.go
new file mode 100644 (file)
index 0000000..010d7ea
--- /dev/null
@@ -0,0 +1,223 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "reflect"
+       "strings"
+)
+
+// Map from "Controller" or "Controller.Method" to the Filter chain
+var filterOverrides = make(map[string][]Filter)
+
+// FilterConfigurator allows the developer configure the filter chain on a
+// per-controller or per-action basis.  The filter configuration is applied by
+// the FilterConfiguringFilter, which is itself a filter stage.  For example,
+//
+// Assuming:
+//   Filters = []Filter{
+//     RouterFilter,
+//     FilterConfiguringFilter,
+//     SessionFilter,
+//     ActionInvoker,
+//   }
+//
+// Add:
+//   FilterAction(App.Action).
+//     Add(OtherFilter)
+//
+//   => RouterFilter, FilterConfiguringFilter, SessionFilter, OtherFilter, ActionInvoker
+//
+// Remove:
+//   FilterAction(App.Action).
+//     Remove(SessionFilter)
+//
+//   => RouterFilter, FilterConfiguringFilter, OtherFilter, ActionInvoker
+//
+// Insert:
+//   FilterAction(App.Action).
+//     Insert(OtherFilter, revel.BEFORE, SessionFilter)
+//
+//   => RouterFilter, FilterConfiguringFilter, OtherFilter, SessionFilter, ActionInvoker
+//
+// Filter modifications may be combined between Controller and Action.  For example:
+//   FilterController(App{}).
+//     Add(Filter1)
+//   FilterAction(App.Action).
+//     Add(Filter2)
+//
+//  .. would result in App.Action being filtered by both Filter1 and Filter2.
+//
+// Note: the last filter stage is not subject to the configurator.  In
+// particular, Add() adds a filter to the second-to-last place.
+type FilterConfigurator struct {
+       key            string // e.g. "App", "App.Action"
+       controllerName string // e.g. "App"
+}
+
+func newFilterConfigurator(controllerName, methodName string) FilterConfigurator {
+       if methodName == "" {
+               return FilterConfigurator{controllerName, controllerName}
+       }
+       return FilterConfigurator{controllerName + "." + methodName, controllerName}
+}
+
+// FilterController returns a configurator for the filters applied to all
+// actions on the given controller instance.  For example:
+//   FilterController(MyController{})
+func FilterController(controllerInstance interface{}) FilterConfigurator {
+       t := reflect.TypeOf(controllerInstance)
+       for t.Kind() == reflect.Ptr {
+               t = t.Elem()
+       }
+       return newFilterConfigurator(t.Name(), "")
+}
+
+// FilterAction returns a configurator for the filters applied to the given
+// controller method. For example:
+//   FilterAction(MyController.MyAction)
+func FilterAction(methodRef interface{}) FilterConfigurator {
+       var (
+               methodValue = reflect.ValueOf(methodRef)
+               methodType  = methodValue.Type()
+       )
+       if methodType.Kind() != reflect.Func || methodType.NumIn() == 0 {
+               panic("Expecting a controller method reference (e.g. Controller.Action), got a " +
+                       methodType.String())
+       }
+
+       controllerType := methodType.In(0)
+       method := FindMethod(controllerType, methodValue)
+       if method == nil {
+               panic("Action not found on controller " + controllerType.Name())
+       }
+
+       for controllerType.Kind() == reflect.Ptr {
+               controllerType = controllerType.Elem()
+       }
+
+       return newFilterConfigurator(controllerType.Name(), method.Name)
+}
+
+// Add the given filter in the second-to-last position in the filter chain.
+// (Second-to-last so that it is before ActionInvoker)
+func (conf FilterConfigurator) Add(f Filter) FilterConfigurator {
+       conf.apply(func(fc []Filter) []Filter {
+               return conf.addFilter(f, fc)
+       })
+       return conf
+}
+
+func (conf FilterConfigurator) addFilter(f Filter, fc []Filter) []Filter {
+       return append(fc[:len(fc)-1], f, fc[len(fc)-1])
+}
+
+// Remove a filter from the filter chain.
+func (conf FilterConfigurator) Remove(target Filter) FilterConfigurator {
+       conf.apply(func(fc []Filter) []Filter {
+               return conf.rmFilter(target, fc)
+       })
+       return conf
+}
+
+func (conf FilterConfigurator) rmFilter(target Filter, fc []Filter) []Filter {
+       for i, f := range fc {
+               if FilterEq(f, target) {
+                       return append(fc[:i], fc[i+1:]...)
+               }
+       }
+       return fc
+}
+
+// Insert a filter into the filter chain before or after another.
+// This may be called with the BEFORE or AFTER constants, for example:
+//   revel.FilterAction(App.Index).
+//     Insert(MyFilter, revel.BEFORE, revel.ActionInvoker).
+//     Insert(MyFilter2, revel.AFTER, revel.PanicFilter)
+func (conf FilterConfigurator) Insert(insert Filter, where When, target Filter) FilterConfigurator {
+       if where != BEFORE && where != AFTER {
+               panic("where must be BEFORE or AFTER")
+       }
+       conf.apply(func(fc []Filter) []Filter {
+               return conf.insertFilter(insert, where, target, fc)
+       })
+       return conf
+}
+
+func (conf FilterConfigurator) insertFilter(insert Filter, where When, target Filter, fc []Filter) []Filter {
+       for i, f := range fc {
+               if FilterEq(f, target) {
+                       if where == BEFORE {
+                               return append(fc[:i], append([]Filter{insert}, fc[i:]...)...)
+                       }
+                       return append(fc[:i+1], append([]Filter{insert}, fc[i+1:]...)...)
+               }
+       }
+       return fc
+}
+
+// getChain returns the filter chain that applies to the given controller or
+// action.  If no overrides are configured, then a copy of the default filter
+// chain is returned.
+func (conf FilterConfigurator) getChain() []Filter {
+       var filters []Filter
+       if filters = getOverrideChain(conf.controllerName, conf.key); filters == nil {
+               // The override starts with all filters after FilterConfiguringFilter
+               for i, f := range Filters {
+                       if FilterEq(f, FilterConfiguringFilter) {
+                               filters = make([]Filter, len(Filters)-i-1)
+                               copy(filters, Filters[i+1:])
+                               break
+                       }
+               }
+               if filters == nil {
+                       panic("FilterConfiguringFilter not found in revel.Filters.")
+               }
+       }
+       return filters
+}
+
+// apply applies the given functional change to the filter overrides.
+// No other function modifies the filterOverrides map.
+func (conf FilterConfigurator) apply(f func([]Filter) []Filter) {
+       // Updates any actions that have had their filters overridden, if this is a
+       // Controller configurator.
+       if conf.controllerName == conf.key {
+               for k, v := range filterOverrides {
+                       if strings.HasPrefix(k, conf.controllerName+".") {
+                               filterOverrides[k] = f(v)
+                       }
+               }
+       }
+
+       // Update the Controller or Action overrides.
+       filterOverrides[conf.key] = f(conf.getChain())
+}
+
+// FilterEq returns true if the two filters reference the same filter.
+func FilterEq(a, b Filter) bool {
+       return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer()
+}
+
+// FilterConfiguringFilter is a filter stage that customizes the remaining
+// filter chain for the action being invoked.
+func FilterConfiguringFilter(c *Controller, fc []Filter) {
+       if newChain := getOverrideChain(c.Name, c.Action); newChain != nil {
+               newChain[0](c, newChain[1:])
+               return
+       }
+       fc[0](c, fc[1:])
+}
+
+// getOverrideChain retrieves the overrides for the action that is set
+func getOverrideChain(controllerName, action string) []Filter {
+       if newChain, ok := filterOverrides[action]; ok {
+               return newChain
+       }
+       if newChain, ok := filterOverrides[controllerName]; ok {
+               return newChain
+       }
+       return nil
+}
diff --git a/src/foundation/api/revel/filterconfig_test.go b/src/foundation/api/revel/filterconfig_test.go
new file mode 100644 (file)
index 0000000..a3973f9
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import "testing"
+
+type FakeController struct{}
+
+func (c FakeController) Foo()  {}
+func (c *FakeController) Bar() {}
+
+func TestFilterConfiguratorKey(t *testing.T) {
+       conf := FilterController(FakeController{})
+       if conf.key != "FakeController" {
+               t.Errorf("Expected key 'FakeController', was %s", conf.key)
+       }
+
+       conf = FilterController(&FakeController{})
+       if conf.key != "FakeController" {
+               t.Errorf("Expected key 'FakeController', was %s", conf.key)
+       }
+
+       conf = FilterAction(FakeController.Foo)
+       if conf.key != "FakeController.Foo" {
+               t.Errorf("Expected key 'FakeController.Foo', was %s", conf.key)
+       }
+
+       conf = FilterAction((*FakeController).Bar)
+       if conf.key != "FakeController.Bar" {
+               t.Errorf("Expected key 'FakeController.Bar', was %s", conf.key)
+       }
+}
+
+func TestFilterConfigurator(t *testing.T) {
+       // Filters is global state.  Restore it after this test.
+       oldFilters := make([]Filter, len(Filters))
+       copy(oldFilters, Filters)
+       defer func() {
+               Filters = oldFilters
+       }()
+
+       Filters = []Filter{
+               RouterFilter,
+               FilterConfiguringFilter,
+               SessionFilter,
+               FlashFilter,
+               ActionInvoker,
+       }
+
+       // Do one of each operation.
+       conf := FilterAction(FakeController.Foo).
+               Add(NilFilter).
+               Remove(FlashFilter).
+               Insert(ValidationFilter, BEFORE, NilFilter).
+               Insert(I18nFilter, AFTER, NilFilter)
+       expected := []Filter{
+               SessionFilter,
+               ValidationFilter,
+               NilFilter,
+               I18nFilter,
+               ActionInvoker,
+       }
+       actual := getOverride("Foo")
+       if len(actual) != len(expected) || !filterSliceEqual(actual, expected) {
+               t.Errorf("Ops failed.\nActual: %#v\nExpect: %#v\nConf:%v", actual, expected, conf)
+       }
+
+       // Action2 should be unchanged
+       if getOverride("Bar") != nil {
+               t.Errorf("Filtering Action should not affect Action2.")
+       }
+
+       // Test that combining overrides on both the Controller and Action works.
+       FilterController(FakeController{}).
+               Add(PanicFilter)
+       expected = []Filter{
+               SessionFilter,
+               ValidationFilter,
+               NilFilter,
+               I18nFilter,
+               PanicFilter,
+               ActionInvoker,
+       }
+       actual = getOverride("Foo")
+       if len(actual) != len(expected) || !filterSliceEqual(actual, expected) {
+               t.Errorf("Expected PanicFilter added to Foo.\nActual: %#v\nExpect: %#v", actual, expected)
+       }
+
+       expected = []Filter{
+               SessionFilter,
+               FlashFilter,
+               PanicFilter,
+               ActionInvoker,
+       }
+       actual = getOverride("Bar")
+       if len(actual) != len(expected) || !filterSliceEqual(actual, expected) {
+               t.Errorf("Expected PanicFilter added to Bar.\nActual: %#v\nExpect: %#v", actual, expected)
+       }
+
+       FilterAction((*FakeController).Bar).
+               Add(NilFilter)
+       expected = []Filter{
+               SessionFilter,
+               ValidationFilter,
+               NilFilter,
+               I18nFilter,
+               PanicFilter,
+               ActionInvoker,
+       }
+       actual = getOverride("Foo")
+       if len(actual) != len(expected) || !filterSliceEqual(actual, expected) {
+               t.Errorf("Expected no change to Foo.\nActual: %#v\nExpect: %#v", actual, expected)
+       }
+
+       expected = []Filter{
+               SessionFilter,
+               FlashFilter,
+               PanicFilter,
+               NilFilter,
+               ActionInvoker,
+       }
+       actual = getOverride("Bar")
+       if len(actual) != len(expected) || !filterSliceEqual(actual, expected) {
+               t.Errorf("Expected NilFilter added to Bar.\nActual: %#v\nExpect: %#v", actual, expected)
+       }
+}
+
+func filterSliceEqual(a, e []Filter) bool {
+       for i, f := range a {
+               if !FilterEq(f, e[i]) {
+                       return false
+               }
+       }
+       return true
+}
+
+func getOverride(methodName string) []Filter {
+       return getOverrideChain("FakeController", "FakeController."+methodName)
+}
diff --git a/src/foundation/api/revel/flash.go b/src/foundation/api/revel/flash.go
new file mode 100644 (file)
index 0000000..b27f5e0
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+)
+
+// Flash represents a cookie that is overwritten on each request.
+// It allows data to be stored across one page at a time.
+// This is commonly used to implement success or error messages.
+// E.g. the Post/Redirect/Get pattern:
+// http://en.wikipedia.org/wiki/Post/Redirect/Get
+type Flash struct {
+       // `Data` is the input which is read in `restoreFlash`, `Out` is the output which is set in a FLASH cookie at the end of the `FlashFilter()`
+       Data, Out map[string]string
+}
+
+// Error serializes the given msg and args to an "error" key within
+// the Flash cookie.
+func (f Flash) Error(msg string, args ...interface{}) {
+       if len(args) == 0 {
+               f.Out["error"] = msg
+       } else {
+               f.Out["error"] = fmt.Sprintf(msg, args...)
+       }
+}
+
+// Success serializes the given msg and args to a "success" key within
+// the Flash cookie.
+func (f Flash) Success(msg string, args ...interface{}) {
+       if len(args) == 0 {
+               f.Out["success"] = msg
+       } else {
+               f.Out["success"] = fmt.Sprintf(msg, args...)
+       }
+}
+
+// FlashFilter is a Revel Filter that retrieves and sets the flash cookie.
+// Within Revel, it is available as a Flash attribute on Controller instances.
+// The name of the Flash cookie is set as CookiePrefix + "_FLASH".
+func FlashFilter(c *Controller, fc []Filter) {
+       c.Flash = restoreFlash(c.Request)
+       c.ViewArgs["flash"] = c.Flash.Data
+
+       fc[0](c, fc[1:])
+
+       // Store the flash.
+       var flashValue string
+       for key, value := range c.Flash.Out {
+               flashValue += "\x00" + key + ":" + value + "\x00"
+       }
+       c.SetCookie(&http.Cookie{
+               Name:     CookiePrefix + "_FLASH",
+               Value:    url.QueryEscape(flashValue),
+               HttpOnly: true,
+               Secure:   CookieSecure,
+               Path:     "/",
+       })
+}
+
+// restoreFlash deserializes a Flash cookie struct from a request.
+func restoreFlash(req *Request) Flash {
+       flash := Flash{
+               Data: make(map[string]string),
+               Out:  make(map[string]string),
+       }
+       if cookie, err := req.Cookie(CookiePrefix + "_FLASH"); err == nil {
+               ParseKeyValueCookie(cookie.GetValue(), func(key, val string) {
+                       flash.Data[key] = val
+               })
+       }
+       return flash
+}
diff --git a/src/foundation/api/revel/http.go b/src/foundation/api/revel/http.go
new file mode 100644 (file)
index 0000000..dbdd58d
--- /dev/null
@@ -0,0 +1,489 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+
+       "context"
+       "mime/multipart"
+       "path/filepath"
+)
+
+// Request is Revel's HTTP request object structure
+type Request struct {
+       In              ServerRequest   // The server request
+       Header          *RevelHeader    // The revel header
+       ContentType     string          // The content type
+       Format          string          // The output format "html", "xml", "json", or "txt"
+       AcceptLanguages AcceptLanguages // The languages to accept
+       Locale          string          // THe locale
+       WebSocket       ServerWebSocket // The websocket
+       Method          string          // The method
+       RemoteAddr      string          // The remote address
+       Host            string          // The host
+       // URL request path from the server (built)
+       URL *url.URL // The url
+       // DEPRECATED use GetForm()
+       Form url.Values // The Form
+       // DEPRECATED use GetMultipartForm()
+       MultipartForm *MultipartForm // The multipart form
+       controller    *Controller    // The controller, so some of this data can be fetched
+}
+
+var FORM_NOT_FOUND = errors.New("Form Not Found")
+var httpLog = RevelLog.New("section", "http")
+
+// Response is Revel's HTTP response object structure
+type Response struct {
+       Status      int
+       ContentType string
+       Out         OutResponse
+       writer      io.Writer
+}
+
+// The output response
+type OutResponse struct {
+       // internalHeader.Server Set by ServerResponse.Get(HTTP_SERVER_HEADER), saves calling the get every time the header needs to be written to
+       internalHeader *RevelHeader   // The internal header
+       Server         ServerResponse // The server response
+       response       *Response      // The response
+}
+
+// The header defined in Revel
+type RevelHeader struct {
+       Server ServerHeader // The server
+}
+
+// NewResponse wraps ServerResponse inside a Revel's Response and returns it
+func NewResponse(w ServerResponse) (r *Response) {
+       r = &Response{Out: OutResponse{Server: w, internalHeader: &RevelHeader{}}}
+       r.Out.response = r
+       return r
+}
+
+// NewRequest returns a Revel's HTTP request instance with given HTTP instance
+func NewRequest(r ServerRequest) *Request {
+       req := &Request{Header: &RevelHeader{}}
+       if r != nil {
+               req.SetRequest(r)
+       }
+       return req
+}
+func (req *Request) SetRequest(r ServerRequest) {
+       req.In = r
+       if h, e := req.In.Get(HTTP_SERVER_HEADER); e == nil {
+               req.Header.Server = h.(ServerHeader)
+       }
+
+       req.URL, _ = req.GetValue(HTTP_URL).(*url.URL)
+       req.ContentType = ResolveContentType(req)
+       req.Format = ResolveFormat(req)
+       req.AcceptLanguages = ResolveAcceptLanguage(req)
+       req.Method, _ = req.GetValue(HTTP_METHOD).(string)
+       req.RemoteAddr, _ = req.GetValue(HTTP_REMOTE_ADDR).(string)
+       req.Host, _ = req.GetValue(HTTP_HOST).(string)
+
+}
+
+// Returns a cookie
+func (req *Request) Cookie(key string) (ServerCookie, error) {
+       if req.Header.Server != nil {
+               return req.Header.Server.GetCookie(key)
+       }
+       return nil, http.ErrNoCookie
+}
+
+// Fetch the requested URI
+func (req *Request) GetRequestURI() string {
+       uri, _ := req.GetValue(HTTP_REQUEST_URI).(string)
+       return uri
+}
+
+// Fetch the query
+func (req *Request) GetQuery() (v url.Values) {
+       v, _ = req.GetValue(ENGINE_PARAMETERS).(url.Values)
+       return
+}
+
+// Fetch the path
+func (req *Request) GetPath() (path string) {
+       path, _ = req.GetValue(ENGINE_PATH).(string)
+       return
+}
+
+// Fetch the body
+func (req *Request) GetBody() (body io.Reader) {
+       body, _ = req.GetValue(HTTP_BODY).(io.Reader)
+       return
+}
+
+// Fetch the context
+func (req *Request) Context() (c context.Context) {
+       c, _ = req.GetValue(HTTP_REQUEST_CONTEXT).(context.Context)
+       return
+}
+
+// Deprecated use controller.Params.Get()
+func (req *Request) FormValue(key string) (value string) {
+       return req.controller.Params.Get(key)
+}
+
+// Deprecated use controller.Params.Form[Key]
+func (req *Request) PostFormValue(key string) (value string) {
+       valueList := req.controller.Params.Form[key]
+       if len(valueList) > 0 {
+               value = valueList[0]
+       }
+       return
+}
+
+// Deprecated use GetForm() instead
+func (req *Request) ParseForm() (e error) {
+       if req.Form == nil {
+               req.Form, e = req.GetForm()
+       }
+       return
+}
+
+func (req *Request) GetForm() (url.Values, error) {
+       if form, err := req.In.Get(HTTP_FORM); err != nil {
+               return nil, err
+       } else if values, found := form.(url.Values); found {
+               req.Form = values
+               return values, nil
+       }
+       return nil, FORM_NOT_FOUND
+}
+
+// Deprecated for backwards compatibility only
+type MultipartForm struct {
+       File   map[string][]*multipart.FileHeader
+       Value  url.Values
+       origin ServerMultipartForm
+}
+
+func (req *Request) MultipartReader() (*multipart.Reader, error) {
+
+       return nil, errors.New("MultipartReader not supported, use controller.Param")
+}
+
+// Deprecated for backwards compatibility only
+func newMultipareForm(s ServerMultipartForm) (f *MultipartForm) {
+       return &MultipartForm{File: s.GetFiles(), Value: s.GetValues(), origin: s}
+}
+
+// Deprecated use GetMultipartForm() instead
+func (req *Request) ParseMultipartForm(_ int64) (e error) {
+       var s ServerMultipartForm
+       if s, e = req.GetMultipartForm(); e == nil {
+               req.MultipartForm = newMultipareForm(s)
+       }
+       return
+}
+
+// Return the args for the controller
+func (req *Request) Args() map[string]interface{} {
+       return req.controller.Args
+}
+
+// Return a multipart form
+func (req *Request) GetMultipartForm() (ServerMultipartForm, error) {
+       if form, err := req.In.Get(HTTP_MULTIPART_FORM); err != nil {
+               return nil, err
+       } else if values, found := form.(ServerMultipartForm); found {
+               return values, nil
+       }
+       return nil, FORM_NOT_FOUND
+}
+
+// Destroy the request
+func (req *Request) Destroy() {
+       req.In = nil
+       req.ContentType = ""
+       req.Format = ""
+       req.AcceptLanguages = nil
+       req.Method = ""
+       req.RemoteAddr = ""
+       req.Host = ""
+       req.Header.Destroy()
+       req.URL = nil
+       req.Form = nil
+       req.MultipartForm = nil
+}
+
+// Set the server response
+func (resp *Response) SetResponse(r ServerResponse) {
+       resp.Out.Server = r
+       if h, e := r.Get(HTTP_SERVER_HEADER); e == nil {
+               resp.Out.internalHeader.Server, _ = h.(ServerHeader)
+       }
+}
+
+// Destroy the output response
+func (o *OutResponse) Destroy() {
+       o.response = nil
+       o.internalHeader.Destroy()
+}
+
+// Destroy the RevelHeader
+func (h *RevelHeader) Destroy() {
+       h.Server = nil
+}
+
+// Destroy the Response
+func (resp *Response) Destroy() {
+       resp.Out.Destroy()
+       resp.Status = 0
+       resp.ContentType = ""
+       resp.writer = nil
+}
+
+// UserAgent returns the client's User-Agent header string.
+func (r *Request) UserAgent() string {
+       return r.Header.Get("User-Agent")
+}
+
+// Referer returns the client's Referer header string.
+func (req *Request) Referer() string {
+       return req.Header.Get("Referer")
+}
+
+// Return the httpheader for the key
+func (req *Request) GetHttpHeader(key string) string {
+       return req.Header.Get(key)
+}
+
+// Return the value from the server
+func (r *Request) GetValue(key int) (value interface{}) {
+       value, _ = r.In.Get(key)
+       return
+}
+
+// WriteHeader writes the header (for now, just the status code).
+// The status may be set directly by the application (c.Response.Status = 501).
+// If it isn't, then fall back to the provided status code.
+func (resp *Response) WriteHeader(defaultStatusCode int, defaultContentType string) {
+       if resp.ContentType == "" {
+               resp.ContentType = defaultContentType
+       }
+       resp.Out.internalHeader.Set("Content-Type", resp.ContentType)
+       if resp.Status == 0 {
+               resp.Status = defaultStatusCode
+       }
+       resp.SetStatus(resp.Status)
+}
+func (resp *Response) SetStatus(statusCode int) {
+       if resp.Out.internalHeader.Server != nil {
+               resp.Out.internalHeader.Server.SetStatus(statusCode)
+       } else {
+               resp.Out.Server.Set(ENGINE_RESPONSE_STATUS, statusCode)
+       }
+
+}
+
+// Return the writer
+func (resp *Response) GetWriter() (writer io.Writer) {
+       writer = resp.writer
+       if writer == nil {
+               if w, e := resp.Out.Server.Get(ENGINE_WRITER); e == nil {
+                       writer, resp.writer = w.(io.Writer), w.(io.Writer)
+               }
+       }
+
+       return
+}
+
+// Replace the writer
+func (resp *Response) SetWriter(writer io.Writer) bool {
+       resp.writer = writer
+       // Leave it up to the engine to flush and close the writer
+       return resp.Out.Server.Set(ENGINE_WRITER, writer)
+}
+
+// Passes full control to the response to the caller - terminates any initial writes
+func (resp *Response) GetStreamWriter() (writer StreamWriter) {
+       if w, e := resp.Out.Server.Get(HTTP_STREAM_WRITER); e == nil {
+               writer = w.(StreamWriter)
+       }
+       return
+}
+
+// Return the header
+func (o *OutResponse) Header() *RevelHeader {
+       return o.internalHeader
+}
+
+// Write the header out
+func (o *OutResponse) Write(data []byte) (int, error) {
+       return o.response.GetWriter().Write(data)
+}
+
+// Set a value in the header
+func (h *RevelHeader) Set(key, value string) {
+       if h.Server != nil {
+               h.Server.Set(key, value)
+       }
+}
+
+// Add a key to the header
+func (h *RevelHeader) Add(key, value string) {
+       if h.Server != nil {
+               h.Server.Add(key, value)
+       }
+}
+
+// Set a cookie in the header
+func (h *RevelHeader) SetCookie(cookie string) {
+       if h.Server != nil {
+               h.Server.SetCookie(cookie)
+       }
+}
+
+// Set the status for the header
+func (h *RevelHeader) SetStatus(status int) {
+       if h.Server != nil {
+               h.Server.SetStatus(status)
+       }
+}
+
+// Get a key from the header
+func (h *RevelHeader) Get(key string) (value string) {
+       values := h.GetAll(key)
+       if len(values) > 0 {
+               value = values[0]
+       }
+       return
+}
+
+// GetAll returns []string of items (the header split by a comma)
+func (h *RevelHeader) GetAll(key string) (values []string) {
+       if h.Server != nil {
+               values = h.Server.Get(key)
+       }
+       return
+}
+
+// ResolveContentType gets the content type.
+// e.g. From "multipart/form-data; boundary=--" to "multipart/form-data"
+// If none is specified, returns "text/html" by default.
+func ResolveContentType(req *Request) string {
+
+       contentType := req.Header.Get("Content-Type")
+       if contentType == "" {
+               return "text/html"
+       }
+       return strings.ToLower(strings.TrimSpace(strings.Split(contentType, ";")[0]))
+}
+
+// ResolveFormat maps the request's Accept MIME type declaration to
+// a Request.Format attribute, specifically "html", "xml", "json", or "txt",
+// returning a default of "html" when Accept header cannot be mapped to a
+// value above.
+func ResolveFormat(req *Request) string {
+       ext := strings.ToLower(filepath.Ext(req.GetPath()))
+       switch ext {
+       case ".html":
+               return "html"
+       case ".json":
+               return "json"
+       case ".xml":
+               return "xml"
+       case ".txt":
+               return "txt"
+       }
+
+       accept := req.GetHttpHeader("accept")
+
+       switch {
+       case accept == "",
+               strings.HasPrefix(accept, "*/*"), // */
+               strings.Contains(accept, "application/xhtml"),
+               strings.Contains(accept, "text/html"):
+               return "html"
+       case strings.Contains(accept, "application/json"),
+               strings.Contains(accept, "text/javascript"),
+               strings.Contains(accept, "application/javascript"):
+               return "json"
+       case strings.Contains(accept, "application/xml"),
+               strings.Contains(accept, "text/xml"):
+               return "xml"
+       case strings.Contains(accept, "text/plain"):
+               return "txt"
+       }
+
+       return "html"
+}
+
+// AcceptLanguage is a single language from the Accept-Language HTTP header.
+type AcceptLanguage struct {
+       Language string
+       Quality  float32
+}
+
+// AcceptLanguages is collection of sortable AcceptLanguage instances.
+type AcceptLanguages []AcceptLanguage
+
+func (al AcceptLanguages) Len() int           { return len(al) }
+func (al AcceptLanguages) Swap(i, j int)      { al[i], al[j] = al[j], al[i] }
+func (al AcceptLanguages) Less(i, j int) bool { return al[i].Quality > al[j].Quality }
+func (al AcceptLanguages) String() string {
+       output := bytes.NewBufferString("")
+       for i, language := range al {
+               if _, err := output.WriteString(fmt.Sprintf("%s (%1.1f)", language.Language, language.Quality)); err != nil {
+                       httpLog.Error("String: WriteString failed:", "error", err)
+               }
+               if i != len(al)-1 {
+                       if _, err := output.WriteString(", "); err != nil {
+                               httpLog.Error("String: WriteString failed:", "error", err)
+                       }
+               }
+       }
+       return output.String()
+}
+
+// ResolveAcceptLanguage returns a sorted list of Accept-Language
+// header values.
+//
+// The results are sorted using the quality defined in the header for each
+// language range with the most qualified language range as the first
+// element in the slice.
+//
+// See the HTTP header fields specification
+// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) for more details.
+func ResolveAcceptLanguage(req *Request) AcceptLanguages {
+       header := req.Header.Get("Accept-Language")
+       if header == "" {
+               return req.AcceptLanguages
+       }
+
+       acceptLanguageHeaderValues := strings.Split(header, ",")
+       acceptLanguages := make(AcceptLanguages, len(acceptLanguageHeaderValues))
+
+       for i, languageRange := range acceptLanguageHeaderValues {
+               if qualifiedRange := strings.Split(languageRange, ";q="); len(qualifiedRange) == 2 {
+                       quality, err := strconv.ParseFloat(qualifiedRange[1], 32)
+                       if err != nil {
+                               httpLog.Warn("Detected malformed Accept-Language header quality in  assuming quality is 1", "languageRange", languageRange)
+                               acceptLanguages[i] = AcceptLanguage{qualifiedRange[0], 1}
+                       } else {
+                               acceptLanguages[i] = AcceptLanguage{qualifiedRange[0], float32(quality)}
+                       }
+               } else {
+                       acceptLanguages[i] = AcceptLanguage{languageRange, 1}
+               }
+       }
+
+       sort.Sort(acceptLanguages)
+       return acceptLanguages
+}
diff --git a/src/foundation/api/revel/i18n.go b/src/foundation/api/revel/i18n.go
new file mode 100644 (file)
index 0000000..13fe810
--- /dev/null
@@ -0,0 +1,245 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "html/template"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       "github.com/revel/config"
+)
+
+const (
+       // CurrentLocaleViewArg the key for the current locale view arg value
+       CurrentLocaleViewArg = "currentLocale"
+
+       messageFilesDirectory  = "messages"
+       messageFilePattern     = `^\w+\.[a-zA-Z]{2}$`
+       defaultUnknownFormat   = "??? %s ???"
+       unknownFormatConfigKey = "i18n.unknown_format"
+       defaultLanguageOption  = "i18n.default_language"
+       localeCookieConfigKey  = "i18n.cookie"
+)
+
+var (
+       // All currently loaded message configs.
+       messages            map[string]*config.Config
+       localeParameterName string
+       i18nLog             = RevelLog.New("section", "i18n")
+)
+
+// MessageFunc allows you to override the translation interface.
+//
+// Set this to your own function that translates to the current locale.
+// This allows you to set up your own loading and logging of translated texts.
+//
+// See Message(...) in i18n.go for example of function.
+var MessageFunc = Message
+
+// MessageLanguages returns all currently loaded message languages.
+func MessageLanguages() []string {
+       languages := make([]string, len(messages))
+       i := 0
+       for language := range messages {
+               languages[i] = language
+               i++
+       }
+       return languages
+}
+
+// Message performs a message look-up for the given locale and message using the given arguments.
+//
+// When either an unknown locale or message is detected, a specially formatted string is returned.
+func Message(locale, message string, args ...interface{}) string {
+       language, region := parseLocale(locale)
+       unknownValueFormat := getUnknownValueFormat()
+
+       messageConfig, knownLanguage := messages[language]
+       if !knownLanguage {
+               i18nLog.Debugf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)
+
+               if defaultLanguage, found := Config.String(defaultLanguageOption); found {
+                       i18nLog.Debugf("Using default language '%s'", defaultLanguage)
+
+                       messageConfig, knownLanguage = messages[defaultLanguage]
+                       if !knownLanguage {
+                               i18nLog.Debugf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
+                               return fmt.Sprintf(unknownValueFormat, message)
+                       }
+               } else {
+                       i18nLog.Warnf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
+                       return fmt.Sprintf(unknownValueFormat, message)
+               }
+       }
+
+       // This works because unlike the goconfig documentation suggests it will actually
+       // try to resolve message in DEFAULT if it did not find it in the given section.
+       value, err := messageConfig.String(region, message)
+       if err != nil {
+               i18nLog.Warnf("Unknown message '%s' for locale '%s'", message, locale)
+               return fmt.Sprintf(unknownValueFormat, message)
+       }
+
+       if len(args) > 0 {
+               i18nLog.Debugf("Arguments detected, formatting '%s' with %v", value, args)
+               safeArgs := make([]interface{}, 0, len(args))
+               for _, arg := range args {
+                       switch a := arg.(type) {
+                       case template.HTML:
+                               safeArgs = append(safeArgs, a)
+                       case string:
+                               safeArgs = append(safeArgs, template.HTML(template.HTMLEscapeString(a)))
+                       default:
+                               safeArgs = append(safeArgs, a)
+                       }
+               }
+               value = fmt.Sprintf(value, safeArgs...)
+       }
+
+       return value
+}
+
+func parseLocale(locale string) (language, region string) {
+       if strings.Contains(locale, "-") {
+               languageAndRegion := strings.Split(locale, "-")
+               return languageAndRegion[0], languageAndRegion[1]
+       }
+
+       return locale, ""
+}
+
+// Retrieve message format or default format when i18n message is missing.
+func getUnknownValueFormat() string {
+       return Config.StringDefault(unknownFormatConfigKey, defaultUnknownFormat)
+}
+
+// Recursively read and cache all available messages from all message files on the given path.
+func loadMessages(path string) {
+       messages = make(map[string]*config.Config)
+
+       // Read in messages from the modules. Load the module messges first,
+       // so that it can be override in parent application
+       for _, module := range Modules {
+               i18nLog.Debug("Importing messages from module:", "importpath", module.ImportPath)
+               if err := Walk(filepath.Join(module.Path, messageFilesDirectory), loadMessageFile); err != nil &&
+                       !os.IsNotExist(err) {
+                       i18nLog.Error("Error reading messages files from module:", "error", err)
+               }
+       }
+
+       if err := Walk(path, loadMessageFile); err != nil && !os.IsNotExist(err) {
+               i18nLog.Error("Error reading messages files:", "error", err)
+       }
+}
+
+// Load a single message file
+func loadMessageFile(path string, info os.FileInfo, osError error) error {
+       if osError != nil {
+               return osError
+       }
+       if info.IsDir() {
+               return nil
+       }
+
+       if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
+               messageConfig, err := parseMessagesFile(path)
+               if err != nil {
+                       return err
+               }
+               locale := parseLocaleFromFileName(info.Name())
+
+               // If we have already parsed a message file for this locale, merge both
+               if _, exists := messages[locale]; exists {
+                       messages[locale].Merge(messageConfig)
+                       i18nLog.Debugf("Successfully merged messages for locale '%s'", locale)
+               } else {
+                       messages[locale] = messageConfig
+               }
+
+               i18nLog.Debug("Successfully loaded messages from file", "file", info.Name())
+       } else {
+               i18nLog.Warn("Ignoring file because it did not have a valid extension", "file", info.Name())
+       }
+
+       return nil
+}
+
+func parseMessagesFile(path string) (messageConfig *config.Config, err error) {
+       messageConfig, err = config.ReadDefault(path)
+       return
+}
+
+func parseLocaleFromFileName(file string) string {
+       extension := filepath.Ext(file)[1:]
+       return strings.ToLower(extension)
+}
+
+func init() {
+       OnAppStart(func() {
+               loadMessages(filepath.Join(BasePath, messageFilesDirectory))
+               localeParameterName = Config.StringDefault("i18n.locale.parameter", "")
+       }, 0)
+}
+
+func I18nFilter(c *Controller, fc []Filter) {
+       foundLocale := false
+       // Search for a parameter first
+       if localeParameterName != "" {
+               if locale, found := c.Params.Values[localeParameterName]; found && len(locale[0]) > 0 {
+                       setCurrentLocaleControllerArguments(c, locale[0])
+                       foundLocale = true
+                       i18nLog.Debug("Found locale parameter value: ", "locale", locale[0])
+               }
+       }
+       if !foundLocale {
+               if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
+                       i18nLog.Debug("Found locale cookie value: ", "cookie", cookieValue)
+                       setCurrentLocaleControllerArguments(c, cookieValue)
+               } else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
+                       i18nLog.Debug("Found Accept-Language header value: ", "header", headerValue)
+                       setCurrentLocaleControllerArguments(c, headerValue)
+               } else {
+                       i18nLog.Debug("Unable to find locale in cookie or header, using empty string")
+                       setCurrentLocaleControllerArguments(c, "")
+               }
+       }
+       fc[0](c, fc[1:])
+}
+
+// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
+func setCurrentLocaleControllerArguments(c *Controller, locale string) {
+       c.Request.Locale = locale
+       c.ViewArgs[CurrentLocaleViewArg] = locale
+}
+
+// Determine whether the given request has valid Accept-Language value.
+//
+// Assumes that the accept languages stored in the request are sorted according to quality, with top
+// quality first in the slice.
+func hasAcceptLanguageHeader(request *Request) (bool, string) {
+       if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
+               return true, request.AcceptLanguages[0].Language
+       }
+
+       return false, ""
+}
+
+// Determine whether the given request has a valid language cookie value.
+func hasLocaleCookie(request *Request) (bool, string) {
+       if request != nil {
+               name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG")
+               cookie, err := request.Cookie(name)
+               if err == nil {
+                       return true, cookie.GetValue()
+               }
+               i18nLog.Debug("Unable to read locale cookie ", "name", name, "error", err)
+       }
+
+       return false, ""
+}
diff --git a/src/foundation/api/revel/i18n_test.go b/src/foundation/api/revel/i18n_test.go
new file mode 100644 (file)
index 0000000..ce2f675
--- /dev/null
@@ -0,0 +1,273 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "html/template"
+       "net/http"
+       "testing"
+       "time"
+
+       "github.com/revel/config"
+       "github.com/revel/revel/logger"
+)
+
+const (
+       testDataPath   string = "testdata/i18n"
+       testConfigPath string = "testdata/i18n/config"
+       testConfigName string = "test_app.conf"
+)
+
+func TestI18nLoadMessages(t *testing.T) {
+       loadMessages(testDataPath)
+
+       // Assert that we have the expected number of languages
+       if len(MessageLanguages()) != 2 {
+               t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages()))
+       }
+}
+
+func TestI18nMessage(t *testing.T) {
+       loadMessages(testDataPath)
+       loadTestI18nConfig(t)
+
+       // Assert that we can get a message and we get the expected return value
+       if message := Message("nl", "greeting"); message != "Hallo" {
+               t.Errorf("Message 'greeting' for locale 'nl' (%s) does not have the expected value", message)
+       }
+       if message := Message("en", "greeting"); message != "Hello" {
+               t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
+       }
+       if message := Message("en", "folded"); message != "Greeting is 'Hello'" {
+               t.Error("Unexpected unfolded message: ", message)
+       }
+       if message := Message("en", "arguments.string", "Vincent Hanna"); message != "My name is Vincent Hanna" {
+               t.Errorf("Message 'arguments.string' for locale 'en' (%s) does not have the expected value", message)
+       }
+       if message := Message("en", "arguments.hex", 1234567, 1234567); message != "The number 1234567 in hexadecimal notation would be 12d687" {
+               t.Errorf("Message 'arguments.hex' for locale 'en' (%s) does not have the expected value", message)
+       }
+       if message := Message("en", "folded.arguments", 12345); message != "Rob is 12345 years old" {
+               t.Errorf("Message 'folded.arguments' for locale 'en' (%s) does not have the expected value", message)
+       }
+       if message := Message("en-AU", "greeting"); message != "G'day" {
+               t.Errorf("Message 'greeting' for locale 'en-AU' (%s) does not have the expected value", message)
+       }
+       if message := Message("en-AU", "only_exists_in_default"); message != "Default" {
+               t.Errorf("Message 'only_exists_in_default' for locale 'en-AU' (%s) does not have the expected value", message)
+       }
+
+       // Assert that message merging works
+       if message := Message("en", "greeting2"); message != "Yo!" {
+               t.Errorf("Message 'greeting2' for locale 'en' (%s) does not have the expected value", message)
+       }
+
+       // Assert that we get the expected return value for a locale that doesn't exist
+       if message := Message("unknown locale", "message"); message != "??? message ???" {
+               t.Error("Locale 'unknown locale' is not supposed to exist")
+       }
+       // Assert that we get the expected return value for a message that doesn't exist
+       if message := Message("nl", "unknown message"); message != "??? unknown message ???" {
+               t.Error("Message 'unknown message' is not supposed to exist")
+       }
+       // XSS
+       if message := Message("en", "arguments.string", "<img src=a onerror=alert(1) />"); message != "My name is &lt;img src=a onerror=alert(1) /&gt;" {
+               t.Error("XSS protection for messages is broken:", message)
+       }
+       // Avoid escaping HTML
+       if message := Message("en", "arguments.string", template.HTML("<img src=a onerror=alert(1) />")); message != "My name is <img src=a onerror=alert(1) />" {
+               t.Error("Passing safe HTML to message is broken:", message)
+       }
+}
+
+func TestI18nMessageWithDefaultLocale(t *testing.T) {
+       loadMessages(testDataPath)
+       loadTestI18nConfig(t)
+
+       if message := Message("doesn't exist", "greeting"); message != "Hello" {
+               t.Errorf("Expected message '%s' for unknown locale to be default '%s' but was '%s'", "greeting", "Hello", message)
+       }
+       if message := Message("doesn't exist", "unknown message"); message != "??? unknown message ???" {
+               t.Error("Message 'unknown message' is not supposed to exist in the default language")
+       }
+}
+
+func TestHasLocaleCookie(t *testing.T) {
+       loadTestI18nConfig(t)
+
+       if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en").Request); !found {
+               t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en", value)
+       }
+       if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US").Request); !found {
+               t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en-US", value)
+       }
+       if found, _ := hasLocaleCookie(buildRequestWithCookie("DOESNT_EXIST", "en-US").Request); found {
+               t.Errorf("Expected %s cookie to not exist, but apparently it does", "DOESNT_EXIST")
+       }
+}
+
+func TestHasLocaleCookieWithInvalidConfig(t *testing.T) {
+       loadTestI18nConfigWithoutLanguageCookieOption(t)
+       if found, _ := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US").Request); found {
+               t.Errorf("Expected %s cookie to not exist because the configured name is missing", "APP_LANG")
+       }
+       if found, _ := hasLocaleCookie(buildRequestWithCookie("REVEL_LANG", "en-US").Request); !found {
+               t.Errorf("Expected %s cookie to exist", "REVEL_LANG")
+       }
+}
+
+func TestHasAcceptLanguageHeader(t *testing.T) {
+       if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-US").Request); !found && value != "en-US" {
+               t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-US", value)
+       }
+       if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-GB", "en-US", "nl").Request); !found && value != "en-GB" {
+               t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-GB", value)
+       }
+}
+
+func TestBeforeRequest(t *testing.T) {
+       loadTestI18nConfig(t)
+
+       c := buildEmptyRequest()
+       if I18nFilter(c, NilChain); c.Request.Locale != "" {
+               t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "", c.Request.Locale)
+       }
+
+       c = buildRequestWithCookie("APP_LANG", "en-US")
+       if I18nFilter(c, NilChain); c.Request.Locale != "en-US" {
+               t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-US", c.Request.Locale)
+       }
+
+       c = buildRequestWithAcceptLanguages("en-GB", "en-US")
+       if I18nFilter(c, NilChain); c.Request.Locale != "en-GB" {
+               t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-GB", c.Request.Locale)
+       }
+}
+
+func TestI18nMessageUnknownValueFormat(t *testing.T) {
+       loadMessages(testDataPath)
+       loadTestI18nConfigWithUnknowFormatOption(t)
+
+       // Assert that we can get a message and we get the expected return value
+       if message := Message("en", "greeting"); message != "Hello" {
+               t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
+       }
+
+       // Assert that we get the expected return value with original format
+       if message := Message("unknown locale", "message"); message != "*** message ***" {
+               t.Error("Locale 'unknown locale' is not supposed to exist")
+       }
+       if message := Message("nl", "unknown message"); message != "*** unknown message ***" {
+               t.Error("Message 'unknown message' is not supposed to exist")
+       }
+}
+
+func BenchmarkI18nLoadMessages(b *testing.B) {
+       excludeFromTimer(b, func() {
+               RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error {
+                       return nil
+               }))
+       })
+
+       for i := 0; i < b.N; i++ {
+               loadMessages(testDataPath)
+       }
+}
+
+func BenchmarkI18nMessage(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               Message("nl", "greeting")
+       }
+}
+
+func BenchmarkI18nMessageWithArguments(b *testing.B) {
+       excludeFromTimer(b, func() {
+               RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error {
+                       return nil
+               }))
+       })
+
+
+       for i := 0; i < b.N; i++ {
+               Message("en", "arguments.string", "Vincent Hanna")
+       }
+}
+
+func BenchmarkI18nMessageWithFoldingAndArguments(b *testing.B) {
+       excludeFromTimer(b, func() {
+               RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error {
+                       return nil
+               }))
+       })
+
+
+       for i := 0; i < b.N; i++ {
+               Message("en", "folded.arguments", 12345)
+       }
+}
+
+// Exclude whatever operations the given function performs from the benchmark timer.
+func excludeFromTimer(b *testing.B, f func()) {
+       b.StopTimer()
+       f()
+       b.StartTimer()
+}
+
+func loadTestI18nConfig(t *testing.T) {
+       ConfPaths = append(ConfPaths, testConfigPath)
+       testConfig, err := config.LoadContext(testConfigName, ConfPaths)
+       if err != nil {
+               t.Fatalf("Unable to load test config '%s': %s", testConfigName, err.Error())
+       }
+       Config = testConfig
+       CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
+}
+
+func loadTestI18nConfigWithoutLanguageCookieOption(t *testing.T) {
+       loadTestI18nConfig(t)
+       Config.Raw().RemoveOption("DEFAULT", "i18n.cookie")
+}
+
+func loadTestI18nConfigWithUnknowFormatOption(t *testing.T) {
+       loadTestI18nConfig(t)
+       Config.Raw().AddOption("DEFAULT", "i18n.unknown_format", "*** %s ***")
+}
+
+func buildRequestWithCookie(name, value string) *Controller {
+       httpRequest, _ := http.NewRequest("GET", "/", nil)
+       controller := NewTestController(nil, httpRequest)
+
+       httpRequest.AddCookie(&http.Cookie{
+               Name:       name,
+               Value:      value,
+               Path:       "",
+               Domain:     "",
+               Expires:    time.Now(),
+               RawExpires: "",
+               MaxAge:     0,
+               Secure:     false,
+               HttpOnly:   false,
+               Raw:        "",
+               Unparsed:   nil,
+       })
+       return controller
+}
+
+func buildRequestWithAcceptLanguages(acceptLanguages ...string) *Controller {
+       httpRequest, _ := http.NewRequest("GET", "/", nil)
+       controller := NewTestController(nil, httpRequest)
+
+       request := controller.Request
+       for _, acceptLanguage := range acceptLanguages {
+               request.AcceptLanguages = append(request.AcceptLanguages, AcceptLanguage{acceptLanguage, 1})
+       }
+       return controller
+}
+
+func buildEmptyRequest() *Controller {
+       httpRequest, _ := http.NewRequest("GET", "/", nil)
+       controller := NewTestController(nil, httpRequest)
+       return controller
+}
diff --git a/src/foundation/api/revel/intercept.go b/src/foundation/api/revel/intercept.go
new file mode 100644 (file)
index 0000000..2ee8677
--- /dev/null
@@ -0,0 +1,249 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "log"
+       "reflect"
+       "sort"
+)
+
+// An InterceptorFunc is functionality invoked by the framework BEFORE or AFTER
+// an action.
+//
+// An interceptor may optionally return a Result (instead of nil).  Depending on
+// when the interceptor was invoked, the response is different:
+// 1. BEFORE:  No further interceptors are invoked, and neither is the action.
+// 2. AFTER: Further interceptors are still run.
+// In all cases, any returned Result will take the place of any existing Result.
+//
+// In the BEFORE case, that returned Result is guaranteed to be final, while
+// in the AFTER case it is possible that a further interceptor could emit its
+// own Result.
+//
+// Interceptors are called in the order that they are added.
+//
+// ***
+//
+// Two types of interceptors are provided: Funcs and Methods
+//
+// Func Interceptors may apply to any / all Controllers.
+//
+//   func example(*revel.Controller) revel.Result
+//
+// Method Interceptors are provided so that properties can be set on application
+// controllers.
+//
+//   func (c AppController) example() revel.Result
+//   func (c *AppController) example() revel.Result
+//
+type InterceptorFunc func(*Controller) Result
+type InterceptorMethod interface{}
+type When int
+
+const (
+       BEFORE When = iota
+       AFTER
+       PANIC
+       FINALLY
+)
+
+type InterceptTarget int
+
+const (
+       AllControllers InterceptTarget = iota
+)
+
+type Interception struct {
+       When When
+
+       function InterceptorFunc
+       method   InterceptorMethod
+
+       callable     reflect.Value
+       target       reflect.Type
+       interceptAll bool
+}
+
+// Invoke performs the given interception.
+// val is a pointer to the App Controller.
+func (i Interception) Invoke(val reflect.Value, target *reflect.Value) reflect.Value {
+       var arg reflect.Value
+       if i.function == nil {
+               // If it's an InterceptorMethod, then we have to pass in the target type.
+               arg = *target
+       } else {
+               // If it's an InterceptorFunc, then the type must be *Controller.
+               // We can find that by following the embedded types up the chain.
+               for val.Type() != controllerPtrType {
+                       if val.Kind() == reflect.Ptr {
+                               val = val.Elem()
+                       }
+                       val = val.Field(0)
+               }
+               arg = val
+       }
+
+       vals := i.callable.Call([]reflect.Value{arg})
+       return vals[0]
+}
+
+func InterceptorFilter(c *Controller, fc []Filter) {
+       defer invokeInterceptors(FINALLY, c)
+       defer func() {
+               if err := recover(); err != nil {
+                       invokeInterceptors(PANIC, c)
+                       panic(err)
+               }
+       }()
+
+       // Invoke the BEFORE interceptors and return early, if we get a result.
+       invokeInterceptors(BEFORE, c)
+       if c.Result != nil {
+               return
+       }
+
+       fc[0](c, fc[1:])
+       invokeInterceptors(AFTER, c)
+}
+
+func invokeInterceptors(when When, c *Controller) {
+       var (
+               app    = reflect.ValueOf(c.AppController)
+               result Result
+       )
+
+       for _, intc := range getInterceptors(when, app) {
+               resultValue := intc.Interceptor.Invoke(app, &intc.Target)
+               if !resultValue.IsNil() {
+                       result = resultValue.Interface().(Result)
+               }
+               if when == BEFORE && result != nil {
+                       c.Result = result
+                       return
+               }
+       }
+       if result != nil {
+               c.Result = result
+       }
+}
+
+var interceptors []*Interception
+
+// InterceptFunc installs a general interceptor.
+// This can be applied to any Controller.
+// It must have the signature of:
+//   func example(c *revel.Controller) revel.Result
+func InterceptFunc(intc InterceptorFunc, when When, target interface{}) {
+       interceptors = append(interceptors, &Interception{
+               When:         when,
+               function:     intc,
+               callable:     reflect.ValueOf(intc),
+               target:       reflect.TypeOf(target),
+               interceptAll: target == AllControllers,
+       })
+}
+
+// InterceptMethod installs an interceptor method that applies to its own Controller.
+//   func (c AppController) example() revel.Result
+//   func (c *AppController) example() revel.Result
+func InterceptMethod(intc InterceptorMethod, when When) {
+       methodType := reflect.TypeOf(intc)
+       if methodType.Kind() != reflect.Func || methodType.NumOut() != 1 || methodType.NumIn() != 1 {
+               log.Fatalln("Interceptor method should have signature like",
+                       "'func (c *AppController) example() revel.Result' but was", methodType)
+       }
+
+       interceptors = append(interceptors, &Interception{
+               When:     when,
+               method:   intc,
+               callable: reflect.ValueOf(intc),
+               target:   methodType.In(0),
+       })
+}
+
+// This item is used to provide a sortable set to be returned to the caller. This ensures calls order is maintained
+//
+type interceptorItem struct {
+       Interceptor *Interception
+       Target      reflect.Value
+       Level       int
+}
+type interceptorItemList []*interceptorItem
+
+func (a interceptorItemList) Len() int           { return len(a) }
+func (a interceptorItemList) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a interceptorItemList) Less(i, j int) bool { return a[i].Level < a[j].Level }
+
+type reverseInterceptorItemList []*interceptorItem
+
+func (a reverseInterceptorItemList) Len() int           { return len(a) }
+func (a reverseInterceptorItemList) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a reverseInterceptorItemList) Less(i, j int) bool { return a[i].Level > a[j].Level }
+func getInterceptors(when When, val reflect.Value) interceptorItemList {
+       result := interceptorItemList{}
+       for _, intc := range interceptors {
+               if intc.When != when {
+                       continue
+               }
+
+               level, target := findTarget(val, intc.target)
+               if intc.interceptAll || target.IsValid() {
+                       result = append(result, &interceptorItem{intc, target, level})
+               }
+       }
+
+       // Before is deepest to highest
+       if when == BEFORE {
+               sort.Sort(result)
+       } else {
+               // Everything else is highest to deepest
+               sort.Sort(reverseInterceptorItemList(result))
+       }
+       return result
+}
+
+// Find the value of the target, starting from val and including embedded types.
+// Also, convert between any difference in indirection.
+// If the target couldn't be found, the returned Value will have IsValid() == false
+func findTarget(val reflect.Value, target reflect.Type) (int, reflect.Value) {
+       // Look through the embedded types (until we reach the *revel.Controller at the top).
+       valueQueue := []reflect.Value{val}
+       level := 0
+       for len(valueQueue) > 0 {
+               val, valueQueue = valueQueue[0], valueQueue[1:]
+
+               // Check if val is of a similar type to the target type.
+               if val.Type() == target {
+                       return level, val
+               }
+               if val.Kind() == reflect.Ptr && val.Elem().Type() == target {
+                       return level, val.Elem()
+               }
+               if target.Kind() == reflect.Ptr && target.Elem() == val.Type() {
+                       return level, val.Addr()
+               }
+
+               // If we reached the *revel.Controller and still didn't find what we were
+               // looking for, give up.
+               if val.Type() == controllerPtrType {
+                       continue
+               }
+
+               // Else, add each anonymous field to the queue.
+               if val.Kind() == reflect.Ptr {
+                       val = val.Elem()
+               }
+
+               for i := 0; i < val.NumField(); i++ {
+                       if val.Type().Field(i).Anonymous {
+                               valueQueue = append(valueQueue, val.Field(i))
+                       }
+               }
+               level--
+       }
+
+       return level, reflect.Value{}
+}
diff --git a/src/foundation/api/revel/intercept_test.go b/src/foundation/api/revel/intercept_test.go
new file mode 100644 (file)
index 0000000..ee2138d
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "reflect"
+       "testing"
+)
+
+var funcP = func(c *Controller) Result { return nil }
+var funcP2 = func(c *Controller) Result { return nil }
+
+type InterceptController struct{ *Controller }
+type InterceptControllerN struct{ InterceptController }
+type InterceptControllerP struct{ *InterceptController }
+type InterceptControllerNP struct {
+       *Controller
+       InterceptControllerN
+       InterceptControllerP
+}
+
+func (c InterceptController) methN() Result  { return nil }
+func (c *InterceptController) methP() Result { return nil }
+
+func (c InterceptControllerN) methNN() Result  { return nil }
+func (c *InterceptControllerN) methNP() Result { return nil }
+func (c InterceptControllerP) methPN() Result  { return nil }
+func (c *InterceptControllerP) methPP() Result { return nil }
+
+// Methods accessible from InterceptControllerN
+var MethodN = []interface{}{
+       InterceptController.methN,
+       (*InterceptController).methP,
+       InterceptControllerN.methNN,
+       (*InterceptControllerN).methNP,
+}
+
+// Methods accessible from InterceptControllerP
+var MethodP = []interface{}{
+       InterceptController.methN,
+       (*InterceptController).methP,
+       InterceptControllerP.methPN,
+       (*InterceptControllerP).methPP,
+}
+
+// This checks that all the various kinds of interceptor functions/methods are
+// properly invoked.
+func TestInvokeArgType(t *testing.T) {
+       n := InterceptControllerN{InterceptController{&Controller{}}}
+       p := InterceptControllerP{&InterceptController{&Controller{}}}
+       np := InterceptControllerNP{&Controller{}, n, p}
+       testInterceptorController(t, reflect.ValueOf(&n), MethodN)
+       testInterceptorController(t, reflect.ValueOf(&p), MethodP)
+       testInterceptorController(t, reflect.ValueOf(&np), MethodN)
+       testInterceptorController(t, reflect.ValueOf(&np), MethodP)
+}
+
+func testInterceptorController(t *testing.T, appControllerPtr reflect.Value, methods []interface{}) {
+       interceptors = []*Interception{}
+       InterceptFunc(funcP, BEFORE, appControllerPtr.Elem().Interface())
+       InterceptFunc(funcP2, BEFORE, AllControllers)
+       for _, m := range methods {
+               InterceptMethod(m, BEFORE)
+       }
+       ints := getInterceptors(BEFORE, appControllerPtr)
+
+       if len(ints) != 6 {
+               t.Fatalf("N: Expected 6 interceptors, got %d.", len(ints))
+       }
+
+       testInterception(t, ints[0], reflect.ValueOf(&Controller{}))
+       testInterception(t, ints[1], reflect.ValueOf(&Controller{}))
+       for i := range methods {
+               testInterception(t, ints[i+2], appControllerPtr)
+       }
+}
+
+func testInterception(t *testing.T, intc *interceptorItem, arg reflect.Value) {
+       val := intc.Interceptor.Invoke(arg, &intc.Target)
+       if !val.IsNil() {
+               t.Errorf("Failed (%v): Expected nil got %v", intc, val)
+       }
+}
diff --git a/src/foundation/api/revel/invoker.go b/src/foundation/api/revel/invoker.go
new file mode 100644 (file)
index 0000000..24d40fa
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "io"
+       "reflect"
+)
+
+var (
+       controllerPtrType = reflect.TypeOf(&Controller{})
+       websocketType     = reflect.TypeOf((*ServerWebSocket)(nil)).Elem()
+)
+
+func ActionInvoker(c *Controller, _ []Filter) {
+       // Instantiate the method.
+       methodValue := reflect.ValueOf(c.AppController).MethodByName(c.MethodType.Name)
+
+       // Collect the values for the method's arguments.
+       var methodArgs []reflect.Value
+       for _, arg := range c.MethodType.Args {
+               // If they accept a websocket connection, treat that arg specially.
+               var boundArg reflect.Value
+               if arg.Type.Implements(websocketType) {
+                       boundArg = reflect.ValueOf(c.Request.WebSocket)
+               } else {
+                       boundArg = Bind(c.Params, arg.Name, arg.Type)
+                       // #756 - If the argument is a closer, defer a Close call,
+                       // so we don't risk on leaks.
+                       if closer, ok := boundArg.Interface().(io.Closer); ok {
+                               defer func() {
+                                       _ = closer.Close()
+                               }()
+                       }
+               }
+               methodArgs = append(methodArgs, boundArg)
+       }
+
+       var resultValue reflect.Value
+       if methodValue.Type().IsVariadic() {
+               resultValue = methodValue.CallSlice(methodArgs)[0]
+       } else {
+               resultValue = methodValue.Call(methodArgs)[0]
+       }
+       if resultValue.Kind() == reflect.Interface && !resultValue.IsNil() {
+               c.Result = resultValue.Interface().(Result)
+       }
+}
diff --git a/src/foundation/api/revel/invoker_test.go b/src/foundation/api/revel/invoker_test.go
new file mode 100644 (file)
index 0000000..dff94cc
--- /dev/null
@@ -0,0 +1,134 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "net/url"
+       "reflect"
+       "testing"
+)
+
+// These tests verify that Controllers are initialized properly, given the range
+// of embedding possibilities..
+
+type P struct{ *Controller }
+
+type PN struct{ P }
+
+type PNN struct{ PN }
+
+// Embedded via two paths
+type P2 struct{ *Controller }
+type PP2 struct {
+       *Controller // Need to embed this explicitly to avoid duplicate selector.
+       P
+       P2
+       PNN
+}
+
+func TestFindControllers(t *testing.T) {
+       controllers = make(map[string]*ControllerType)
+       RegisterController((*P)(nil), nil)
+       RegisterController((*PN)(nil), nil)
+       RegisterController((*PNN)(nil), nil)
+       RegisterController((*PP2)(nil), nil)
+
+       // Test construction of indexes to each *Controller
+       checkSearchResults(t, P{}, [][]int{{0}})
+       checkSearchResults(t, PN{}, [][]int{{0, 0}})
+       checkSearchResults(t, PNN{}, [][]int{{0, 0, 0}})
+       checkSearchResults(t, PP2{}, [][]int{{0}, {1, 0}, {2, 0}, {3, 0, 0, 0}})
+}
+
+func checkSearchResults(t *testing.T, obj interface{}, expected [][]int) {
+       actual := findControllers(reflect.TypeOf(obj))
+       if !reflect.DeepEqual(expected, actual) {
+               t.Errorf("Indexes do not match.  expected %v actual %v", expected, actual)
+       }
+}
+
+func TestSetAction(t *testing.T) {
+       controllers = make(map[string]*ControllerType)
+       RegisterController((*P)(nil), []*MethodType{{Name: "Method"}})
+       RegisterController((*PNN)(nil), []*MethodType{{Name: "Method"}})
+       RegisterController((*PP2)(nil), []*MethodType{{Name: "Method"}})
+
+       // Test that all *revel.Controllers are initialized.
+       c := &Controller{Name: "Test"}
+       if err := c.SetAction("P", "Method"); err != nil {
+               t.Error(err)
+       } else if c.AppController.(*P).Controller != c {
+               t.Errorf("P not initialized")
+       }
+
+       if err := c.SetAction("PNN", "Method"); err != nil {
+               t.Error(err)
+       } else if c.AppController.(*PNN).Controller != c {
+               t.Errorf("PNN not initialized")
+       }
+
+       // PP2 has 4 different slots for *Controller.
+       if err := c.SetAction("PP2", "Method"); err != nil {
+               t.Error(err)
+       } else if pp2 := c.AppController.(*PP2); pp2.Controller != c ||
+               pp2.P.Controller != c ||
+               pp2.P2.Controller != c ||
+               pp2.PNN.Controller != c {
+               t.Errorf("PP2 not initialized")
+       }
+}
+
+func BenchmarkSetAction(b *testing.B) {
+       type Mixin1 struct {
+               *Controller
+               x, y int
+               foo  string
+       }
+       type Mixin2 struct {
+               *Controller
+               a, b float64
+               bar  string
+       }
+
+       type Benchmark struct {
+               *Controller
+               Mixin1
+               Mixin2
+               user interface{}
+               guy  string
+       }
+
+       RegisterController((*Mixin1)(nil), []*MethodType{{Name: "Method"}})
+       RegisterController((*Mixin2)(nil), []*MethodType{{Name: "Method"}})
+       RegisterController((*Benchmark)(nil), []*MethodType{{Name: "Method"}})
+       c := Controller{
+               ViewArgs: make(map[string]interface{}),
+       }
+
+       for i := 0; i < b.N; i++ {
+               if err := c.SetAction("Benchmark", "Method"); err != nil {
+                       b.Errorf("Failed to set action: %s", err)
+                       return
+               }
+       }
+}
+
+func BenchmarkInvoker(b *testing.B) {
+       startFakeBookingApp()
+       c := NewTestController(nil, showRequest)
+       c.ViewArgs = make(map[string]interface{})
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               b.Errorf("Failed to set action: %s", err)
+               return
+       }
+
+       c.Params = &Params{Values: make(url.Values)}
+       c.Params.Set("id", "3")
+
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               ActionInvoker(c, nil)
+       }
+}
diff --git a/src/foundation/api/revel/libs.go b/src/foundation/api/revel/libs.go
new file mode 100644 (file)
index 0000000..fb17178
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "crypto/hmac"
+       "crypto/sha1"
+       "encoding/hex"
+       "io"
+       "reflect"
+       "strings"
+)
+
+// Sign a given string with the app-configured secret key.
+// If no secret key is set, returns the empty string.
+// Return the signature in base64 (URLEncoding).
+func Sign(message string) string {
+       if len(secretKey) == 0 {
+               return ""
+       }
+       mac := hmac.New(sha1.New, secretKey)
+       if _, err := io.WriteString(mac, message); err != nil {
+               utilLog.Error("WriteString failed", "error", err)
+               return ""
+       }
+       return hex.EncodeToString(mac.Sum(nil))
+}
+
+// Verify returns true if the given signature is correct for the given message.
+// e.g. it matches what we generate with Sign()
+func Verify(message, sig string) bool {
+       return hmac.Equal([]byte(sig), []byte(Sign(message)))
+}
+
+// ToBool method converts/assert value into true or false. Default is true.
+// When converting to boolean, the following values are considered FALSE:
+// - The integer value is 0 (zero)
+// - The float value 0.0 (zero)
+// - The complex value 0.0 (zero)
+// - For string value, please refer `revel.Atob` method
+// - An array, map, slice with zero elements
+// - Boolean value returned as-is
+// - "nil" value
+func ToBool(val interface{}) bool {
+       if val == nil {
+               return false
+       }
+
+       v := reflect.ValueOf(val)
+       switch v.Kind() {
+       case reflect.Bool:
+               return v.Bool()
+       case reflect.String:
+               return Atob(v.String())
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return v.Int() != 0
+       case reflect.Float32, reflect.Float64:
+               return v.Float() != 0.0
+       case reflect.Complex64, reflect.Complex128:
+               return v.Complex() != 0.0
+       case reflect.Array, reflect.Map, reflect.Slice:
+               return v.Len() != 0
+       }
+
+       // Return true by default
+       return true
+}
+
+// Atob converts string into boolean. It is in-case sensitive
+// When converting to boolean, the following values are considered FALSE:
+// - The "" (empty) string
+// - The "false" string
+// - The "f" string
+// - The "off" string,
+// - The string "0" & "0.0"
+func Atob(v string) bool {
+       switch strings.TrimSpace(strings.ToLower(v)) {
+       case "", "false", "off", "f", "0", "0.0":
+               return false
+       }
+
+       // Return true by default
+       return true
+}
diff --git a/src/foundation/api/revel/libs_test.go b/src/foundation/api/revel/libs_test.go
new file mode 100644 (file)
index 0000000..d8f8355
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import "testing"
+
+func TestToBooleanForFalse(t *testing.T) {
+       if ToBool(nil) ||
+               ToBool([]string{}) ||
+               ToBool(map[string]string{}) ||
+               ToBool(0) ||
+               ToBool(0.0) ||
+               ToBool("") ||
+               ToBool("false") ||
+               ToBool("0") ||
+               ToBool("0.0") ||
+               ToBool("off") ||
+               ToBool("f") {
+               t.Error("Expected 'false' got 'true'")
+       }
+}
+
+func TestToBooleanForTrue(t *testing.T) {
+       if !ToBool([]string{"true"}) ||
+               !ToBool(map[string]string{"true": "value"}) ||
+               !ToBool(1) ||
+               !ToBool(0.1) ||
+               !ToBool("not empty") ||
+               !ToBool("true") ||
+               !ToBool("1") ||
+               !ToBool("1.0") ||
+               !ToBool("on") ||
+               !ToBool("t") {
+               t.Error("Expected 'true' got 'false'")
+       }
+}
diff --git a/src/foundation/api/revel/logger.go b/src/foundation/api/revel/logger.go
new file mode 100644 (file)
index 0000000..7e928f2
--- /dev/null
@@ -0,0 +1,65 @@
+package revel
+
+import (
+       "github.com/revel/revel/logger"
+)
+
+//Logger
+var (
+       // The root log is what all other logs are branched from, meaning if you set the handler for the root
+       // it will adjust all children
+       RootLog = logger.New()
+       // This logger is the application logger, use this for your application log messages - ie jobs and startup,
+       // Use Controller.Log for Controller logging
+       // The requests are logged to this logger with the context of `section:requestlog`
+       AppLog = RootLog.New("module", "app")
+       // This is the logger revel writes to, added log messages will have a context of module:revel in them
+       // It is based off of `RootLog`
+       RevelLog = RootLog.New("module", "revel")
+
+       // This is the handler for the AppLog, it is stored so that if the AppLog is changed it can be assigned to the
+       // new AppLog
+       appLogHandler *logger.CompositeMultiHandler
+
+       // This oldLog is the revel logger, historical for revel, The application should use the AppLog or the Controller.oldLog
+       // DEPRECATED
+       oldLog = AppLog.New("section", "deprecated")
+       // System logger
+       SysLog = AppLog.New("section", "system")
+)
+
+// Initialize the loggers first
+func init() {
+
+       //RootLog.SetHandler(
+       //      logger.LevelHandler(logger.LogLevel(log15.LvlDebug),
+       //              logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))))
+       initLoggers()
+       OnAppStart(initLoggers, -5)
+
+}
+func initLoggers() {
+       appHandle := logger.InitializeFromConfig(BasePath, Config)
+
+       // Set all the log handlers
+       setAppLog(AppLog, appHandle)
+}
+
+// Set the application log and handler, if handler is nil it will
+// use the same handler used to configure the application log before
+func setAppLog(appLog logger.MultiLogger, appHandler *logger.CompositeMultiHandler) {
+       if appLog != nil {
+               AppLog = appLog
+       }
+       if appHandler != nil {
+               appLogHandler = appHandler
+               // Set the app log and the handler for all forked loggers
+               RootLog.SetHandler(appLogHandler)
+
+               // Set the system log handler - this sets golang writer stream to the
+               // sysLog router
+               logger.SetDefaultLog(SysLog)
+               SysLog.SetStackDepth(5)
+               SysLog.SetHandler(appLogHandler)
+       }
+}
diff --git a/src/foundation/api/revel/logger/composite_multihandler.go b/src/foundation/api/revel/logger/composite_multihandler.go
new file mode 100644 (file)
index 0000000..fb15e8a
--- /dev/null
@@ -0,0 +1,174 @@
+package logger
+
+import (
+       "github.com/mattn/go-colorable"
+       "gopkg.in/natefinch/lumberjack.v2"
+       "io"
+       "os"
+)
+
+type CompositeMultiHandler struct {
+       DebugHandler    LogHandler
+       InfoHandler     LogHandler
+       WarnHandler     LogHandler
+       ErrorHandler    LogHandler
+       CriticalHandler LogHandler
+}
+
+func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
+       cw := &CompositeMultiHandler{}
+       return cw, cw
+}
+func (h *CompositeMultiHandler) Log(r *Record) (err error) {
+
+       var handler LogHandler
+
+       switch r.Level {
+       case LvlInfo:
+               handler = h.InfoHandler
+       case LvlDebug:
+               handler = h.DebugHandler
+       case LvlWarn:
+               handler = h.WarnHandler
+       case LvlError:
+               handler = h.ErrorHandler
+       case LvlCrit:
+               handler = h.CriticalHandler
+       }
+
+       // Embed the caller function in the context
+       if handler != nil {
+               handler.Log(r)
+       }
+       return
+}
+
+func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
+       if handler == nil {
+               // Ignore empty handler
+               return
+       }
+       source := &h.DebugHandler
+       switch level {
+       case LvlDebug:
+               source = &h.DebugHandler
+       case LvlInfo:
+               source = &h.InfoHandler
+       case LvlWarn:
+               source = &h.WarnHandler
+       case LvlError:
+               source = &h.ErrorHandler
+       case LvlCrit:
+               source = &h.CriticalHandler
+       }
+
+       if !replace && *source != nil {
+               // If we are not replacing the source make sure that the level handler is applied first
+               if _, isLevel := (*source).(*LevelFilterHandler); !isLevel {
+                       *source = LevelHandler(level, *source)
+               }
+               // If this already was a list add a new logger to it
+               if ll, found := (*source).(*ListLogHandler); found {
+                       ll.Add(handler)
+               } else {
+                       *source = NewListLogHandler(*source, handler)
+               }
+       } else {
+               *source = handler
+       }
+}
+
+// For the multi handler set the handler, using the LogOptions defined
+func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
+       if len(options.Levels) == 0 {
+               options.Levels = LvlAllList
+       }
+
+       // Set all levels
+       for _, lvl := range options.Levels {
+               h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
+       }
+
+}
+func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
+       handler := CallerFileHandler(StreamHandler(writer, JsonFormatEx(
+               options.GetBoolDefault("pretty", false),
+               options.GetBoolDefault("lineSeparated", true),
+       )))
+       if options.HandlerWrap != nil {
+               handler = options.HandlerWrap.SetChild(handler)
+       }
+       h.SetHandlers(handler, options)
+}
+
+// Use built in rolling function
+func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
+       writer := &lumberjack.Logger{
+               Filename:   filePath,
+               MaxSize:    options.GetIntDefault("maxSizeMB", 1024), // megabytes
+               MaxAge:     options.GetIntDefault("maxAgeDays", 7),   //days
+               MaxBackups: options.GetIntDefault("maxBackups", 7),
+               Compress:   options.GetBoolDefault("compress", true),
+       }
+       h.SetJson(writer, options)
+}
+
+func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
+       streamHandler := StreamHandler(
+               writer,
+               TerminalFormatHandler(
+                       options.GetBoolDefault("noColor", false),
+                       options.GetBoolDefault("smallDate", true)))
+
+       if os.Stdout == writer {
+               streamHandler = StreamHandler(
+                       colorable.NewColorableStdout(),
+                       TerminalFormatHandler(
+                               options.GetBoolDefault("noColor", false),
+                               options.GetBoolDefault("smallDate", true)))
+       } else if os.Stderr == writer {
+               streamHandler = StreamHandler(
+                       colorable.NewColorableStderr(),
+                       TerminalFormatHandler(
+                               options.GetBoolDefault("noColor", false),
+                               options.GetBoolDefault("smallDate", true)))
+       }
+       handler := CallerFileHandler(streamHandler)
+
+       if options.HandlerWrap != nil {
+               handler = options.HandlerWrap.SetChild(handler)
+       }
+       h.SetHandlers(handler, options)
+}
+
+// Use built in rolling function
+func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
+       writer := &lumberjack.Logger{
+               Filename:   filePath,
+               MaxSize:    options.GetIntDefault("maxSizeMB", 1024), // megabytes
+               MaxAge:     options.GetIntDefault("maxAgeDays", 7),   //days
+               MaxBackups: options.GetIntDefault("maxBackups", 7),
+               Compress:   options.GetBoolDefault("compress", true),
+       }
+       h.SetTerminal(writer, options)
+}
+
+func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
+       if len(levels) == 0 {
+               levels = LvlAllList
+       }
+       for _, level := range levels {
+               switch level {
+               case LvlDebug:
+                       h.DebugHandler = nil
+               case LvlInfo:
+                       h.InfoHandler = nil
+               case LvlWarn:
+                       h.WarnHandler = nil
+               case LvlError:
+                       h.ErrorHandler = nil
+               case LvlCrit:
+                       h.CriticalHandler = nil
+               }
+       }
+}
diff --git a/src/foundation/api/revel/logger/doc.go b/src/foundation/api/revel/logger/doc.go
new file mode 100644 (file)
index 0000000..0f6156a
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+  Package logger contains filters and handles for the logging utilities in Revel.
+  These facilities all currently use the logging library called log15 at
+  https://github.com/inconshreveable/log15
+
+       Defining handlers happens as follows
+       1) ALL handlers (log.all.output) replace any existing handlers
+       2) Output handlers (log.error.output) replace any existing handlers
+       3) Filter handlers (log.xxx.filter, log.xxx.nfilter) append to existing handlers,
+          note log.all.filter is treated as a filter handler, so it will NOT replace existing ones
+
+
+
+*/
+package logger
diff --git a/src/foundation/api/revel/logger/handlers.go b/src/foundation/api/revel/logger/handlers.go
new file mode 100644 (file)
index 0000000..dee850e
--- /dev/null
@@ -0,0 +1,210 @@
+package logger
+
+import (
+       "fmt"
+       "io"
+)
+
+type LevelFilterHandler struct {
+       Level LogLevel
+       h     LogHandler
+}
+
+// Filters out records which do not match the level
+// Uses the `log15.FilterHandler` to perform this task
+func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
+       return &LevelFilterHandler{lvl, h}
+}
+
+// The implementation of the Log
+func (h LevelFilterHandler) Log(r *Record) error {
+       if r.Level == h.Level {
+               return h.h.Log(r)
+       }
+       return nil
+}
+
+// Filters out records which do not match the level
+// Uses the `log15.FilterHandler` to perform this task
+func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
+       return FilterHandler(func(r *Record) (pass bool) {
+               return r.Level <= lvl
+       }, h)
+}
+
+// Filters out records which match the level
+// Uses the `log15.FilterHandler` to perform this task
+func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
+       return FilterHandler(func(r *Record) (pass bool) {
+               return r.Level != lvl
+       }, h)
+}
+
+func CallerFileHandler(h LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               r.Context.Add("caller", fmt.Sprint(r.Call))
+               return h.Log(r)
+       })
+}
+
+// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
+// Uses the `log15.CallerFuncHandler` to perform this task
+func CallerFuncHandler(h LogHandler) LogHandler {
+       return CallerFuncHandler(h)
+}
+
+// Filters out records which match the key value pair
+// Uses the `log15.MatchFilterHandler` to perform this task
+func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
+       return MatchFilterHandler(key, value, h)
+}
+
+// MatchFilterHandler returns a Handler that only writes records
+// to the wrapped Handler if the given key in the logged
+// context matches the value. For example, to only log records
+// from your ui package:
+//
+//    log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
+//
+func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler {
+       return FilterHandler(func(r *Record) (pass bool) {
+               return r.Context[key] == value
+       }, h)
+}
+
+// If match then A handler is called otherwise B handler is called
+func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               if r.Context[key] == value {
+                       return a.Log(r)
+               } else if b != nil {
+                       return b.Log(r)
+               }
+
+               return nil
+       })
+}
+
+// The nil handler is used if logging for a specific request needs to be turned off
+func NilHandler() LogHandler {
+       return FuncHandler(func(r *Record) error {
+               return nil
+       })
+}
+
+// Match all values in map to log
+func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
+       return matchMapHandler(matchMap, false, a)
+}
+
+// Match !(Match all values in map to log) The inverse of MatchMapHandler
+func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
+       return matchMapHandler(matchMap, true, a)
+}
+
+// Rather then chaining multiple filter handlers, process all here
+func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               matchCount := 0
+               for k, v := range matchMap {
+                       value, found := r.Context[k]
+                       if !found {
+                               return nil
+                       }
+                       // Test for two failure cases
+                       if value == v && inverse || value != v && !inverse {
+                               return nil
+                       } else {
+                               matchCount++
+                       }
+               }
+               if matchCount != len(matchMap) {
+                       return nil
+               }
+               return a.Log(r)
+       })
+}
+
+// Filters out records which do not match the key value pair
+// Uses the `log15.FilterHandler` to perform this task
+func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
+       return FilterHandler(func(r *Record) (pass bool) {
+               return r.Context[key] != value
+       }, h)
+}
+
+func MultiHandler(hs ...LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               for _, h := range hs {
+                       // what to do about failures?
+                       h.Log(r)
+               }
+               return nil
+       })
+}
+
+// StreamHandler writes log records to an io.Writer
+// with the given format. StreamHandler can be used
+// to easily begin writing log records to other
+// outputs.
+//
+// StreamHandler wraps itself with LazyHandler and SyncHandler
+// to evaluate Lazy objects and perform safe concurrent writes.
+func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
+       h := FuncHandler(func(r *Record) error {
+               _, err := wr.Write(fmtr.Format(r))
+               return err
+       })
+       return LazyHandler(SyncHandler(h))
+}
+
+// Filter handler
+func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               if fn(r) {
+                       return h.Log(r)
+               }
+               return nil
+       })
+}
+
+// List log handler handles a list of LogHandlers
+type ListLogHandler struct {
+       handlers []LogHandler
+}
+
+// Create a new list of log handlers
+func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
+       ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
+       return ll
+}
+
+// Log the record
+func (ll *ListLogHandler) Log(r *Record) (err error) {
+       for _, handler := range ll.handlers {
+               if err == nil {
+                       err = handler.Log(r)
+               } else {
+                       handler.Log(r)
+               }
+       }
+       return
+}
+
+// Add another log handler
+func (ll *ListLogHandler) Add(h LogHandler) {
+       if h != nil {
+               ll.handlers = append(ll.handlers, h)
+       }
+}
+
+// Remove a log handler
+func (ll *ListLogHandler) Del(h LogHandler) {
+       if h != nil {
+               for i, handler := range ll.handlers {
+                       if handler == h {
+                               ll.handlers = append(ll.handlers[:i], ll.handlers[i+1:]...)
+                       }
+               }
+       }
+}
diff --git a/src/foundation/api/revel/logger/init.go b/src/foundation/api/revel/logger/init.go
new file mode 100644 (file)
index 0000000..6a02cf8
--- /dev/null
@@ -0,0 +1,189 @@
+package logger
+
+// Get all handlers based on the Config (if available)
+import (
+       "fmt"
+       "github.com/revel/config"
+       "log"
+       "os"
+       "path/filepath"
+       "strings"
+)
+
+func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
+       // If running in test mode suppress anything that is not an error
+       if config != nil && config.BoolDefault(TEST_MODE_FLAG, false) {
+               // Preconfigure all the options
+               config.SetOption("log.info.output", "none")
+               config.SetOption("log.debug.output", "none")
+               config.SetOption("log.warn.output", "none")
+               config.SetOption("log.error.output", "stderr")
+               config.SetOption("log.crit.output", "stderr")
+       }
+
+       // If the configuration has an all option we can skip some
+       c, _ = NewCompositeMultiHandler()
+
+       // Filters are assigned first, non filtered items override filters
+       if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
+               initAllLog(c, basePath, config)
+       }
+       initLogLevels(c, basePath, config)
+       if c.CriticalHandler == nil && c.ErrorHandler != nil {
+               c.CriticalHandler = c.ErrorHandler
+       }
+       if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) {
+               initFilterLog(c, basePath, config)
+               if c.CriticalHandler == nil && c.ErrorHandler != nil {
+                       c.CriticalHandler = c.ErrorHandler
+               }
+               initRequestLog(c, basePath, config)
+       }
+
+       return c
+}
+
+// Init the log.all configuration options
+func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
+       if config != nil {
+               extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
+               if output, found := config.String("log.all.output"); found {
+                       // Set all output for the specified handler
+                       if extraLogFlag {
+                               log.Printf("Adding standard handler for levels to >%s< ", output)
+                       }
+                       initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
+               }
+       }
+}
+
+// Init the filter options
+// log.all.filter ....
+// log.error.filter ....
+func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
+
+       if config != nil {
+               extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
+
+               for _, logFilter := range logFilterList {
+                       // Init for all filters
+                       for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
+                               "trace", // TODO trace is deprecated
+                       } {
+                               optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
+                               for _, option := range optionList {
+                                       splitOptions := strings.Split(option, ".")
+                                       keyMap := map[string]interface{}{}
+                                       for x := 3; x < len(splitOptions); x += 2 {
+                                               keyMap[splitOptions[x]] = splitOptions[x+1]
+                                       }
+                                       phandler := logFilter.parentHandler(keyMap)
+                                       if extraLogFlag {
+                                               log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
+                                               fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap)
+                                       }
+
+                                       if name == "all" {
+                                               initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
+                                       } else {
+                                               initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+// Init the log.error, log.warn etc configuration options
+func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
+       for _, name := range []string{"debug", "info", "warn", "error", "crit",
+               "trace", // TODO trace is deprecated
+       } {
+               if config != nil {
+                       extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false)
+                       output, found := config.String("log." + name + ".output")
+                       if found {
+                               if extraLogFlag {
+                                       log.Printf("Adding standard handler %s output %s", name, output)
+                               }
+                               initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
+                       }
+                       // Gets the list of options with said prefix
+               } else {
+                       initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
+               }
+       }
+}
+
+// Init the request log options
+func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
+       // Request logging to a separate output handler
+       // This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
+       // context with the word "section=requestlog" to that handler.
+       // Note if request logging is not enabled the MatchAbHandler will not be added and the
+       // request log messages will be sent out the INFO handler
+       outputRequest := "stdout"
+       if config != nil {
+               outputRequest = config.StringDefault("log.request.output", "")
+       }
+       oldInfo := c.InfoHandler
+       c.InfoHandler = nil
+       if outputRequest != "" {
+               initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
+       }
+       if c.InfoHandler != nil || oldInfo != nil {
+               if c.InfoHandler == nil {
+                       c.InfoHandler = oldInfo
+               } else {
+                       c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
+               }
+       }
+}
+
+// Returns a handler for the level using the output string
+// Accept formats for output string are
+// LogFunctionMap[value] callback function
+// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
+func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
+       if options.Ctx != nil {
+               options.SetExtendedOptions(
+                       "noColor", !options.Ctx.BoolDefault("log.colorize", true),
+                       "smallDate", options.Ctx.BoolDefault("log.smallDate", true),
+                       "maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
+                       "maxAge", options.Ctx.IntDefault("log.maxage", 14),
+                       "maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
+                       "compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
+               )
+       }
+
+       output = strings.TrimSpace(output)
+       if funcHandler, found := LogFunctionMap[output]; found {
+               funcHandler(c, options)
+       } else {
+               switch output {
+               case "":
+                       fallthrough
+               case "off":
+               // No handler, discard data
+               default:
+                       // Write to file specified
+                       if !filepath.IsAbs(output) {
+                               output = filepath.Join(basePath, output)
+                       }
+
+                       if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
+                               log.Panic(err)
+                       }
+
+                       if strings.HasSuffix(output, "json") {
+                               c.SetJsonFile(output, options)
+                       } else {
+                               // Override defaults for a terminal file
+                               options.SetExtendedOptions("noColor", true)
+                               options.SetExtendedOptions("smallDate", false)
+                               c.SetTerminalFile(output, options)
+                       }
+               }
+       }
+       return
+}
diff --git a/src/foundation/api/revel/logger/init_test.go b/src/foundation/api/revel/logger/init_test.go
new file mode 100644 (file)
index 0000000..50fcd61
--- /dev/null
@@ -0,0 +1,273 @@
+// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package logger_test
+
+import (
+       "github.com/revel/config"
+       "github.com/revel/revel/logger"
+       "github.com/stretchr/testify/assert"
+       "os"
+       "strings"
+       "testing"
+)
+
+type (
+       // A counter for the tester
+       testCounter struct {
+               debug, info, warn, error, critical int
+       }
+       // The data to tes
+       testData struct {
+               config []string
+               result testResult
+               tc     *testCounter
+       }
+       // The test result
+       testResult struct {
+               debug, info, warn, error, critical int
+       }
+)
+
+// Single test cases
+var singleCases = []testData{
+       {config: []string{"log.crit.output"},
+               result: testResult{0, 0, 0, 0, 1}},
+       {config: []string{"log.error.output"},
+               result: testResult{0, 0, 0, 1, 1}},
+       {config: []string{"log.warn.output"},
+               result: testResult{0, 0, 1, 0, 0}},
+       {config: []string{"log.info.output"},
+               result: testResult{0, 1, 0, 0, 0}},
+       {config: []string{"log.debug.output"},
+               result: testResult{1, 0, 0, 0, 0}},
+}
+
+// Test singles
+func TestSingleCases(t *testing.T) {
+       rootLog := logger.New()
+       for _, testCase := range singleCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// Filter test cases
+var filterCases = []testData{
+       {config: []string{"log.crit.filter.module.app"},
+               result: testResult{0, 0, 0, 0, 1}},
+       {config: []string{"log.crit.filter.module.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.error.filter.module.app"},
+               result: testResult{0, 0, 0, 1, 1}},
+       {config: []string{"log.error.filter.module.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.warn.filter.module.app"},
+               result: testResult{0, 0, 1, 0, 0}},
+       {config: []string{"log.warn.filter.module.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.info.filter.module.app"},
+               result: testResult{0, 1, 0, 0, 0}},
+       {config: []string{"log.info.filter.module.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.debug.filter.module.app"},
+               result: testResult{1, 0, 0, 0, 0}},
+       {config: []string{"log.debug.filter.module.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+}
+
+// Filter test
+func TestFilterCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for _, testCase := range filterCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// Inverse test cases
+var nfilterCases = []testData{
+       {config: []string{"log.crit.nfilter.module.appa"},
+               result: testResult{0, 0, 0, 0, 1}},
+       {config: []string{"log.crit.nfilter.modules.appa"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.crit.nfilter.module.app"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error
+               result: testResult{0, 0, 0, 1, 1}},
+       {config: []string{"log.error.nfilter.module.app"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.warn.nfilter.module.appa"},
+               result: testResult{0, 0, 1, 0, 0}},
+       {config: []string{"log.warn.nfilter.module.app"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.info.nfilter.module.appa"},
+               result: testResult{0, 1, 0, 0, 0}},
+       {config: []string{"log.info.nfilter.module.app"},
+               result: testResult{0, 0, 0, 0, 0}},
+       {config: []string{"log.debug.nfilter.module.appa"},
+               result: testResult{1, 0, 0, 0, 0}},
+       {config: []string{"log.debug.nfilter.module.app"},
+               result: testResult{0, 0, 0, 0, 0}},
+}
+
+// Inverse test
+func TestNotFilterCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for _, testCase := range nfilterCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// off test cases
+var offCases = []testData{
+       {config: []string{"log.all.output", "log.error.output=off"},
+               result: testResult{1, 1, 1, 0, 1}},
+}
+
+// Off test
+func TestOffCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for _, testCase := range offCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// Duplicate test cases
+var duplicateCases = []testData{
+       {config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"},
+               result: testResult{1, 1, 1, 2, 1}},
+}
+
+// test duplicate cases
+func TestDuplicateCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for _, testCase := range duplicateCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// Contradicting cases
+var contradictCases = []testData{
+       {config: []string{"log.all.output", "log.error.output=off", "log.all.output"},
+               result: testResult{1, 1, 1, 0, 1}},
+       {config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"},
+               result: testResult{2, 1, 1, 0, 1}},
+       {config: []string{"log.all.filter.module.app", "log.info.output=off", "log.info.filter.module.app"},
+               result: testResult{1, 2, 1, 1, 1}},
+       {config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"},
+               result: testResult{1, 1, 1, 1, 1}},
+}
+
+// Contradiction test
+func TestContradictCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for _, testCase := range contradictCases {
+               testCase.logTest(rootLog, t)
+               testCase.validate(t)
+       }
+}
+
+// All test cases
+var allCases = []testData{
+       {config: []string{"log.all.filter.module.app"},
+               result: testResult{1, 1, 1, 1, 1}},
+       {config: []string{"log.all.output"},
+               result: testResult{2, 2, 2, 2, 2}},
+}
+
+// All tests
+func TestAllCases(t *testing.T) {
+       rootLog := logger.New("module", "app")
+       for i, testCase := range allCases {
+               testCase.logTest(rootLog, t)
+               allCases[i] = testCase
+       }
+       rootLog = logger.New()
+       for i, testCase := range allCases {
+               testCase.logTest(rootLog, t)
+               allCases[i] = testCase
+       }
+       for _, testCase := range allCases {
+               testCase.validate(t)
+       }
+
+}
+
+func (c *testCounter) Log(r *logger.Record) error {
+       switch r.Level {
+       case logger.LvlDebug:
+               c.debug++
+       case logger.LvlInfo:
+               c.info++
+       case logger.LvlWarn:
+               c.warn++
+       case logger.LvlError:
+               c.error++
+       case logger.LvlCrit:
+               c.critical++
+       default:
+               panic("Unknown log level")
+       }
+       return nil
+}
+func (td *testData) logTest(rootLog logger.MultiLogger, t *testing.T) {
+       if td.tc == nil {
+               td.tc = &testCounter{}
+               counterInit(td.tc)
+       }
+       newContext := config.NewContext()
+       for _, i := range td.config {
+               iout := strings.Split(i, "=")
+               if len(iout) > 1 {
+                       newContext.SetOption(iout[0], iout[1])
+               } else {
+                       newContext.SetOption(i, "test")
+               }
+       }
+
+       newContext.SetOption("specialUseFlag", "true")
+
+       handler := logger.InitializeFromConfig("test", newContext)
+
+       rootLog.SetHandler(handler)
+
+       td.runLogTest(rootLog)
+}
+
+func (td *testData) runLogTest(log logger.MultiLogger) {
+       log.Debug("test")
+       log.Info("test")
+       log.Warn("test")
+       log.Error("test")
+       log.Crit("test")
+}
+
+func (td *testData) validate(t *testing.T) {
+       t.Logf("Test %#v expected %#v", td.tc, td.result)
+       assert.Equal(t, td.result.debug, td.tc.debug, "Debug failed "+strings.Join(td.config, " "))
+       assert.Equal(t, td.result.info, td.tc.info, "Info failed "+strings.Join(td.config, " "))
+       assert.Equal(t, td.result.warn, td.tc.warn, "Warn failed "+strings.Join(td.config, " "))
+       assert.Equal(t, td.result.error, td.tc.error, "Error failed "+strings.Join(td.config, " "))
+       assert.Equal(t, td.result.critical, td.tc.critical, "Critical failed "+strings.Join(td.config, " "))
+}
+
+// Add test to the function map
+func counterInit(tc *testCounter) {
+       logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) {
+               // Output to the test log and the stdout
+               outHandler := logger.LogHandler(
+                       logger.NewListLogHandler(tc,
+                               logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))),
+               )
+               if logOptions.HandlerWrap != nil {
+                       outHandler = logOptions.HandlerWrap.SetChild(outHandler)
+               }
+
+               c.SetHandlers(outHandler, logOptions)
+       }
+}
diff --git a/src/foundation/api/revel/logger/log_function_map.go b/src/foundation/api/revel/logger/log_function_map.go
new file mode 100644 (file)
index 0000000..ebbc76d
--- /dev/null
@@ -0,0 +1,37 @@
+package logger
+
+import (
+       "os"
+)
+
+// The log function map can be added to, so that you can specify your own logging mechanism
+// it has defaults for off, stdout, stderr
+var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
+       // Do nothing - set the logger off
+       "off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
+               // Only drop the results if there is a parent handler defined
+               if logOptions.HandlerWrap != nil {
+                       for _, l := range logOptions.Levels {
+                               c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
+                       }
+               } else {
+                       // Clear existing handler
+                       c.SetHandlers(NilHandler(), logOptions)
+               }
+       },
+       // Do nothing - set the logger off
+       "": func(*CompositeMultiHandler, *LogOptions) {},
+       // Set the levels to stdout, replace existing
+       "stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
+               if logOptions.Ctx != nil {
+                       logOptions.SetExtendedOptions(
+                               "noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
+                               "smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
+               }
+               c.SetTerminal(os.Stdout, logOptions)
+       },
+       // Set the levels to stderr output to terminal
+       "stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
+               c.SetTerminal(os.Stderr, logOptions)
+       },
+}
diff --git a/src/foundation/api/revel/logger/logger.go b/src/foundation/api/revel/logger/logger.go
new file mode 100644 (file)
index 0000000..b9abdaf
--- /dev/null
@@ -0,0 +1,203 @@
+package logger
+
+import (
+       "fmt"
+       "github.com/revel/config"
+       "time"
+)
+
+// The LogHandler defines the interface to handle the log records
+type (
+       // The Multilogger reduces the number of exposed defined logging variables,
+       // and allows the output to be easily refined
+       MultiLogger interface {
+               // New returns a new Logger that has this logger's context plus the given context
+               New(ctx ...interface{}) MultiLogger
+
+               // SetHandler updates the logger to write records to the specified handler.
+               SetHandler(h LogHandler)
+
+               // Set the stack depth for the logger
+               SetStackDepth(int) MultiLogger
+
+               // Log a message at the given level with context key/value pairs
+               Debug(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters
+               Debugf(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs
+               Info(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters
+               Infof(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs
+               Warn(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters
+               Warnf(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs
+               Error(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters
+               Errorf(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs
+               Crit(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters
+               Critf(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs and exits
+               Fatal(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters and exits
+               Fatalf(msg string, params ...interface{})
+
+               // Log a message at the given level with context key/value pairs and panics
+               Panic(msg string, ctx ...interface{})
+
+               // Log a message at the given level formatting message with the parameters and panics
+               Panicf(msg string, params ...interface{})
+       }
+
+       // The log handler interface
+       LogHandler interface {
+               Log(*Record) error
+               //log15.Handler
+       }
+
+       // The log stack handler interface
+       LogStackHandler interface {
+               LogHandler
+               GetStack() int
+       }
+
+       // The log handler interface which has child logs
+       ParentLogHandler interface {
+               SetChild(handler LogHandler) LogHandler
+       }
+
+       // The log format interface
+       LogFormat interface {
+               Format(r *Record) []byte
+       }
+
+       // The log level type
+       LogLevel int
+
+       // Used for the callback to LogFunctionMap
+       LogOptions struct {
+               Ctx                    *config.Context
+               ReplaceExistingHandler bool
+               HandlerWrap            ParentLogHandler
+               Levels                 []LogLevel
+               ExtendedOptions        map[string]interface{}
+       }
+
+       // The log record
+       Record struct {
+               Message string     // The message
+               Time    time.Time  // The time
+               Level   LogLevel   //The level
+               Call    CallStack  // The call stack if built
+               Context ContextMap // The context
+       }
+
+       // The lazy structure to implement a function to be invoked only if needed
+       Lazy struct {
+               Fn interface{} // the function
+       }
+
+       // Currently the only requirement for the callstack is to support the Formatter method
+       // which stack.Call does so we use that
+       CallStack interface {
+               fmt.Formatter // Requirement
+       }
+)
+
+// FormatFunc returns a new Format object which uses
+// the given function to perform record formatting.
+func FormatFunc(f func(*Record) []byte) LogFormat {
+       return formatFunc(f)
+}
+
+type formatFunc func(*Record) []byte
+
+func (f formatFunc) Format(r *Record) []byte {
+       return f(r)
+}
+func NewRecord(message string, level LogLevel) *Record {
+       return &Record{Message: message, Context: ContextMap{}, Level: level}
+}
+
+const (
+       LvlCrit  LogLevel = iota // Critical
+       LvlError                 // Error
+       LvlWarn                  // Warning
+       LvlInfo                  // Information
+       LvlDebug                 // Debug
+)
+
+// A list of all the log levels
+var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
+
+// Implements the ParentLogHandler
+type parentLogHandler struct {
+       setChild func(handler LogHandler) LogHandler
+}
+
+// Create a new parent log handler
+func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
+       return &parentLogHandler{callBack}
+}
+
+// Sets the child of the log handler
+func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
+       return p.setChild(child)
+}
+
+// Create a new log options
+func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
+       logOptions = &LogOptions{
+               Ctx: cfg,
+               ReplaceExistingHandler: replaceHandler,
+               HandlerWrap:            phandler,
+               Levels:                 lvl,
+               ExtendedOptions:        map[string]interface{}{},
+       }
+       return
+}
+
+// Assumes options will be an even number and have a string, value syntax
+func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
+       for x := 0; x < len(options); x += 2 {
+               l.ExtendedOptions[options[x].(string)] = options[x+1]
+       }
+}
+
+// Gets a string option with default
+func (l *LogOptions) GetStringDefault(option, value string) string {
+       if v, found := l.ExtendedOptions[option]; found {
+               return v.(string)
+       }
+       return value
+}
+
+// Gets an int option with default
+func (l *LogOptions) GetIntDefault(option string, value int) int {
+       if v, found := l.ExtendedOptions[option]; found {
+               return v.(int)
+       }
+       return value
+}
+
+// Gets a boolean option with default
+func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
+       if v, found := l.ExtendedOptions[option]; found {
+               return v.(bool)
+       }
+       return value
+}
diff --git a/src/foundation/api/revel/logger/revel_logger.go b/src/foundation/api/revel/logger/revel_logger.go
new file mode 100644 (file)
index 0000000..b3a6f86
--- /dev/null
@@ -0,0 +1,142 @@
+package logger
+
+import (
+       "fmt"
+       "github.com/revel/log15"
+       "log"
+       "os"
+)
+
+// This type implements the MultiLogger
+type RevelLogger struct {
+       log15.Logger
+}
+
+// Set the systems default logger
+// Default logs will be captured and handled by revel at level info
+func SetDefaultLog(fromLog MultiLogger) {
+       log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
+       // No need to show date and time, that will be logged with revel
+       log.SetFlags(0)
+}
+
+func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
+       rl.Debug(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted info message
+func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
+       rl.Info(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted warn message
+func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
+       rl.Warn(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted error message
+func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
+       rl.Error(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted critical message
+func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
+       rl.Crit(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted fatal message
+func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
+       rl.Fatal(fmt.Sprintf(msg, param...))
+}
+
+// Print a formatted panic message
+func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
+       rl.Panic(fmt.Sprintf(msg, param...))
+}
+
+// Print a critical message and call os.Exit(1)
+func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
+       rl.Crit(msg, ctx...)
+       os.Exit(1)
+}
+
+// Print a critical message and panic
+func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
+       rl.Crit(msg, ctx...)
+       panic(msg)
+}
+
+// Override log15 method
+func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
+       old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
+       return old
+}
+
+// Set the stack level to check for the caller
+func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
+       rl.Logger.SetStackDepth(amount) // Ignore the logger returned
+       return rl
+}
+
+// Create a new logger
+func New(ctx ...interface{}) MultiLogger {
+       r := &RevelLogger{Logger: log15.New(ctx...)}
+       r.SetStackDepth(1)
+       return r
+}
+
+// Set the handler in the Logger
+func (rl *RevelLogger) SetHandler(h LogHandler) {
+       rl.Logger.SetHandler(callHandler(h.Log))
+}
+
+// The function wrapper to implement the callback
+type callHandler func(r *Record) error
+
+// Log implementation, reads the record and extracts the details from the log record
+// Hiding the implementation.
+func (c callHandler) Log(log *log15.Record) error {
+       ctx := log.Ctx
+       var ctxMap ContextMap
+       if len(ctx) > 0 {
+               ctxMap = make(ContextMap, len(ctx)/2)
+
+               for i := 0; i < len(ctx); i += 2 {
+                       v := ctx[i]
+                       key, ok := v.(string)
+                       if !ok {
+                               key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v)
+                       }
+                       var value interface{}
+                       if len(ctx) > i+1 {
+                               value = ctx[i+1]
+                       } else {
+                               value = "LOGGER_VALUE_MISSING"
+                       }
+                       ctxMap[key] = value
+               }
+       } else {
+               ctxMap = make(ContextMap, 0)
+       }
+       r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)}
+       return c(r)
+}
+
+// Internally used contextMap, allows conversion of map to map[string]string
+type ContextMap map[string]interface{}
+
+// Convert the context map to be string only values, any non string values are ignored
+func (m ContextMap) StringMap() (newMap map[string]string) {
+       if m != nil {
+               newMap = map[string]string{}
+               for key, value := range m {
+                       if svalue, isstring := value.(string); isstring {
+                               newMap[key] = svalue
+                       }
+               }
+       }
+       return
+}
+func (m ContextMap) Add(key string, value interface{}) {
+       m[key] = value
+}
diff --git a/src/foundation/api/revel/logger/terminal_format.go b/src/foundation/api/revel/logger/terminal_format.go
new file mode 100644 (file)
index 0000000..ca2cd15
--- /dev/null
@@ -0,0 +1,245 @@
+package logger
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       "reflect"
+       "strconv"
+       "sync"
+       "time"
+)
+
+const (
+       timeFormat          = "2006-01-02T15:04:05-0700"
+       termTimeFormat      = "2006/01/02 15:04:05"
+       termSmallTimeFormat = "15:04:05"
+       floatFormat         = 'f'
+       errorKey            = "REVEL_ERROR"
+)
+
+var (
+       levelString = map[LogLevel]string{LvlDebug: "DEBUG",
+               LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT"}
+)
+
+// Outputs to the terminal in a format like below
+// INFO  09:11:32 server-engine.go:169: Request Stats
+func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
+       dateFormat := termTimeFormat
+       if smallDate {
+               dateFormat = termSmallTimeFormat
+       }
+       return FormatFunc(func(r *Record) []byte {
+               // Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
+               var color = 0
+               switch r.Level {
+               case LvlCrit:
+                       // Magenta
+                       color = 35
+               case LvlError:
+                       // Red
+                       color = 31
+               case LvlWarn:
+                       // Yellow
+                       color = 33
+               case LvlInfo:
+                       // Green
+                       color = 32
+               case LvlDebug:
+                       // Cyan
+                       color = 36
+               }
+
+               b := &bytes.Buffer{}
+               caller, _ := r.Context["caller"].(string)
+               module, _ := r.Context["module"].(string)
+               if noColor == false && color > 0 {
+                       if len(module) > 0 {
+                               fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
+                       } else {
+                               fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message)
+                       }
+               } else {
+                       fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message)
+               }
+
+               i := 0
+               for k, v := range r.Context {
+                       if i != 0 {
+                               b.WriteByte(' ')
+                       }
+                       i++
+                       if k == "module" || k == "caller" {
+                               continue
+                       }
+
+                       v := formatLogfmtValue(v)
+
+                       // TODO: we should probably check that all of your key bytes aren't invalid
+                       if noColor == false && color > 0 {
+                               fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
+                       } else {
+                               b.WriteString(k)
+                               b.WriteByte('=')
+                               b.WriteString(v)
+                       }
+               }
+
+               b.WriteByte('\n')
+
+               return b.Bytes()
+       })
+}
+
+// formatValue formats a value for serialization
+func formatLogfmtValue(value interface{}) string {
+       if value == nil {
+               return "nil"
+       }
+
+       if t, ok := value.(time.Time); ok {
+               // Performance optimization: No need for escaping since the provided
+               // timeFormat doesn't have any escape characters, and escaping is
+               // expensive.
+               return t.Format(termTimeFormat)
+       }
+       value = formatShared(value)
+       switch v := value.(type) {
+       case bool:
+               return strconv.FormatBool(v)
+       case float32:
+               return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
+       case float64:
+               return strconv.FormatFloat(v, floatFormat, 7, 64)
+       case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+               return fmt.Sprintf("%d", value)
+       case string:
+               return escapeString(v)
+       default:
+               return escapeString(fmt.Sprintf("%+v", value))
+       }
+}
+
+// Format the value in json format
+func formatShared(value interface{}) (result interface{}) {
+       defer func() {
+               if err := recover(); err != nil {
+                       if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
+                               result = "nil"
+                       } else {
+                               panic(err)
+                       }
+               }
+       }()
+
+       switch v := value.(type) {
+       case time.Time:
+               return v.Format(timeFormat)
+
+       case error:
+               return v.Error()
+
+       case fmt.Stringer:
+               return v.String()
+
+       default:
+               return v
+       }
+}
+
+// A reusuable buffer for outputting data
+var stringBufPool = sync.Pool{
+       New: func() interface{} { return new(bytes.Buffer) },
+}
+
+// Escape the string when needed
+func escapeString(s string) string {
+       needsQuotes := false
+       needsEscape := false
+       for _, r := range s {
+               if r <= ' ' || r == '=' || r == '"' {
+                       needsQuotes = true
+               }
+               if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
+                       needsEscape = true
+               }
+       }
+       if needsEscape == false && needsQuotes == false {
+               return s
+       }
+       e := stringBufPool.Get().(*bytes.Buffer)
+       e.WriteByte('"')
+       for _, r := range s {
+               switch r {
+               case '\\', '"':
+                       e.WriteByte('\\')
+                       e.WriteByte(byte(r))
+               case '\n':
+                       e.WriteString("\\n")
+               case '\r':
+                       e.WriteString("\\r")
+               case '\t':
+                       e.WriteString("\\t")
+               default:
+                       e.WriteRune(r)
+               }
+       }
+       e.WriteByte('"')
+       var ret string
+       if needsQuotes {
+               ret = e.String()
+       } else {
+               ret = string(e.Bytes()[1 : e.Len()-1])
+       }
+       e.Reset()
+       stringBufPool.Put(e)
+       return ret
+}
+
+// JsonFormatEx formats log records as JSON objects. If pretty is true,
+// records will be pretty-printed. If lineSeparated is true, records
+// will be logged with a new line between each record.
+func JsonFormatEx(pretty, lineSeparated bool) LogFormat {
+       jsonMarshal := json.Marshal
+       if pretty {
+               jsonMarshal = func(v interface{}) ([]byte, error) {
+                       return json.MarshalIndent(v, "", "    ")
+               }
+       }
+
+       return FormatFunc(func(r *Record) []byte {
+               props := make(map[string]interface{})
+
+               props["t"] = r.Time
+               props["lvl"] = levelString[r.Level]
+               props["msg"] = r.Message
+               for k, v := range r.Context {
+                       props[k] = formatJsonValue(v)
+               }
+
+               b, err := jsonMarshal(props)
+               if err != nil {
+                       b, _ = jsonMarshal(map[string]string{
+                               errorKey: err.Error(),
+                       })
+                       return b
+               }
+
+               if lineSeparated {
+                       b = append(b, '\n')
+               }
+
+               return b
+       })
+}
+
+func formatJsonValue(value interface{}) interface{} {
+       value = formatShared(value)
+       switch value.(type) {
+       case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
+               return value
+       default:
+               return fmt.Sprintf("%+v", value)
+       }
+}
diff --git a/src/foundation/api/revel/logger/utils.go b/src/foundation/api/revel/logger/utils.go
new file mode 100644 (file)
index 0000000..3918cc1
--- /dev/null
@@ -0,0 +1,110 @@
+package logger
+
+import (
+       "github.com/revel/log15"
+       "gopkg.in/stack.v0"
+       "log"
+)
+
+// Utility package to make existing logging backwards compatible
+var (
+       // Convert the string to LogLevel
+       toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug),
+               "info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn),
+               "error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit),
+               "trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug
+       }
+)
+
+const (
+       // The test mode flag overrides the default log level and shows only errors
+       TEST_MODE_FLAG = "testModeFlag"
+       // The special use flag enables showing messages when the logger is setup
+       SPECIAL_USE_FLAG = "specialUseFlag"
+)
+
+// Returns the logger for the name
+func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
+       switch name {
+       case "trace": // TODO trace is deprecated, replaced by debug
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
+       case "debug":
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
+       case "info":
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
+       case "warn":
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlWarn}, "", 0)
+       case "error":
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlError}, "", 0)
+       case "request":
+               l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
+       }
+
+       return l
+
+}
+
+// Used by the initFilterLog to handle the filters
+var logFilterList = []struct {
+       LogPrefix, LogSuffix string
+       parentHandler        func(map[string]interface{}) ParentLogHandler
+}{{
+       "log.", ".filter",
+       func(keyMap map[string]interface{}) ParentLogHandler {
+               return NewParentLogHandler(func(child LogHandler) LogHandler {
+                       return MatchMapHandler(keyMap, child)
+               })
+
+       },
+}, {
+       "log.", ".nfilter",
+       func(keyMap map[string]interface{}) ParentLogHandler {
+               return NewParentLogHandler(func(child LogHandler) LogHandler {
+                       return NotMatchMapHandler(keyMap, child)
+               })
+       },
+}}
+
+// This structure and method will handle the old output format and log it to the new format
+type loggerRewrite struct {
+       Logger         MultiLogger
+       Level          log15.Lvl
+       hideDeprecated bool
+}
+
+// The message indicating that a logger is using a deprecated log mechanism
+var log_deprecated = []byte("* LOG DEPRECATED * ")
+
+// Implements the Write of the logger
+func (lr loggerRewrite) Write(p []byte) (n int, err error) {
+       if !lr.hideDeprecated {
+               p = append(log_deprecated, p...)
+       }
+       n = len(p)
+       if len(p) > 0 && p[n-1] == '\n' {
+               p = p[:n-1]
+               n--
+       }
+
+       switch lr.Level {
+       case log15.LvlInfo:
+               lr.Logger.Info(string(p))
+       case log15.LvlDebug:
+               lr.Logger.Debug(string(p))
+       case log15.LvlWarn:
+               lr.Logger.Warn(string(p))
+       case log15.LvlError:
+               lr.Logger.Error(string(p))
+       case log15.LvlCrit:
+               lr.Logger.Crit(string(p))
+       }
+
+       return
+}
+
+// For logging purposes the call stack can be used to record the stack trace of a bad error
+// simply pass it as a context field in your log statement like
+// `controller.Log.Crit("This should not occur","stack",revel.NewCallStack())`
+func NewCallStack() interface{} {
+       return stack.Trace()
+}
diff --git a/src/foundation/api/revel/logger/wrap_handlers.go b/src/foundation/api/revel/logger/wrap_handlers.go
new file mode 100644 (file)
index 0000000..3d68e75
--- /dev/null
@@ -0,0 +1,98 @@
+package logger
+
+// FuncHandler returns a Handler that logs records with the given
+// function.
+import (
+       "fmt"
+       "reflect"
+       "sync"
+       "time"
+)
+
+// Function handler wraps the declared function and returns the handler for it
+func FuncHandler(fn func(r *Record) error) LogHandler {
+       return funcHandler(fn)
+}
+
+// The type decleration for the function
+type funcHandler func(r *Record) error
+
+// The implementation of the Log
+func (h funcHandler) Log(r *Record) error {
+       return h(r)
+}
+
+// This function allows you to do a full declaration for the log,
+// it is recommended you use FuncHandler instead
+func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler {
+       return remoteHandler(log)
+}
+
+// The type used for the HandlerFunc
+type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error
+
+// The Log implementation
+func (c remoteHandler) Log(record *Record) error {
+       return c(record.Message, record.Time, record.Level, record.Call, record.Context)
+}
+
+// SyncHandler can be wrapped around a handler to guarantee that
+// only a single Log operation can proceed at a time. It's necessary
+// for thread-safe concurrent writes.
+func SyncHandler(h LogHandler) LogHandler {
+       var mu sync.Mutex
+       return FuncHandler(func(r *Record) error {
+               defer mu.Unlock()
+               mu.Lock()
+               return h.Log(r)
+       })
+}
+
+// LazyHandler writes all values to the wrapped handler after evaluating
+// any lazy functions in the record's context. It is already wrapped
+// around StreamHandler and SyslogHandler in this library, you'll only need
+// it if you write your own Handler.
+func LazyHandler(h LogHandler) LogHandler {
+       return FuncHandler(func(r *Record) error {
+               for k, v := range r.Context {
+                       if lz, ok := v.(Lazy); ok {
+                               value, err := evaluateLazy(lz)
+                               if err != nil {
+                                       r.Context[errorKey] = "bad lazy " + k
+                               } else {
+                                       v = value
+                               }
+                       }
+               }
+
+               return h.Log(r)
+       })
+}
+
+func evaluateLazy(lz Lazy) (interface{}, error) {
+       t := reflect.TypeOf(lz.Fn)
+
+       if t.Kind() != reflect.Func {
+               return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
+       }
+
+       if t.NumIn() > 0 {
+               return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
+       }
+
+       if t.NumOut() == 0 {
+               return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
+       }
+
+       value := reflect.ValueOf(lz.Fn)
+       results := value.Call([]reflect.Value{})
+       if len(results) == 1 {
+               return results[0].Interface(), nil
+       } else {
+               values := make([]interface{}, len(results))
+               for i, v := range results {
+                       values[i] = v.Interface()
+               }
+               return values, nil
+       }
+}
diff --git a/src/foundation/api/revel/model/revel_container.go b/src/foundation/api/revel/model/revel_container.go
new file mode 100644 (file)
index 0000000..c3398d9
--- /dev/null
@@ -0,0 +1,14 @@
+package model
+
+import "github.com/revel/revel/utils"
+
+// The single instance object that has the config populated to it
+type RevelContainer struct {
+       Controller struct {
+               Reuse              bool                              // True if the controllers are reused Set via revel.controller.reuse
+               Stack              *utils.SimpleLockStack            // size set by revel.controller.stack,  revel.controller.maxstack
+               CachedMap          map[string]*utils.SimpleLockStack // The map of reusable controllers
+               CachedStackSize    int                               // The default size of each stack in CachedMap Set via revel.cache.controller.stack
+               CachedStackMaxSize int                               // The max size of each stack in CachedMap Set via revel.cache.controller.maxstack
+       }
+}
diff --git a/src/foundation/api/revel/module.go b/src/foundation/api/revel/module.go
new file mode 100644 (file)
index 0000000..e51ac76
--- /dev/null
@@ -0,0 +1,213 @@
+package revel
+
+import (
+       "fmt"
+       "github.com/revel/revel/logger"
+       "go/build"
+       "gopkg.in/stack.v0"
+       "path/filepath"
+       "sort"
+       "strings"
+)
+
+// Module specific functions
+type Module struct {
+       Name, ImportPath, Path string
+       ControllerTypeList     []*ControllerType
+       Log                    logger.MultiLogger
+       initializedModules     map[string]ModuleCallbackInterface
+}
+
+// Modules can be called back after they are loaded in revel by using this interface.
+type ModuleCallbackInterface func(*Module)
+
+// The namespace separator constant
+const namespaceSeperator = `\` // (note cannot be . or : as this is already used for routes)
+
+var (
+       Modules   []*Module                                                                                     // The list of modules in use
+       anyModule = &Module{}                                                                                   // Wildcard search for controllers for a module (for backward compatible lookups)
+       appModule = &Module{Name: "App", initializedModules: map[string]ModuleCallbackInterface{}, Log: AppLog} // The app module
+       moduleLog = RevelLog.New("section", "module")
+)
+
+// Called by a module init() function, caller will receive the *Module object created for that module
+// This would be useful for assigning a logger for logging information in the module (since the module context would be correct)
+func RegisterModuleInit(callback ModuleCallbackInterface) {
+       // Store the module that called this so we can do a callback when the app is initialized
+       // The format %+k is from go-stack/Call.Format and returns the package path
+       key := fmt.Sprintf("%+k", stack.Caller(1))
+       appModule.initializedModules[key] = callback
+       if Initialized {
+               RevelLog.Error("Application already initialized, initializing using app module", "key", key)
+               callback(appModule)
+       }
+}
+
+// Called on startup to make a callback so that modules can be initialized through the `RegisterModuleInit` function
+func init() {
+       AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+               if typeOf == REVEL_BEFORE_MODULES_LOADED {
+                       Modules = []*Module{appModule}
+                       appModule.Path = filepath.ToSlash(AppPath)
+                       appModule.ImportPath = filepath.ToSlash(AppPath)
+               }
+
+               return
+       })
+}
+
+// Returns the namespace for the module in the format `module_name|`
+func (m *Module) Namespace() (namespace string) {
+       namespace = m.Name + namespaceSeperator
+       return
+}
+
+// Returns the named controller and action that is in this module
+func (m *Module) ControllerByName(name, action string) (ctype *ControllerType) {
+       comparison := name
+       if strings.Index(name, namespaceSeperator) < 0 {
+               comparison = m.Namespace() + name
+       }
+       for _, c := range m.ControllerTypeList {
+               if c.Name() == comparison {
+                       ctype = c
+                       break
+               }
+       }
+       return
+}
+
+// Adds the controller type to this module
+func (m *Module) AddController(ct *ControllerType) {
+       m.ControllerTypeList = append(m.ControllerTypeList, ct)
+}
+
+// Based on the full path given return the relevant module
+// Only to be used on initialization
+func ModuleFromPath(path string, addGopathToPath bool) (module *Module) {
+       path = filepath.ToSlash(path)
+       gopathList := filepath.SplitList(build.Default.GOPATH)
+       // Strip away the vendor folder
+       if i := strings.Index(path, "/vendor/"); i > 0 {
+               path = path[i+len("vendor/"):]
+       }
+
+       // See if the path exists in the module based
+       for i := range Modules {
+               if addGopathToPath {
+                       for _, gopath := range gopathList {
+                               if strings.Contains(filepath.ToSlash(filepath.Clean(filepath.Join(gopath, "src", path))), Modules[i].Path) {
+                                       module = Modules[i]
+                                       break
+                               }
+                       }
+               } else {
+                       if strings.Contains(path, Modules[i].ImportPath) {
+                               module = Modules[i]
+                               break
+                       }
+
+               }
+
+               if module != nil {
+                       break
+               }
+       }
+       // Default to the app module if not found
+       if module == nil {
+               module = appModule
+       }
+       return
+}
+
+// ModuleByName returns the module of the given name, if loaded, case insensitive.
+func ModuleByName(name string) (*Module, bool) {
+       // If the name ends with the namespace separator remove it
+       if name[len(name)-1] == []byte(namespaceSeperator)[0] {
+               name = name[:len(name)-1]
+       }
+       name = strings.ToLower(name)
+       if name == strings.ToLower(appModule.Name) {
+               return appModule, true
+       }
+       for _, module := range Modules {
+               if strings.ToLower(module.Name) == name {
+                       return module, true
+               }
+       }
+       return nil, false
+}
+
+// Loads the modules specified in the config
+func loadModules() {
+       keys := []string{}
+       for _, key := range Config.Options("module.") {
+               keys = append(keys, key)
+       }
+
+       // Reorder module order by key name, a poor mans sort but at least it is consistent
+       sort.Strings(keys)
+       for _, key := range keys {
+               moduleLog.Debug("Sorted keys", "keys", key)
+
+       }
+       for _, key := range keys {
+               moduleImportPath := Config.StringDefault(key, "")
+               if moduleImportPath == "" {
+                       continue
+               }
+
+               modulePath, err := ResolveImportPath(moduleImportPath)
+               if err != nil {
+                       moduleLog.Error("Failed to load module.  Import of path failed", "modulePath", moduleImportPath, "error", err)
+               }
+               // Drop anything between module.???.<name of module>
+               subKey := key[len("module."):]
+               if index := strings.Index(subKey, "."); index > -1 {
+                       subKey = subKey[index+1:]
+               }
+               addModule(subKey, moduleImportPath, modulePath)
+       }
+
+       // Modules loaded, now show module path
+       for key, callback := range appModule.initializedModules {
+               if m := ModuleFromPath(key, false); m != nil {
+                       callback(m)
+               } else {
+                       RevelLog.Error("Callback for non registered module initializing with application module", "modulePath", key)
+                       callback(appModule)
+               }
+       }
+}
+
+// called by `loadModules`, creates a new `Module` instance and appends it to the `Modules` list
+func addModule(name, importPath, modulePath string) {
+       if _, found := ModuleByName(name); found {
+               moduleLog.Panic("Attempt to import duplicate module %s path %s aborting startup", "name", name, "path", modulePath)
+       }
+       Modules = append(Modules, &Module{Name: name,
+               ImportPath: filepath.ToSlash(importPath),
+               Path:       filepath.ToSlash(modulePath),
+               Log:        RootLog.New("module", name)})
+       if codePath := filepath.Join(modulePath, "app"); DirExists(codePath) {
+               CodePaths = append(CodePaths, codePath)
+               if viewsPath := filepath.Join(modulePath, "app", "views"); DirExists(viewsPath) {
+                       TemplatePaths = append(TemplatePaths, viewsPath)
+               }
+       }
+
+       moduleLog.Debug("Loaded module ", "module", filepath.Base(modulePath))
+
+       // Hack: There is presently no way for the testrunner module to add the
+       // "test" subdirectory to the CodePaths.  So this does it instead.
+       if importPath == Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") {
+               joinedPath := filepath.Join(BasePath, "tests")
+               moduleLog.Debug("Found testrunner module, adding `tests` path ", "path", joinedPath)
+               CodePaths = append(CodePaths, joinedPath)
+       }
+       if testsPath := filepath.Join(modulePath, "tests"); DirExists(testsPath) {
+               moduleLog.Debug("Found tests path ", "path", testsPath)
+               CodePaths = append(CodePaths, testsPath)
+       }
+}
diff --git a/src/foundation/api/revel/namespace.go b/src/foundation/api/revel/namespace.go
new file mode 100644 (file)
index 0000000..9f66cf4
--- /dev/null
@@ -0,0 +1,37 @@
+package revel
+
+import (
+       "bytes"
+       "regexp"
+)
+
+// Module matching template syntax allows for modules to replace this text with the name of the module declared on import
+// this allows the reverse router to use correct syntax
+// Match _LOCAL_.static or  _LOCAL_|
+var namespaceReplacement = regexp.MustCompile(`(_LOCAL_)(\.(.*?))?\\`)
+
+// Function to replace the bytes data that may match the _LOCAL_ namespace specifier,
+// the replacement will be the current module.Name
+func namespaceReplace(fileBytes []byte, module *Module) []byte {
+       newBytes, lastIndex := &bytes.Buffer{}, 0
+       matches := namespaceReplacement.FindAllSubmatchIndex(fileBytes, -1)
+       for _, match := range matches {
+               // Write up to first bytes
+               newBytes.Write(fileBytes[lastIndex:match[0]])
+               // skip ahead index to match[1]
+               lastIndex = match[3]
+               if match[4] > 0 {
+                       // This match includes the module name as imported by the module
+                       // We could transform the module name if it is different..
+                       // For now leave it the same
+                       // so _LOCAL_.static| becomes static|
+                       lastIndex++
+               } else {
+                       // Inject the module name
+                       newBytes.Write([]byte(module.Name))
+               }
+       }
+       // Write remainder of document
+       newBytes.Write(fileBytes[lastIndex:])
+       return newBytes.Bytes()
+}
diff --git a/src/foundation/api/revel/panic.go b/src/foundation/api/revel/panic.go
new file mode 100644 (file)
index 0000000..464f2de
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http"
+       "runtime/debug"
+)
+
+// PanicFilter wraps the action invocation in a protective defer blanket that
+// converts panics into 500 error pages.
+func PanicFilter(c *Controller, fc []Filter) {
+       defer func() {
+               if err := recover(); err != nil {
+                       handleInvocationPanic(c, err)
+               }
+       }()
+       fc[0](c, fc[1:])
+}
+
+// This function handles a panic in an action invocation.
+// It cleans up the stack trace, logs it, and displays an error page.
+func handleInvocationPanic(c *Controller, err interface{}) {
+       error := NewErrorFromPanic(err)
+       if error != nil {
+               utilLog.Error("PanicFilter: Caught panic", "error", err, "stack", error.Stack)
+               if DevMode {
+                       fmt.Println(err)
+                       fmt.Println(error.Stack)
+               }
+       } else {
+               utilLog.Error("PanicFilter: Caught panic, unable to determine stack location", "error", err, "stack", string(debug.Stack()))
+               if DevMode {
+                       fmt.Println(err)
+                       fmt.Println("stack", string(debug.Stack()))
+               }
+       }
+
+       if error == nil && DevMode {
+               // Only show the sensitive information in the debug stack trace in development mode, not production
+               c.Response.SetStatus(http.StatusInternalServerError)
+               _, _ = c.Response.GetWriter().Write(debug.Stack())
+               return
+       }
+
+       c.Result = c.RenderError(error)
+}
diff --git a/src/foundation/api/revel/params.go b/src/foundation/api/revel/params.go
new file mode 100644 (file)
index 0000000..3df3dde
--- /dev/null
@@ -0,0 +1,180 @@
+// Copyright (c) 2012-2017 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "encoding/json"
+       "errors"
+       "io/ioutil"
+       "mime/multipart"
+       "net/url"
+       "os"
+       "reflect"
+)
+
+// Params provides a unified view of the request params.
+// Includes:
+// - URL query string
+// - Form values
+// - File uploads
+//
+// Warning: param maps other than Values may be nil if there were none.
+type Params struct {
+       url.Values // A unified view of all the individual param maps below.
+
+       // Set by the router
+       Fixed url.Values // Fixed parameters from the route, e.g. App.Action("fixed param")
+       Route url.Values // Parameters extracted from the route,  e.g. /customers/{id}
+
+       // Set by the ParamsFilter
+       Query url.Values // Parameters from the query string, e.g. /index?limit=10
+       Form  url.Values // Parameters from the request body.
+
+       Files    map[string][]*multipart.FileHeader // Files uploaded in a multipart form
+       tmpFiles []*os.File                         // Temp files used during the request.
+       JSON     []byte                             // JSON data from request body
+}
+
+var paramsLogger = RevelLog.New("section", "params")
+
+// ParseParams parses the `http.Request` params into `revel.Controller.Params`
+func ParseParams(params *Params, req *Request) {
+       params.Query = req.GetQuery()
+
+       // Parse the body depending on the content type.
+       switch req.ContentType {
+       case "application/x-www-form-urlencoded":
+               // Typical form.
+               var err error
+               if params.Form, err = req.GetForm(); err != nil {
+                       paramsLogger.Warn("ParseParams: Error parsing request body", "error", err)
+               }
+
+       case "multipart/form-data":
+               // Multipart form.
+               if mp, err := req.GetMultipartForm(); err != nil {
+                       paramsLogger.Warn("ParseParams: parsing request body:", "error", err)
+               } else {
+                       params.Form = mp.GetValues()
+                       params.Files = mp.GetFiles()
+               }
+       case "application/json":
+               fallthrough
+       case "text/json":
+               if body := req.GetBody(); body != nil {
+                       if content, err := ioutil.ReadAll(body); err == nil {
+                               // We wont bind it until we determine what we are binding too
+                               params.JSON = content
+                       } else {
+                               paramsLogger.Error("ParseParams: Failed to ready request body bytes", "error", err)
+                       }
+               } else {
+                       paramsLogger.Info("ParseParams: Json post received with empty body")
+               }
+       }
+
+       params.Values = params.calcValues()
+}
+
+// Bind looks for the named parameter, converts it to the requested type, and
+// writes it into "dest", which must be settable.  If the value can not be
+// parsed, "dest" is set to the zero value.
+func (p *Params) Bind(dest interface{}, name string) {
+       value := reflect.ValueOf(dest)
+       if value.Kind() != reflect.Ptr {
+               paramsLogger.Panic("Bind: revel/params: non-pointer passed to Bind: " + name)
+       }
+       value = value.Elem()
+       if !value.CanSet() {
+               paramsLogger.Panic("Bind: revel/params: non-settable variable passed to Bind: " + name)
+       }
+
+       // Remove the json from the Params, this will stop the binder from attempting
+       // to use the json data to populate the destination interface. We do not want
+       // to do this on a named bind directly against the param, it is ok to happen when
+       // the action is invoked.
+       jsonData := p.JSON
+       p.JSON = nil
+       value.Set(Bind(p, name, value.Type()))
+       p.JSON = jsonData
+}
+
+// Bind binds the JSON data to the dest.
+func (p *Params) BindJSON(dest interface{}) error {
+       value := reflect.ValueOf(dest)
+       if value.Kind() != reflect.Ptr {
+               paramsLogger.Warn("BindJSON: Not a pointer")
+               return errors.New("BindJSON not a pointer")
+       }
+       if err := json.Unmarshal(p.JSON, dest); err != nil {
+               paramsLogger.Warn("BindJSON: Unable to unmarshal request:", "error", err)
+               return err
+       }
+       return nil
+}
+
+// calcValues returns a unified view of the component param maps.
+func (p *Params) calcValues() url.Values {
+       numParams := len(p.Query) + len(p.Fixed) + len(p.Route) + len(p.Form)
+
+       // If there were no params, return an empty map.
+       if numParams == 0 {
+               return make(url.Values, 0)
+       }
+
+       // If only one of the param sources has anything, return that directly.
+       switch numParams {
+       case len(p.Query):
+               return p.Query
+       case len(p.Route):
+               return p.Route
+       case len(p.Fixed):
+               return p.Fixed
+       case len(p.Form):
+               return p.Form
+       }
+
+       // Copy everything into a param map,
+       // order of priority is least to most trusted
+       values := make(url.Values, numParams)
+
+       // ?query string parameters are first
+       for k, v := range p.Query {
+               values[k] = append(values[k], v...)
+       }
+
+       // form parameters append
+       for k, v := range p.Form {
+               values[k] = append(values[k], v...)
+       }
+
+       // :/path parameters overwrite
+       for k, v := range p.Route {
+               values[k] = v
+       }
+
+       // fixed route parameters overwrite
+       for k, v := range p.Fixed {
+               values[k] = v
+       }
+
+       return values
+}
+
+func ParamsFilter(c *Controller, fc []Filter) {
+       ParseParams(c.Params, c.Request)
+
+       // Clean up from the request.
+       defer func() {
+               for _, tmpFile := range c.Params.tmpFiles {
+                       err := os.Remove(tmpFile.Name())
+                       if err != nil {
+                               paramsLogger.Warn("ParamsFilter: Could not remove upload temp file:", err)
+                       }
+               }
+       }()
+
+       fc[0](c, fc[1:])
+}
diff --git a/src/foundation/api/revel/params_test.go b/src/foundation/api/revel/params_test.go
new file mode 100644 (file)
index 0000000..571a244
--- /dev/null
@@ -0,0 +1,182 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "net/url"
+       "reflect"
+       "testing"
+)
+
+// Params: Testing Multipart forms
+
+const (
+       MultipartBoundary = "A"
+       MultipartFormData = `--A
+Content-Disposition: form-data; name="text1"
+
+data1
+--A
+Content-Disposition: form-data; name="text2"
+
+data2
+--A
+Content-Disposition: form-data; name="text2"
+
+data3
+--A
+Content-Disposition: form-data; name="file1"; filename="test.txt"
+Content-Type: text/plain
+
+content1
+--A
+Content-Disposition: form-data; name="file2[]"; filename="test.txt"
+Content-Type: text/plain
+
+content2
+--A
+Content-Disposition: form-data; name="file2[]"; filename="favicon.ico"
+Content-Type: image/x-icon
+
+xyz
+--A
+Content-Disposition: form-data; name="file3[0]"; filename="test.txt"
+Content-Type: text/plain
+
+content3
+--A
+Content-Disposition: form-data; name="file3[1]"; filename="favicon.ico"
+Content-Type: image/x-icon
+
+zzz
+--A--
+`
+)
+
+// The values represented by the form data.
+type fh struct {
+       filename string
+       content  []byte
+}
+
+var (
+       expectedValues = map[string][]string{
+               "text1": {"data1"},
+               "text2": {"data2", "data3"},
+       }
+       expectedFiles = map[string][]fh{
+               "file1":    {fh{"test.txt", []byte("content1")}},
+               "file2[]":  {fh{"test.txt", []byte("content2")}, fh{"favicon.ico", []byte("xyz")}},
+               "file3[0]": {fh{"test.txt", []byte("content3")}},
+               "file3[1]": {fh{"favicon.ico", []byte("zzz")}},
+       }
+)
+
+func getMultipartRequest() *http.Request {
+       req, _ := http.NewRequest("POST", "http://localhost/path",
+               bytes.NewBufferString(MultipartFormData))
+       req.Header.Set(
+               "Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", MultipartBoundary))
+       req.Header.Set(
+               "Content-Length", fmt.Sprintf("%d", len(MultipartFormData)))
+       return req
+}
+
+func BenchmarkParams(b *testing.B) {
+       c := NewTestController(nil, showRequest)
+       c.Params = &Params{}
+
+       for i := 0; i < b.N; i++ {
+               ParamsFilter(c, NilChain)
+       }
+}
+
+func TestMultipartForm(t *testing.T) {
+       c := NewTestController(nil, getMultipartRequest())
+       c.Params = &Params{}
+
+       ParamsFilter(c, NilChain)
+
+       if !reflect.DeepEqual(expectedValues, map[string][]string(c.Params.Values)) {
+               t.Errorf("Param values: (expected) %v != %v (actual)",
+                       expectedValues, map[string][]string(c.Params.Values))
+       }
+
+       actualFiles := make(map[string][]fh)
+       for key, fileHeaders := range c.Params.Files {
+               for _, fileHeader := range fileHeaders {
+                       file, _ := fileHeader.Open()
+                       content, _ := ioutil.ReadAll(file)
+                       actualFiles[key] = append(actualFiles[key], fh{fileHeader.Filename, content})
+               }
+       }
+
+       if !reflect.DeepEqual(expectedFiles, actualFiles) {
+               t.Errorf("Param files: (expected) %v != %v (actual)", expectedFiles, actualFiles)
+       }
+}
+
+func TestBind(t *testing.T) {
+       params := Params{
+               Values: url.Values{
+                       "x": {"5"},
+               },
+       }
+       var x int
+       params.Bind(&x, "x")
+       if x != 5 {
+               t.Errorf("Failed to bind x.  Value: %d", x)
+       }
+}
+
+func TestResolveAcceptLanguage(t *testing.T) {
+       request := buildHTTPRequestWithAcceptLanguage("")
+       if result := ResolveAcceptLanguage(request); result != nil {
+               t.Errorf("Expected Accept-Language to resolve to an empty string but it was '%s'", result)
+       }
+
+       request = buildHTTPRequestWithAcceptLanguage("en-GB,en;q=0.8,nl;q=0.6")
+       if result := ResolveAcceptLanguage(request); len(result) != 3 {
+               t.Errorf("Unexpected Accept-Language values length of %d (expected %d)", len(result), 3)
+       } else {
+               if result[0].Language != "en-GB" {
+                       t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en-GB", result[0].Language)
+               }
+               if result[1].Language != "en" {
+                       t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en", result[1].Language)
+               }
+               if result[2].Language != "nl" {
+                       t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "nl", result[2].Language)
+               }
+       }
+
+       request = buildHTTPRequestWithAcceptLanguage("en;q=0.8,nl;q=0.6,en-AU;q=malformed")
+       if result := ResolveAcceptLanguage(request); len(result) != 3 {
+               t.Errorf("Unexpected Accept-Language values length of %d (expected %d)", len(result), 3)
+       } else {
+               if result[0].Language != "en-AU" {
+                       t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en-AU", result[0].Language)
+               }
+       }
+}
+
+func BenchmarkResolveAcceptLanguage(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               request := buildHTTPRequestWithAcceptLanguage("en-GB,en;q=0.8,nl;q=0.6,fr;q=0.5,de-DE;q=0.4,no-NO;q=0.4,ru;q=0.2")
+               ResolveAcceptLanguage(request)
+       }
+}
+
+func buildHTTPRequestWithAcceptLanguage(acceptLanguage string) *Request {
+       request, _ := http.NewRequest("POST", "http://localhost/path", nil)
+       request.Header.Set("Accept-Language", acceptLanguage)
+       c := NewTestController(nil, request)
+
+       return c.Request
+}
diff --git a/src/foundation/api/revel/results.go b/src/foundation/api/revel/results.go
new file mode 100644 (file)
index 0000000..59d0507
--- /dev/null
@@ -0,0 +1,513 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "bytes"
+       "encoding/json"
+       "encoding/xml"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "net/http"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+type Result interface {
+       Apply(req *Request, resp *Response)
+}
+
+// ErrorResult structure used to handles all kinds of error codes (500, 404, ..).
+// It renders the relevant error page (errors/CODE.format, e.g. errors/500.json).
+// If RunMode is "dev", this results in a friendly error page.
+type ErrorResult struct {
+       ViewArgs map[string]interface{}
+       Error    error
+}
+
+var resultsLog = RevelLog.New("section", "results")
+
+func (r ErrorResult) Apply(req *Request, resp *Response) {
+       format := req.Format
+       status := resp.Status
+       if status == 0 {
+               status = http.StatusInternalServerError
+       }
+
+       contentType := ContentTypeByFilename("xxx." + format)
+       if contentType == DefaultFileContentType {
+               contentType = "text/plain"
+       }
+       lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
+       // Get the error template.
+       var err error
+       templatePath := fmt.Sprintf("errors/%d.%s", status, format)
+       tmpl, err := MainTemplateLoader.TemplateLang(templatePath, lang)
+
+       // This func shows a plaintext error message, in case the template rendering
+       // doesn't work.
+       showPlaintext := func(err error) {
+               PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+
+                       "Additionally, an error occurred when rendering the error page:\n%s",
+                       r.Error, err)}.Apply(req, resp)
+       }
+
+       if tmpl == nil {
+               if err == nil {
+                       err = fmt.Errorf("Couldn't find template %s", templatePath)
+               }
+               templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
+               showPlaintext(err)
+               return
+       }
+
+       // If it's not a revel error, wrap it in one.
+       var revelError *Error
+       switch e := r.Error.(type) {
+       case *Error:
+               revelError = e
+       case error:
+               revelError = &Error{
+                       Title:       "Server Error",
+                       Description: e.Error(),
+               }
+       }
+
+       if revelError == nil {
+               panic("no error provided")
+       }
+
+       if r.ViewArgs == nil {
+               r.ViewArgs = make(map[string]interface{})
+       }
+       r.ViewArgs["RunMode"] = RunMode
+       r.ViewArgs["DevMode"] = DevMode
+       r.ViewArgs["Error"] = revelError
+       r.ViewArgs["Router"] = MainRouter
+
+       resultsLog.Info("Rendering error template", "template", templatePath, "error", revelError)
+
+       // Render it.
+       var b bytes.Buffer
+       err = tmpl.Render(&b, r.ViewArgs)
+
+       // If there was an error, print it in plain text.
+       if err != nil {
+               templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
+               showPlaintext(err)
+               return
+       }
+
+       // need to check if we are on a websocket here
+       // net/http panics if we write to a hijacked connection
+       if req.Method == "WS" {
+               if err := req.WebSocket.MessageSendJSON(fmt.Sprint(revelError)); err != nil {
+                       resultsLog.Error("Apply: Send failed", "error", err)
+               }
+       } else {
+               resp.WriteHeader(status, contentType)
+               if _, err := b.WriteTo(resp.GetWriter()); err != nil {
+                       resultsLog.Error("Apply: Response WriteTo failed:", "error", err)
+               }
+       }
+
+}
+
+type PlaintextErrorResult struct {
+       Error error
+}
+
+// Apply method is used when the template loader or error template is not available.
+func (r PlaintextErrorResult) Apply(req *Request, resp *Response) {
+       resp.WriteHeader(http.StatusInternalServerError, "text/plain; charset=utf-8")
+       if _, err := resp.GetWriter().Write([]byte(r.Error.Error())); err != nil {
+               resultsLog.Error("Apply: Write error:", "error", err)
+       }
+}
+
+// RenderTemplateResult action methods returns this result to request
+// a template be rendered.
+type RenderTemplateResult struct {
+       Template Template
+       ViewArgs map[string]interface{}
+}
+
+func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
+       // Handle panics when rendering templates.
+       defer func() {
+               if err := recover(); err != nil {
+                       resultsLog.Error("Apply: panic recovery", "error", err)
+                       PlaintextErrorResult{fmt.Errorf("Template Execution Panic in %s:\n%s",
+                               r.Template.Name(), err)}.Apply(req, resp)
+               }
+       }()
+
+       chunked := Config.BoolDefault("results.chunked", false)
+
+       // If it's a HEAD request, throw away the bytes.
+       out := io.Writer(resp.GetWriter())
+       if req.Method == "HEAD" {
+               out = ioutil.Discard
+       }
+
+       // In a prod mode, write the status, render, and hope for the best.
+       // (In a dev mode, always render to a temporary buffer first to avoid having
+       // error pages distorted by HTML already written)
+       if chunked && !DevMode {
+               resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
+               if err := r.renderOutput(out); err != nil {
+                       r.renderError(err, req, resp)
+               }
+               return
+       }
+
+       // Render the template into a temporary buffer, to see if there was an error
+       // rendering the template.  If not, then copy it into the response buffer.
+       // Otherwise, template render errors may result in unpredictable HTML (and
+       // would carry a 200 status code)
+       b, err := r.ToBytes()
+       if err != nil {
+               r.renderError(err, req, resp)
+               return
+       }
+
+       if !chunked {
+               resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
+       }
+       resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
+       if _, err := b.WriteTo(out); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+}
+
+// Return a byte array and or an error object if the template failed to render
+func (r *RenderTemplateResult) ToBytes() (b *bytes.Buffer, err error) {
+       defer func() {
+               if rerr := recover(); rerr != nil {
+                       resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr)
+                       err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr)
+               }
+       }()
+       b = &bytes.Buffer{}
+       if err = r.renderOutput(b); err == nil {
+               if Config.BoolDefault("results.trim.html", false) {
+                       b = r.compressHtml(b)
+               }
+       }
+       return
+}
+
+// Output the template to the writer, catch any panics and return as an error
+func (r *RenderTemplateResult) renderOutput(wr io.Writer) (err error) {
+       defer func() {
+               if rerr := recover(); rerr != nil {
+                       resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr)
+                       err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr)
+               }
+       }()
+       err = r.Template.Render(wr, r.ViewArgs)
+       return
+}
+
+// Trimming the HTML will do the following:
+// * Remove all leading & trailing whitespace on every line
+// * Remove all empty lines
+// * Attempt to keep formatting inside <pre></pre> tags
+//
+// This is safe unless white-space: pre; is used in css for formatting.
+// Since there is no way to detect that, you will have to keep trimming off in these cases.
+func (r *RenderTemplateResult) compressHtml(b *bytes.Buffer) (b2 *bytes.Buffer) {
+
+       // Allocate length of original buffer, so we can write everything without allocating again
+       b2.Grow(b.Len())
+       insidePre := false
+       for {
+               text, err := b.ReadString('\n')
+               // Convert to lower case for finding <pre> tags.
+               tl := strings.ToLower(text)
+               if strings.Contains(tl, "<pre>") {
+                       insidePre = true
+               }
+               // Trim if not inside a <pre> statement
+               if !insidePre {
+                       // Cut trailing/leading whitespace
+                       text = strings.Trim(text, " \t\r\n")
+                       if len(text) > 0 {
+                               if _, err = b2.WriteString(text); err != nil {
+                                       resultsLog.Error("Apply: ", "error", err)
+                               }
+                               if _, err = b2.WriteString("\n"); err != nil {
+                                       resultsLog.Error("Apply: ", "error", err)
+                               }
+                       }
+               } else {
+                       if _, err = b2.WriteString(text); err != nil {
+                               resultsLog.Error("Apply: ", "error", err)
+                       }
+               }
+               if strings.Contains(tl, "</pre>") {
+                       insidePre = false
+               }
+               // We are finished
+               if err != nil {
+                       break
+               }
+       }
+
+       return
+}
+
+// Render the error in the response
+func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) {
+       compileError, found := err.(*Error)
+       if !found {
+               var templateContent []string
+               templateName, line, description := ParseTemplateError(err)
+               if templateName == "" {
+                       templateLog.Info("Cannot determine template name to render error", "error", err)
+                       templateName = r.Template.Name()
+                       templateContent = r.Template.Content()
+
+               } else {
+                       lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
+                       if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil {
+                               templateContent = tmpl.Content()
+                       } else {
+                               templateLog.Info("Unable to retreive template ", "error", err)
+                       }
+               }
+               compileError = &Error{
+                       Title:       "Template Execution Error",
+                       Path:        templateName,
+                       Description: description,
+                       Line:        line,
+                       SourceLines: templateContent,
+               }
+       }
+       resp.Status = 500
+       resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description)
+       ErrorResult{r.ViewArgs, compileError}.Apply(req, resp)
+}
+
+type RenderHTMLResult struct {
+       html string
+}
+
+func (r RenderHTMLResult) Apply(req *Request, resp *Response) {
+       resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
+       if _, err := resp.GetWriter().Write([]byte(r.html)); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+}
+
+type RenderJSONResult struct {
+       obj      interface{}
+       callback string
+}
+
+func (r RenderJSONResult) Apply(req *Request, resp *Response) {
+       var b []byte
+       var err error
+       if Config.BoolDefault("results.pretty", false) {
+               b, err = json.MarshalIndent(r.obj, "", "  ")
+       } else {
+               b, err = json.Marshal(r.obj)
+       }
+
+       if err != nil {
+               ErrorResult{Error: err}.Apply(req, resp)
+               return
+       }
+
+       if r.callback == "" {
+               resp.WriteHeader(http.StatusOK, "application/json; charset=utf-8")
+               if _, err = resp.GetWriter().Write(b); err != nil {
+                       resultsLog.Error("Apply: Response write failed:", "error", err)
+               }
+               return
+       }
+
+       resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8")
+       if _, err = resp.GetWriter().Write([]byte(r.callback + "(")); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+       if _, err = resp.GetWriter().Write(b); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+       if _, err = resp.GetWriter().Write([]byte(");")); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+}
+
+type RenderXMLResult struct {
+       obj interface{}
+}
+
+func (r RenderXMLResult) Apply(req *Request, resp *Response) {
+       var b []byte
+       var err error
+       if Config.BoolDefault("results.pretty", false) {
+               b, err = xml.MarshalIndent(r.obj, "", "  ")
+       } else {
+               b, err = xml.Marshal(r.obj)
+       }
+
+       if err != nil {
+               ErrorResult{Error: err}.Apply(req, resp)
+               return
+       }
+
+       resp.WriteHeader(http.StatusOK, "application/xml; charset=utf-8")
+       if _, err = resp.GetWriter().Write(b); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+}
+
+type RenderTextResult struct {
+       text string
+}
+
+func (r RenderTextResult) Apply(req *Request, resp *Response) {
+       resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8")
+       if _, err := resp.GetWriter().Write([]byte(r.text)); err != nil {
+               resultsLog.Error("Apply: Response write failed", "error", err)
+       }
+}
+
+type ContentDisposition string
+
+var (
+       NoDisposition ContentDisposition = ""
+       Attachment    ContentDisposition = "attachment"
+       Inline        ContentDisposition = "inline"
+)
+
+type BinaryResult struct {
+       Reader   io.Reader
+       Name     string
+       Length   int64
+       Delivery ContentDisposition
+       ModTime  time.Time
+}
+
+func (r *BinaryResult) Apply(req *Request, resp *Response) {
+       if r.Delivery != NoDisposition {
+               disposition := string(r.Delivery)
+               if r.Name != "" {
+                       disposition += fmt.Sprintf(`; filename="%s"`, r.Name)
+               }
+               resp.Out.internalHeader.Set("Content-Disposition", disposition)
+       }
+       if resp.ContentType != "" {
+               resp.Out.internalHeader.Set("Content-Type", resp.ContentType)
+       } else {
+               contentType := ContentTypeByFilename(r.Name)
+               resp.Out.internalHeader.Set("Content-Type", contentType)
+       }
+       if content, ok := r.Reader.(io.ReadSeeker); ok && r.Length < 0 {
+               // get the size from the stream
+               // go1.6 compatibility change, go1.6 does not define constants io.SeekStart
+               //if size, err := content.Seek(0, io.SeekEnd); err == nil {
+               //      if _, err = content.Seek(0, io.SeekStart); err == nil {
+               if size, err := content.Seek(0, 2); err == nil {
+                       if _, err = content.Seek(0, 0); err == nil {
+                               r.Length = size
+                       }
+               }
+       }
+
+       // Write stream writes the status code to the header as well
+       if ws := resp.GetStreamWriter(); ws != nil {
+               if err := ws.WriteStream(r.Name, r.Length, r.ModTime, r.Reader); err != nil {
+                       resultsLog.Error("Apply: Response write failed", "error", err)
+               }
+       }
+
+       // Close the Reader if we can
+       if v, ok := r.Reader.(io.Closer); ok {
+               _ = v.Close()
+       }
+}
+
+type RedirectToURLResult struct {
+       url string
+}
+
+func (r *RedirectToURLResult) Apply(req *Request, resp *Response) {
+       resp.Out.internalHeader.Set("Location", r.url)
+       resp.WriteHeader(http.StatusFound, "")
+}
+
+type RedirectToActionResult struct {
+       val  interface{}
+       args []interface{}
+}
+
+func (r *RedirectToActionResult) Apply(req *Request, resp *Response) {
+       url, err := getRedirectURL(r.val, r.args)
+       if err != nil {
+               resultsLog.Error("Apply: Couldn't resolve redirect", "error", err)
+               ErrorResult{Error: err}.Apply(req, resp)
+               return
+       }
+       resp.Out.internalHeader.Set("Location", url)
+       resp.WriteHeader(http.StatusFound, "")
+}
+
+func getRedirectURL(item interface{}, args []interface{}) (string, error) {
+       // Handle strings
+       if url, ok := item.(string); ok {
+               return url, nil
+       }
+
+       // Handle funcs
+       val := reflect.ValueOf(item)
+       typ := reflect.TypeOf(item)
+       if typ.Kind() == reflect.Func && typ.NumIn() > 0 {
+               // Get the Controller Method
+               recvType := typ.In(0)
+               method := FindMethod(recvType, val)
+               if method == nil {
+                       return "", errors.New("couldn't find method")
+               }
+
+               // Construct the action string (e.g. "Controller.Method")
+               if recvType.Kind() == reflect.Ptr {
+                       recvType = recvType.Elem()
+               }
+               module := ModuleFromPath(recvType.PkgPath(), true)
+               action := module.Namespace() + recvType.Name() + "." + method.Name
+               // Fetch the action path to get the defaults
+               pathData, found := splitActionPath(nil, action, true)
+               if !found {
+                       return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action)
+               }
+
+               // Build the map for the router to reverse
+               // Unbind the arguments.
+               argsByName := make(map[string]string)
+               // Bind any static args first
+               fixedParams := len(pathData.FixedParamsByName)
+               methodType := pathData.TypeOfController.Method(pathData.MethodName)
+
+               for i, argValue := range args {
+                       Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue)
+               }
+
+               actionDef := MainRouter.Reverse(action, argsByName)
+               if actionDef == nil {
+                       return "", errors.New("no route for action " + action)
+               }
+
+               return actionDef.String(), nil
+       }
+
+       // Out of guesses
+       return "", errors.New("didn't recognize type: " + typ.String())
+}
diff --git a/src/foundation/api/revel/results_test.go b/src/foundation/api/revel/results_test.go
new file mode 100644 (file)
index 0000000..ce375d1
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http/httptest"
+       "strings"
+       "testing"
+)
+
+// Added test case for redirection testing for strings
+func TestRedirect(t *testing.T) {
+       startFakeBookingApp()
+       redirect := RedirectToURLResult{fmt.Sprintf("/hotels/index/foo")}
+       resp := httptest.NewRecorder()
+       c := NewTestController(resp, showRequest)
+       redirect.Apply(c.Request, c.Response)
+       if resp.Header().Get("Location") != "/hotels/index/foo" {
+               t.Errorf("Failed to set redirect header correctly. : %s", resp.Header().Get("Location"))
+       }
+}
+
+// Test that the render response is as expected.
+func TestBenchmarkRender(t *testing.T) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               t.Errorf("SetAction failed: %s", err)
+       }
+       result := Hotels{c}.Show(3)
+       result.Apply(c.Request, c.Response)
+       if !strings.Contains(resp.Body.String(), "300 Main St.") {
+               t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body)
+       }
+}
+
+func BenchmarkRenderChunked(b *testing.B) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       resp.Body = nil
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               b.Errorf("SetAction failed: %s", err)
+       }
+       Config.SetOption("results.chunked", "true")
+       b.ResetTimer()
+
+       hotels := Hotels{c}
+       for i := 0; i < b.N; i++ {
+               hotels.Show(3).Apply(c.Request, c.Response)
+       }
+}
+
+func BenchmarkRenderNotChunked(b *testing.B) {
+       startFakeBookingApp()
+       resp := httptest.NewRecorder()
+       resp.Body = nil
+       c := NewTestController(resp, showRequest)
+       if err := c.SetAction("Hotels", "Show"); err != nil {
+               b.Errorf("SetAction failed: %s", err)
+       }
+       Config.SetOption("results.chunked", "false")
+       b.ResetTimer()
+
+       hotels := Hotels{c}
+       for i := 0; i < b.N; i++ {
+               hotels.Show(3).Apply(c.Request, c.Response)
+       }
+}
diff --git a/src/foundation/api/revel/revel.go b/src/foundation/api/revel/revel.go
new file mode 100644 (file)
index 0000000..27ac6ef
--- /dev/null
@@ -0,0 +1,301 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "go/build"
+       "log"
+       "path/filepath"
+       "strings"
+
+       "encoding/json"
+       "fmt"
+       "github.com/revel/config"
+       "github.com/revel/revel/logger"
+       "github.com/revel/revel/model"
+)
+
+const (
+       // RevelImportPath Revel framework import path
+       RevelImportPath = "github.com/revel/revel"
+)
+
+const (
+       TEST_MODE_FLAG   = "testModeFlag"
+       SPECIAL_USE_FLAG = "specialUseFlag"
+)
+
+// App details
+var (
+       RevelConfig *model.RevelContainer
+       AppName    string // e.g. "sample"
+       AppRoot    string // e.g. "/app1"
+       BasePath   string // e.g. "$GOPATH/src/corp/sample"
+       AppPath    string // e.g. "$GOPATH/src/corp/sample/app"
+       ViewsPath  string // e.g. "$GOPATH/src/corp/sample/app/views"
+       ImportPath string // e.g. "corp/sample"
+       SourcePath string // e.g. "$GOPATH/src"
+
+       Config  *config.Context
+       RunMode string // Application-defined (by default, "dev" or "prod")
+       DevMode bool   // if true, RunMode is a development mode.
+
+       // Revel installation details
+       RevelPath string // e.g. "$GOPATH/src/github.com/revel/revel"
+
+       // Where to look for templates
+       // Ordered by priority. (Earlier paths take precedence over later paths.)
+       CodePaths     []string // Code base directories, for modules and app
+       TemplatePaths []string // Template path directories manually added
+
+       // ConfPaths where to look for configurations
+       // Config load order
+       // 1. framework (revel/conf/*)
+       // 2. application (conf/*)
+       // 3. user supplied configs (...) - User configs can override/add any from above
+       ConfPaths []string
+
+       // Server config.
+       //
+       // Alert: This is how the app is configured, which may be different from
+       // the current process reality.  For example, if the app is configured for
+       // port 9000, HTTPPort will always be 9000, even though in dev mode it is
+       // run on a random port and proxied.
+       HTTPPort    int    // e.g. 9000
+       HTTPAddr    string // e.g. "", "127.0.0.1"
+       HTTPSsl     bool   // e.g. true if using ssl
+       HTTPSslCert string // e.g. "/path/to/cert.pem"
+       HTTPSslKey  string // e.g. "/path/to/key.pem"
+
+       // All cookies dropped by the framework begin with this prefix.
+       CookiePrefix string
+       // Cookie domain
+       CookieDomain string
+       // Cookie flags
+       CookieSecure bool
+
+       // Revel request access log, not exposed from package.
+       // However output settings can be controlled from app.conf
+
+       // True when revel engine has been initialized (Init has returned)
+       Initialized bool
+
+       // Private
+       secretKey     []byte             // Key used to sign cookies. An empty key disables signing.
+       packaged      bool               // If true, this is running from a pre-built package.
+       initEventList = []EventHandler{} // Event handler list for receiving events
+)
+
+// Init initializes Revel -- it provides paths for getting around the app.
+//
+// Params:
+//   mode - the run mode, which determines which app.conf settings are used.
+//   importPath - the Go import path of the application.
+//   srcPath - the path to the source directory, containing Revel and the app.
+//     If not specified (""), then a functioning Go installation is required.
+func Init(inputmode, importPath, srcPath string) {
+       RevelConfig = &model.RevelContainer{}
+       // Ignore trailing slashes.
+       ImportPath = strings.TrimRight(importPath, "/")
+       SourcePath = srcPath
+
+       RunMode = updateLog(inputmode)
+
+       // If the SourcePath is not specified, find it using build.Import.
+       var revelSourcePath string // may be different from the app source path
+       if SourcePath == "" {
+               revelSourcePath, SourcePath = findSrcPaths(importPath)
+       } else {
+               // If the SourcePath was specified, assume both Revel and the app are within it.
+               SourcePath = filepath.Clean(SourcePath)
+               revelSourcePath = SourcePath
+               packaged = true
+       }
+
+       RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))
+       BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath))
+       AppPath = filepath.Join(BasePath, "app")
+       ViewsPath = filepath.Join(AppPath, "views")
+
+       CodePaths = []string{AppPath}
+
+       if ConfPaths == nil {
+               ConfPaths = []string{}
+       }
+
+       // Config load order
+       // 1. framework (revel/conf/*)
+       // 2. application (conf/*)
+       // 3. user supplied configs (...) - User configs can override/add any from above
+       ConfPaths = append(
+               []string{
+                       filepath.Join(RevelPath, "conf"),
+                       filepath.Join(BasePath, "conf"),
+               },
+               ConfPaths...)
+
+       TemplatePaths = []string{
+               ViewsPath,
+               filepath.Join(RevelPath, "templates"),
+       }
+
+       // Load app.conf
+       var err error
+       Config, err = config.LoadContext("app.conf", ConfPaths)
+       if err != nil || Config == nil {
+               RevelLog.Fatal("Failed to load app.conf:", "error", err)
+       }
+
+       // After application config is loaded update the logger
+       updateLog(inputmode)
+
+       // Configure properties from app.conf
+       DevMode = Config.BoolDefault("mode.dev", false)
+       HTTPPort = Config.IntDefault("http.port", 9000)
+       HTTPAddr = Config.StringDefault("http.addr", "")
+       HTTPSsl = Config.BoolDefault("http.ssl", false)
+       HTTPSslCert = Config.StringDefault("http.sslcert", "")
+       HTTPSslKey = Config.StringDefault("http.sslkey", "")
+       if HTTPSsl {
+               if HTTPSslCert == "" {
+                       RevelLog.Fatal("No http.sslcert provided.")
+               }
+               if HTTPSslKey == "" {
+                       RevelLog.Fatal("No http.sslkey provided.")
+               }
+       }
+
+       AppName = Config.StringDefault("app.name", "(not set)")
+       AppRoot = Config.StringDefault("app.root", "")
+       CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
+       CookieDomain = Config.StringDefault("cookie.domain", "")
+       CookieSecure = Config.BoolDefault("cookie.secure", HTTPSsl)
+       if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" {
+               SetSecretKey([]byte(secretStr))
+       }
+
+       RaiseEvent(REVEL_BEFORE_MODULES_LOADED, nil)
+       loadModules()
+       RaiseEvent(REVEL_AFTER_MODULES_LOADED, nil)
+
+       Initialized = true
+       RevelLog.Info("Initialized Revel", "Version", Version, "BuildDate", BuildDate, "MinimumGoVersion", MinimumGoVersion)
+}
+
+// The input mode can be as simple as "prod" or it can be a JSON string like
+// {"mode":"%s","testModeFlag":true}
+// When this function is called it returns the true "inputmode" extracted from the parameter
+// and it sets the log context appropriately
+func updateLog(inputmode string) (returnMode string) {
+       if inputmode == "" {
+               returnMode = config.DefaultSection
+               return
+       } else {
+               returnMode = inputmode
+       }
+
+       // Check to see if the mode is a json object
+       modemap := map[string]interface{}{}
+
+       var testModeFlag, specialUseFlag bool
+       if err := json.Unmarshal([]byte(inputmode), &modemap); err == nil {
+               returnMode = modemap["mode"].(string)
+               if testmode, found := modemap[TEST_MODE_FLAG]; found {
+                       testModeFlag, _ = testmode.(bool)
+               }
+               if specialUse, found := modemap[SPECIAL_USE_FLAG]; found {
+                       specialUseFlag, _ = specialUse.(bool)
+               }
+       }
+
+       var newContext *config.Context
+       // If the Config is nil, set the logger to minimal log messages by adding the option
+       if Config == nil {
+               newContext = config.NewContext()
+               newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(true))
+       } else {
+               // Ensure that the selected runmode appears in app.conf.
+               // If empty string is passed as the mode, treat it as "DEFAULT"
+               if !Config.HasSection(returnMode) {
+                       log.Fatalln("app.conf: No mode found:", returnMode)
+               }
+               Config.SetSection(returnMode)
+               newContext = Config
+       }
+
+       // Only set the testmode flag if it doesnt exist
+       if _, found := newContext.Bool(TEST_MODE_FLAG); !found {
+               newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(testModeFlag))
+       }
+       if _, found := newContext.Bool(SPECIAL_USE_FLAG); !found {
+               newContext.SetOption(SPECIAL_USE_FLAG, fmt.Sprint(specialUseFlag))
+       }
+
+       appHandle := logger.InitializeFromConfig(BasePath, newContext)
+
+       // Set all the log handlers
+       setAppLog(AppLog, appHandle)
+
+       return
+}
+
+// Set the secret key
+func SetSecretKey(newKey []byte) error {
+       secretKey = newKey
+       return nil
+}
+
+// ResolveImportPath returns the filesystem path for the given import path.
+// Returns an error if the import path could not be found.
+func ResolveImportPath(importPath string) (string, error) {
+       if packaged {
+               return filepath.Join(SourcePath, importPath), nil
+       }
+
+       modPkg, err := build.Import(importPath, RevelPath, build.FindOnly)
+       if err != nil {
+               return "", err
+       }
+       return modPkg.Dir, nil
+}
+
+// CheckInit method checks `revel.Initialized` if not initialized it panics
+func CheckInit() {
+       if !Initialized {
+               RevelLog.Panic("CheckInit: Revel has not been initialized!")
+       }
+}
+
+// findSrcPaths uses the "go/build" package to find the source root for Revel
+// and the app.
+func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) {
+       var (
+               gopaths = filepath.SplitList(build.Default.GOPATH)
+               goroot  = build.Default.GOROOT
+       )
+
+       if len(gopaths) == 0 {
+               RevelLog.Fatal("GOPATH environment variable is not set. " +
+                       "Please refer to http://golang.org/doc/code.html to configure your Go environment.")
+       }
+
+       if ContainsString(gopaths, goroot) {
+               RevelLog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
+                       "Please refer to http://golang.org/doc/code.html to configure your Go environment.",
+                       gopaths, goroot)
+       }
+
+       appPkg, err := build.Import(importPath, "", build.FindOnly)
+       if err != nil {
+               RevelLog.Panic("Failed to import "+importPath+" with error:", "error", err)
+       }
+
+       revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly)
+       if err != nil {
+               RevelLog.Fatal("Failed to find Revel with error:", "error", err)
+       }
+
+       return revelPkg.Dir[:len(revelPkg.Dir)-len(RevelImportPath)], appPkg.SrcRoot
+}
diff --git a/src/foundation/api/revel/revel_hooks.go b/src/foundation/api/revel/revel_hooks.go
new file mode 100644 (file)
index 0000000..9378166
--- /dev/null
@@ -0,0 +1,103 @@
+package revel
+
+import (
+       "sort"
+)
+
+// The list of startup hooks
+type (
+       // The startup hooks structure
+       RevelHook struct {
+               order int    // The order
+               f     func() // The function to call
+       }
+
+       RevelHooks []RevelHook
+)
+
+var (
+       // The local instance of the list
+       startupHooks RevelHooks
+
+       // The instance of the list
+       shutdownHooks RevelHooks
+)
+
+// Called to run the hooks
+func (r RevelHooks) Run() {
+       serverLogger.Infof("There is %d hooks need to run ...", len(r))
+       sort.Sort(r)
+       for i, hook := range r {
+               utilLog.Infof("Run the %d hook ...", i+1)
+               hook.f()
+       }
+}
+
+// Adds a new function hook, using the order
+func (r RevelHooks) Add(fn func(), order ...int) RevelHooks {
+       o := 1
+       if len(order) > 0 {
+               o = order[0]
+       }
+       return append(r, RevelHook{order: o, f: fn})
+}
+
+// Sorting function
+func (slice RevelHooks) Len() int {
+       return len(slice)
+}
+
+// Sorting function
+func (slice RevelHooks) Less(i, j int) bool {
+       return slice[i].order < slice[j].order
+}
+
+// Sorting function
+func (slice RevelHooks) Swap(i, j int) {
+       slice[i], slice[j] = slice[j], slice[i]
+}
+
+// OnAppStart registers a function to be run at app startup.
+//
+// The order you register the functions will be the order they are run.
+// You can think of it as a FIFO queue.
+// This process will happen after the config file is read
+// and before the server is listening for connections.
+//
+// Ideally, your application should have only one call to init() in the file init.go.
+// The reason being that the call order of multiple init() functions in
+// the same package is undefined.
+// Inside of init() call revel.OnAppStart() for each function you wish to register.
+//
+// Example:
+//
+//      // from: yourapp/app/controllers/somefile.go
+//      func InitDB() {
+//          // do DB connection stuff here
+//      }
+//
+//      func FillCache() {
+//          // fill a cache from DB
+//          // this depends on InitDB having been run
+//      }
+//
+//      // from: yourapp/app/init.go
+//      func init() {
+//          // set up filters...
+//
+//          // register startup functions
+//          revel.OnAppStart(InitDB)
+//          revel.OnAppStart(FillCache)
+//      }
+//
+// This can be useful when you need to establish connections to databases or third-party services,
+// setup app components, compile assets, or any thing you need to do between starting Revel and accepting connections.
+//
+func OnAppStart(f func(), order ...int) {
+       startupHooks = startupHooks.Add(f, order...)
+}
+
+// Called to add a new stop hook
+func OnAppStop(f func(), order ...int) {
+       shutdownHooks = shutdownHooks.Add(f, order...)
+}
diff --git a/src/foundation/api/revel/revel_test.go b/src/foundation/api/revel/revel_test.go
new file mode 100644 (file)
index 0000000..1f8c87a
--- /dev/null
@@ -0,0 +1,13 @@
+package revel
+
+import (
+       "net/http"
+)
+
+func NewTestController(w http.ResponseWriter, r *http.Request) *Controller {
+       context := NewGoContext(nil)
+       context.Request.SetRequest(r)
+       context.Response.SetResponse(w)
+       c := NewController(context)
+       return c
+}
diff --git a/src/foundation/api/revel/router.go b/src/foundation/api/revel/router.go
new file mode 100644 (file)
index 0000000..54fb6e2
--- /dev/null
@@ -0,0 +1,846 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "encoding/csv"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "net/url"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       "os"
+       "sync"
+
+       "github.com/revel/pathtree"
+       "github.com/revel/revel/logger"
+)
+
+const (
+       httpStatusCode = "404"
+)
+
+type Route struct {
+       ModuleSource        *Module         // Module name of route
+       Method              string          // e.g. GET
+       Path                string          // e.g. /app/:id
+       Action              string          // e.g. "Application.ShowApp", "404"
+       ControllerNamespace string          // e.g. "testmodule.",
+       ControllerName      string          // e.g. "Application", ""
+       MethodName          string          // e.g. "ShowApp", ""
+       FixedParams         []string        // e.g. "arg1","arg2","arg3" (CSV formatting)
+       TreePath            string          // e.g. "/GET/app/:id"
+       TypeOfController    *ControllerType // The controller type (if route is not wild carded)
+
+       routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
+       line       int    // e.g. 3
+}
+
+type RouteMatch struct {
+       Action           string // e.g. 404
+       ControllerName   string // e.g. Application
+       MethodName       string // e.g. ShowApp
+       FixedParams      []string
+       Params           map[string][]string // e.g. {id: 123}
+       TypeOfController *ControllerType     // The controller type
+       ModuleSource     *Module             // The module
+}
+
+type ActionPathData struct {
+       Key                 string            // The unique key
+       ControllerNamespace string            // The controller namespace
+       ControllerName      string            // The controller name
+       MethodName          string            // The method name
+       Action              string            // The action
+       ModuleSource        *Module           // The module
+       Route               *Route            // The route
+       FixedParamsByName   map[string]string // The fixed parameters
+       TypeOfController    *ControllerType   // The controller type
+}
+
+var (
+       // Used to store decoded action path mappings
+       actionPathCacheMap = map[string]*ActionPathData{}
+       // Used to prevent concurrent writes to map
+       actionPathCacheLock = sync.Mutex{}
+       // The path returned if not found
+       notFound = &RouteMatch{Action: "404"}
+)
+
+var routerLog = RevelLog.New("section", "router")
+
+func init() {
+       AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+               // Add in an
+               if typeOf == ROUTE_REFRESH_REQUESTED {
+                       // Clear the actionPathCacheMap cache
+                       actionPathCacheLock.Lock()
+                       defer actionPathCacheLock.Unlock()
+                       actionPathCacheMap = map[string]*ActionPathData{}
+               }
+               return
+       })
+}
+
+// NewRoute prepares the route to be used in matching.
+func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
+       // Handle fixed arguments
+       argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource)))
+       csvReader := csv.NewReader(argsReader)
+       csvReader.TrimLeadingSpace = true
+       fargs, err := csvReader.Read()
+       if err != nil && err != io.EOF {
+               routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs)
+       }
+
+       r = &Route{
+               ModuleSource: moduleSource,
+               Method:       strings.ToUpper(method),
+               Path:         path,
+               Action:       string(namespaceReplace([]byte(action), moduleSource)),
+               FixedParams:  fargs,
+               TreePath:     treePath(strings.ToUpper(method), path),
+               routesPath:   routesPath,
+               line:         line,
+       }
+
+       // URL pattern
+       if !strings.HasPrefix(r.Path, "/") {
+               routerLog.Error("NewRoute: Absolute URL required.")
+               return
+       }
+
+       // Ignore the not found status code
+       if action != httpStatusCode {
+               routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action)
+               pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false)
+               if found {
+                       if pathData.TypeOfController != nil {
+                               // Assign controller type to avoid looking it up based on name
+                               r.TypeOfController = pathData.TypeOfController
+                               // Create the fixed parameters
+                               if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 {
+                                       methodType := pathData.TypeOfController.Method(pathData.MethodName)
+                                       if methodType != nil {
+                                               pathData.FixedParamsByName = make(map[string]string, l)
+                                               for i, argValue := range pathData.Route.FixedParams {
+                                                       Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue)
+                                               }
+                                       } else {
+                                               routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName)
+                                       }
+                               }
+                       }
+                       r.ControllerNamespace = pathData.ControllerNamespace
+                       r.ControllerName = pathData.ControllerName
+                       r.ModuleSource = pathData.ModuleSource
+                       r.MethodName = pathData.MethodName
+
+                       // The same action path could be used for multiple routes (like the Static.Serve)
+               } else {
+                       routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap)
+               }
+       }
+       return
+}
+
+func (route *Route) ActionPath() string {
+       return route.ModuleSource.Namespace() + route.ControllerName
+}
+
+func treePath(method, path string) string {
+       if method == "*" {
+               method = ":METHOD"
+       }
+       return "/" + method + path
+}
+
+type Router struct {
+       Routes []*Route
+       Tree   *pathtree.Node
+       Module string // The module the route is associated with
+       path   string // path to the routes file
+}
+
+func (router *Router) Route(req *Request) (routeMatch *RouteMatch) {
+       // Override method if set in header
+       if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
+               req.Method = method
+       }
+
+       leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath()))
+       if leaf == nil {
+               return nil
+       }
+
+       // Create a map of the route parameters.
+       var params url.Values
+       if len(expansions) > 0 {
+               params = make(url.Values)
+               for i, v := range expansions {
+                       params[leaf.Wildcards[i]] = []string{v}
+               }
+       }
+       var route *Route
+       var controllerName, methodName string
+
+       // The leaf value is now a list of possible routes to match, only a controller
+       routeList := leaf.Value.([]*Route)
+       var typeOfController *ControllerType
+
+       //INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList))
+       for index := range routeList {
+               route = routeList[index]
+               methodName = route.MethodName
+
+               // Special handling for explicit 404's.
+               if route.Action == httpStatusCode {
+                       route = nil
+                       break
+               }
+
+               // If wildcard match on method name use the method name from the params
+               if methodName[0] == ':' {
+                       if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 {
+                               methodName = strings.ToLower(methodKey[0])
+                       } else {
+                               routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName)
+                       }
+               }
+
+               // If the action is variablized, replace into it with the captured args.
+               controllerName = route.ControllerName
+               if controllerName[0] == ':' {
+                       controllerName = strings.ToLower(params[controllerName[1:]][0])
+                       if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil {
+                               break
+                       }
+               } else {
+                       typeOfController = route.TypeOfController
+                       break
+               }
+               route = nil
+       }
+
+       if route == nil {
+               routeMatch = notFound
+       } else {
+
+               routeMatch = &RouteMatch{
+                       ControllerName:   route.ControllerNamespace + controllerName,
+                       MethodName:       methodName,
+                       Params:           params,
+                       FixedParams:      route.FixedParams,
+                       TypeOfController: typeOfController,
+                       ModuleSource:     route.ModuleSource,
+               }
+       }
+
+       return
+}
+
+// Refresh re-reads the routes file and re-calculates the routing table.
+// Returns an error if a specified action could not be found.
+func (router *Router) Refresh() (err *Error) {
+       RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
+       router.Routes, err = parseRoutesFile(appModule, router.path, "", true)
+       RaiseEvent(ROUTE_REFRESH_COMPLETED, nil)
+       if err != nil {
+               return
+       }
+       err = router.updateTree()
+       return
+}
+
+func (router *Router) updateTree() *Error {
+       router.Tree = pathtree.New()
+       pathMap := map[string][]*Route{}
+
+       allPathsOrdered := []string{}
+       // It is possible for some route paths to overlap
+       // based on wildcard matches,
+       // TODO when pathtree is fixed (made to be smart enough to not require a predefined intake order) keeping the routes in order is not necessary
+       for _, route := range router.Routes {
+               if _, found := pathMap[route.TreePath]; !found {
+                       pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+                       allPathsOrdered = append(allPathsOrdered, route.TreePath)
+               } else {
+                       pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+               }
+       }
+       for _, path := range allPathsOrdered {
+               routeList := pathMap[path]
+               err := router.Tree.Add(path, routeList)
+
+               // Allow GETs to respond to HEAD requests.
+               if err == nil && routeList[0].Method == "GET" {
+                       err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList)
+               }
+
+               // Error adding a route to the pathtree.
+               if err != nil {
+                       return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line)
+               }
+       }
+       return nil
+}
+
+// Returns the controller namespace and name, action and module if found from the actionPath specified
+func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) {
+       actionPath = strings.ToLower(actionPath)
+       if pathData, found = actionPathCacheMap[actionPath]; found && useCache {
+               return
+       }
+       var (
+               controllerNamespace, controllerName, methodName, action string
+               foundModuleSource                                       *Module
+               typeOfController                                        *ControllerType
+               log                                                     = routerLog.New("actionPath", actionPath)
+       )
+       actionSplit := strings.Split(actionPath, ".")
+       if actionPathData != nil {
+               foundModuleSource = actionPathData.ModuleSource
+       }
+       if len(actionSplit) == 2 {
+               controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1])
+               if i := strings.Index(methodName, "("); i > 0 {
+                       methodName = methodName[:i]
+               }
+               log = log.New("controller", controllerName, "method", methodName)
+               log.Debug("splitActionPath: Check for namespace")
+               if i := strings.Index(controllerName, namespaceSeperator); i > -1 {
+                       controllerNamespace = controllerName[:i+1]
+                       if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found {
+                               log.Debug("Found module namespace")
+                               foundModuleSource = moduleSource
+                               controllerNamespace = moduleSource.Namespace()
+                       } else {
+                               log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath)
+                       }
+                       controllerName = controllerName[i+1:]
+                       // Check for the type of controller
+                       typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+                       found = typeOfController != nil
+               } else if controllerName[0] != ':' {
+                       // First attempt to find the controller in the module source
+                       if foundModuleSource != nil {
+                               typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+                               if typeOfController != nil {
+                                       controllerNamespace = typeOfController.Namespace
+                               }
+                       }
+                       log.Info("Found controller for path", "controllerType", typeOfController)
+
+                       if typeOfController == nil {
+                               // Check to see if we can determine the controller from only the controller name
+                               // an actionPath without a moduleSource will only come from
+                               // Scan through the controllers
+                               matchName := controllerName
+                               for key, controller := range controllers {
+                                       // Strip away the namespace from the controller. to be match
+                                       regularName := key
+                                       if i := strings.Index(key, namespaceSeperator); i > -1 {
+                                               regularName = regularName[i+1:]
+                                       }
+                                       if regularName == matchName {
+                                               // Found controller match
+                                               typeOfController = controller
+                                               controllerNamespace = typeOfController.Namespace
+                                               controllerName = typeOfController.ShortName()
+                                               foundModuleSource = typeOfController.ModuleSource
+                                               found = true
+                                               break
+                                       }
+                               }
+                       } else {
+                               found = true
+                       }
+               } else {
+                       // If wildcard assign the route the controller namespace found
+                       controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator
+                       foundModuleSource = actionPathData.ModuleSource
+                       found = true
+               }
+               action = actionSplit[1]
+       } else {
+               foundPaths := ""
+               for path := range actionPathCacheMap {
+                       foundPaths += path + ","
+               }
+               log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths)
+               found = false
+       }
+
+       // Make sure no concurrent map writes occur
+       if found {
+               actionPathCacheLock.Lock()
+               defer actionPathCacheLock.Unlock()
+               if actionPathData != nil {
+                       actionPathData.ControllerNamespace = controllerNamespace
+                       actionPathData.ControllerName = controllerName
+                       actionPathData.MethodName = methodName
+                       actionPathData.Action = action
+                       actionPathData.ModuleSource = foundModuleSource
+                       actionPathData.TypeOfController = typeOfController
+               } else {
+                       actionPathData = &ActionPathData{
+                               ControllerNamespace: controllerNamespace,
+                               ControllerName:      controllerName,
+                               MethodName:          methodName,
+                               Action:              action,
+                               ModuleSource:        foundModuleSource,
+                               TypeOfController:    typeOfController,
+                       }
+               }
+               actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "")
+               if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' {
+                       log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers)
+               }
+
+               pathData = actionPathData
+               if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 {
+                       // If there are fixed params on the route then add them to the path
+                       // This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable
+                       // for example
+                       // GET   /test/                     Application.Index("Test", "Test2")
+                       // {{url "Application.Index(test,test)" }}
+                       // should be parseable
+                       actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")"
+               }
+               if actionPathData.Route != nil {
+                       log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", actionPath, actionPathData.Route)
+               }
+               pathData.Key = actionPath
+               actionPathCacheMap[actionPath] = pathData
+               if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil {
+                       actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData
+                       log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route)
+               }
+       }
+       return
+}
+
+// parseRoutesFile reads the given routes file and returns the contained routes.
+func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
+       contentBytes, err := ioutil.ReadFile(routesPath)
+       if err != nil {
+               return nil, &Error{
+                       Title:       "Failed to load routes file",
+                       Description: err.Error(),
+               }
+       }
+       return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate)
+}
+
+// parseRoutes reads the content of a routes file into the routing table.
+func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
+       var routes []*Route
+
+       // For each line..
+       for n, line := range strings.Split(content, "\n") {
+               line = strings.TrimSpace(line)
+               if len(line) == 0 || line[0] == '#' {
+                       continue
+               }
+
+               const modulePrefix = "module:"
+
+               // Handle included routes from modules.
+               // e.g. "module:testrunner" imports all routes from that module.
+               if strings.HasPrefix(line, modulePrefix) {
+                       moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate)
+                       if err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+                       routes = append(routes, moduleRoutes...)
+                       continue
+               }
+
+               // A single route
+               method, path, action, fixedArgs, found := parseRouteLine(line)
+               if !found {
+                       continue
+               }
+
+               // this will avoid accidental double forward slashes in a route.
+               // this also avoids pathtree freaking out and causing a runtime panic
+               // because of the double slashes
+               if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
+                       joinedPath = joinedPath[0 : len(joinedPath)-1]
+               }
+               path = strings.Join([]string{AppRoot, joinedPath, path}, "")
+
+               // This will import the module routes under the path described in the
+               // routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all
+               // routes' paths will have the path /jobs prepended to them.
+               // See #282 for more info
+               if method == "*" && strings.HasPrefix(action, modulePrefix) {
+                       moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate)
+                       if err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+                       routes = append(routes, moduleRoutes...)
+                       continue
+               }
+
+               route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n)
+               routes = append(routes, route)
+
+               if validate {
+                       if err := validateRoute(route); err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+               }
+       }
+
+       return routes, nil
+}
+
+// validateRoute checks that every specified action exists.
+func validateRoute(route *Route) error {
+       // Skip 404s
+       if route.Action == httpStatusCode {
+               return nil
+       }
+
+       // Skip variable routes.
+       if route.ControllerName[0] == ':' || route.MethodName[0] == ':' {
+               return nil
+       }
+
+       // Precheck to see if controller exists
+       if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found {
+               // Scan through controllers to find module
+               for _, c := range controllers {
+                       controllerName := strings.ToLower(c.Type.Name())
+                       if controllerName == route.ControllerName {
+                               route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator
+                               routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path)
+                       }
+               }
+       }
+
+       // TODO need to check later
+       // does it do only validation or validation and instantiate the controller.
+       var c Controller
+       return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController)
+}
+
+// routeError adds context to a simple error message.
+func routeError(err error, routesPath, content string, n int) *Error {
+       if revelError, ok := err.(*Error); ok {
+               return revelError
+       }
+       // Load the route file content if necessary
+       if content == "" {
+               if contentBytes, er := ioutil.ReadFile(routesPath); er != nil {
+                       routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er)
+               } else {
+                       content = string(contentBytes)
+               }
+       }
+       return &Error{
+               Title:       "Route validation error",
+               Description: err.Error(),
+               Path:        routesPath,
+               Line:        n + 1,
+               SourceLines: strings.Split(content, "\n"),
+               Stack:       fmt.Sprintf("%s", logger.NewCallStack()),
+       }
+}
+
+// getModuleRoutes loads the routes file for the given module and returns the
+// list of routes.
+func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) {
+       // Look up the module.  It may be not found due to the common case of e.g. the
+       // testrunner module being active only in dev mode.
+       module, found := ModuleByName(moduleName)
+       if !found {
+               routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName)
+               return nil, nil
+       }
+       routePath := filepath.Join(module.Path, "conf", "routes")
+       if _, e := os.Stat(routePath); e == nil {
+               routes, err = parseRoutesFile(module, routePath, joinedPath, validate)
+       }
+       if err == nil {
+               for _, route := range routes {
+                       route.ModuleSource = module
+               }
+       }
+
+       return routes, err
+}
+
+// Groups:
+// 1: method
+// 4: path
+// 5: action
+// 6: fixedargs
+var routePattern = regexp.MustCompile(
+       "(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" +
+               "[(]?([^)]*)(\\))?[ \t]+" +
+               "(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
+               `\(?([^)]*)\)?[ \t]*$`)
+
+func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
+       matches := routePattern.FindStringSubmatch(line)
+       if matches == nil {
+               return
+       }
+       method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
+       found = true
+       return
+}
+
+func NewRouter(routesPath string) *Router {
+       return &Router{
+               Tree: pathtree.New(),
+               path: routesPath,
+       }
+}
+
+type ActionDefinition struct {
+       Host, Method, URL, Action string
+       Star                      bool
+       Args                      map[string]string
+}
+
+func (a *ActionDefinition) String() string {
+       return a.URL
+}
+
+func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) {
+       log := routerLog.New("action", action)
+       pathData, found := splitActionPath(nil, action, true)
+       if !found {
+               routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
+               return nil
+       }
+
+       log.Debug("Checking for route", "pathdataRoute", pathData.Route)
+       if pathData.Route == nil {
+               var possibleRoute *Route
+               // If the route is nil then we need to go through the routes to find the first matching route
+               // from this controllers namespace, this is likely a wildcard route match
+               for _, route := range router.Routes {
+                       // Skip routes that are not wild card or empty
+                       if route.ControllerName == "" || route.MethodName == "" {
+                               continue
+                       }
+                       if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' {
+                               // Wildcard match in same module space
+                               pathData.Route = route
+                               break
+                       } else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName &&
+                               (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+                               // Action path match
+                               pathData.Route = route
+                               break
+                       } else if route.ControllerName == pathData.ControllerName &&
+                               (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+                               // Controller name match
+                               possibleRoute = route
+                       }
+               }
+               if pathData.Route == nil && possibleRoute != nil {
+                       pathData.Route = possibleRoute
+                       routerLog.Warnf("Reverse: For a url reverse a match was based on  %s matched path to route %#v ", action, possibleRoute)
+               }
+               if pathData.Route != nil {
+                       routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route)
+               }
+       }
+
+       // Likely unknown route because of a wildcard, perform manual lookup
+       if pathData.Route != nil {
+               route := pathData.Route
+
+               // If the controller or method are wildcards we need to populate the argValues
+               controllerWildcard := route.ControllerName[0] == ':'
+               methodWildcard := route.MethodName[0] == ':'
+
+               // populate route arguments with the names
+               if controllerWildcard {
+                       argValues[route.ControllerName[1:]] = pathData.ControllerName
+               }
+               if methodWildcard {
+                       argValues[route.MethodName[1:]] = pathData.MethodName
+               }
+               // In theory all routes should be defined and pre-populated, the route controllers may not be though
+               // with wildcard routes
+               if pathData.TypeOfController == nil {
+                       if controllerWildcard || methodWildcard {
+                               if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil {
+                                       // Wildcard match boundary
+                                       pathData.TypeOfController = controller
+                                       // See if the path exists in the module based
+                               } else {
+                                       routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+                                       return
+                               }
+                       }
+               }
+
+               if pathData.TypeOfController == nil {
+                       routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+                       return
+               }
+               var (
+                       queryValues  = make(url.Values)
+                       pathElements = strings.Split(route.Path, "/")
+               )
+               for i, el := range pathElements {
+                       if el == "" || (el[0] != ':' && el[0] != '*') {
+                               continue
+                       }
+                       val, ok := pathData.FixedParamsByName[el[1:]]
+                       if !ok {
+                               val, ok = argValues[el[1:]]
+                       }
+                       if !ok {
+                               val = "<nil>"
+                               routerLog.Error("Reverse: reverse route missing route argument ", "argument", el[1:])
+                       }
+                       pathElements[i] = val
+                       delete(argValues, el[1:])
+                       continue
+               }
+
+               // Add any args that were not inserted into the path into the query string.
+               for k, v := range argValues {
+                       queryValues.Set(k, v)
+               }
+
+               // Calculate the final URL and Method
+               urlPath := strings.Join(pathElements, "/")
+               if len(queryValues) > 0 {
+                       urlPath += "?" + queryValues.Encode()
+               }
+
+               method := route.Method
+               star := false
+               if route.Method == "*" {
+                       method = "GET"
+                       star = true
+               }
+
+               //INFO.Printf("Reversing action %s to %s Using Route %#v",action,url,pathData.Route)
+
+               return &ActionDefinition{
+                       URL:    urlPath,
+                       Method: method,
+                       Star:   star,
+                       Action: action,
+                       Args:   argValues,
+                       Host:   "TODO",
+               }
+       }
+
+       routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues)
+       return nil
+}
+
+func RouterFilter(c *Controller, fc []Filter) {
+       // Figure out the Controller/Action
+       route := MainRouter.Route(c.Request)
+       if route == nil {
+               c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI())
+               return
+       }
+
+       // The route may want to explicitly return a 404.
+       if route.Action == httpStatusCode {
+               c.Result = c.NotFound("(intentionally)")
+               return
+       }
+
+       // Set the action.
+       if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil {
+               c.Result = c.NotFound(err.Error())
+               return
+       }
+
+       // Add the route and fixed params to the Request Params.
+       c.Params.Route = route.Params
+       // Assign logger if from module
+       if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule {
+               c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP,
+                       "path", c.Request.URL.Path, "method", c.Request.Method)
+       }
+
+       // Add the fixed parameters mapped by name.
+       // TODO: Pre-calculate this mapping.
+       for i, value := range route.FixedParams {
+               if c.Params.Fixed == nil {
+                       c.Params.Fixed = make(url.Values)
+               }
+               if i < len(c.MethodType.Args) {
+                       arg := c.MethodType.Args[i]
+                       c.Params.Fixed.Set(arg.Name, value)
+               } else {
+                       routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value)
+                       break
+               }
+       }
+
+       fc[0](c, fc[1:])
+}
+
+// HTTPMethodOverride overrides allowed http methods via form or browser param
+func HTTPMethodOverride(c *Controller, fc []Filter) {
+       // An array of HTTP verbs allowed.
+       verbs := []string{"POST", "PUT", "PATCH", "DELETE"}
+
+       method := strings.ToUpper(c.Request.Method)
+
+       if method == "POST" {
+               param := ""
+               if f, err := c.Request.GetForm(); err == nil {
+                       param = strings.ToUpper(f.Get("_method"))
+               }
+
+               if len(param) > 0 {
+                       override := false
+                       // Check if param is allowed
+                       for _, verb := range verbs {
+                               if verb == param {
+                                       override = true
+                                       break
+                               }
+                       }
+
+                       if override {
+                               c.Request.Method = param
+                       } else {
+                               c.Response.Status = 405
+                               c.Result = c.RenderError(&Error{
+                                       Title:       "Method not allowed",
+                                       Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
+                               })
+                               return
+                       }
+
+               }
+       }
+
+       fc[0](c, fc[1:]) // Execute the next filter stage.
+}
+
+func init() {
+       OnAppStart(func() {
+               MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes"))
+               err := MainRouter.Refresh()
+               if MainWatcher != nil && Config.BoolDefault("watch.routes", true) {
+                       MainWatcher.Listen(MainRouter, MainRouter.path)
+               } else if err != nil {
+                       // Not in dev mode and Route loading failed, we should crash.
+                       routerLog.Panic("init: router initialize error", "error", err)
+               }
+       })
+}
diff --git a/src/foundation/api/revel/router_test.go b/src/foundation/api/revel/router_test.go
new file mode 100644 (file)
index 0000000..fe66970
--- /dev/null
@@ -0,0 +1,678 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+       "strings"
+       "testing"
+)
+
+// Data-driven tests that check that a given routes-file line translates into
+// the expected Route object.
+var routeTestCases = map[string]*Route{
+       "get / Application.Index": {
+               Method:      "GET",
+               Path:        "/",
+               Action:      "Application.Index",
+               FixedParams: []string{},
+       },
+
+       "post /app/:id Application.SaveApp": {
+               Method:      "POST",
+               Path:        "/app/:id",
+               Action:      "Application.SaveApp",
+               FixedParams: []string{},
+       },
+
+       "get /app/ Application.List": {
+               Method:      "GET",
+               Path:        "/app/",
+               Action:      "Application.List",
+               FixedParams: []string{},
+       },
+
+       `get /app/:appId/ Application.Show`: {
+               Method:      "GET",
+               Path:        `/app/:appId/`,
+               Action:      "Application.Show",
+               FixedParams: []string{},
+       },
+
+       `get /app-wild/*appId/ Application.WildShow`: {
+               Method:      "GET",
+               Path:        `/app-wild/*appId/`,
+               Action:      "Application.WildShow",
+               FixedParams: []string{},
+       },
+
+       `GET /public/:filepath   Static.Serve("public")`: {
+               Method: "GET",
+               Path:   "/public/:filepath",
+               Action: "Static.Serve",
+               FixedParams: []string{
+                       "public",
+               },
+       },
+
+       `GET /javascript/:filepath Static.Serve("public/js")`: {
+               Method: "GET",
+               Path:   "/javascript/:filepath",
+               Action: "Static.Serve",
+               FixedParams: []string{
+                       "public",
+               },
+       },
+
+       "* /apps/:id/:action Application.:action": {
+               Method:      "*",
+               Path:        "/apps/:id/:action",
+               Action:      "Application.:action",
+               FixedParams: []string{},
+       },
+
+       "* /:controller/:action :controller.:action": {
+               Method:      "*",
+               Path:        "/:controller/:action",
+               Action:      ":controller.:action",
+               FixedParams: []string{},
+       },
+
+       `GET / Application.Index("Test", "Test2")`: {
+               Method: "GET",
+               Path:   "/",
+               Action: "Application.Index",
+               FixedParams: []string{
+                       "Test",
+                       "Test2",
+               },
+       },
+}
+
+// Run the test cases above.
+func TestComputeRoute(t *testing.T) {
+       for routeLine, expected := range routeTestCases {
+               method, path, action, fixedArgs, found := parseRouteLine(routeLine)
+               if !found {
+                       t.Error("Failed to parse route line:", routeLine)
+                       continue
+               }
+               actual := NewRoute(appModule, method, path, action, fixedArgs, "", 0)
+               eq(t, "Method", actual.Method, expected.Method)
+               eq(t, "Path", actual.Path, expected.Path)
+               eq(t, "Action", actual.Action, expected.Action)
+               if t.Failed() {
+                       t.Fatal("Failed on route:", routeLine)
+               }
+       }
+}
+
+// Router Tests
+
+const TestRoutes = `
+# This is a comment
+GET            /                   Application.Index
+GET            /test/              Application.Index("Test", "Test2")
+GET            /app/:id/           Application.Show
+GET            /app-wild/*id/          Application.WildShow
+POST           /app/:id            Application.Save
+PATCH          /app/:id/           Application.Update
+PROPFIND       /app/:id                        Application.WebDevMethodPropFind
+MKCOL          /app/:id                        Application.WebDevMethodMkCol
+COPY           /app/:id                        Application.WebDevMethodCopy
+MOVE           /app/:id                        Application.WebDevMethodMove
+PROPPATCH      /app/:id                        Application.WebDevMethodPropPatch
+LOCK           /app/:id                        Application.WebDevMethodLock
+UNLOCK         /app/:id                        Application.WebDevMethodUnLock
+TRACE          /app/:id                        Application.WebDevMethodTrace
+PURGE          /app/:id                        Application.CacheMethodPurge
+GET   /javascript/:filepath      App\Static.Serve("public/js")
+GET   /public/*filepath          Static.Serve("public")
+*     /:controller/:action       :controller.:action
+
+GET   /favicon.ico               404
+`
+
+var routeMatchTestCases = map[*http.Request]*RouteMatch{
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Index",
+               FixedParams:    []string{},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/test/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Index",
+               FixedParams:    []string{"Test", "Test2"},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "POST",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Save",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123/"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/public/css/style.css"},
+       }: {
+               ControllerName: "static",
+               MethodName:     "Serve",
+               FixedParams:    []string{"public"},
+               Params:         map[string][]string{"filepath": {"css/style.css"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/javascript/sessvars.js"},
+       }: {
+               ControllerName: "static",
+               MethodName:     "Serve",
+               FixedParams:    []string{"public/js"},
+               Params:         map[string][]string{"filepath": {"sessvars.js"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/Implicit/Route"},
+       }: {
+               ControllerName: "implicit",
+               MethodName:     "Route",
+               FixedParams:    []string{},
+               Params: map[string][]string{
+                       "METHOD":     {"GET"},
+                       "controller": {"Implicit"},
+                       "action":     {"Route"},
+               },
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/favicon.ico"},
+       }: {
+               ControllerName: "",
+               MethodName:     "",
+               Action:         "404",
+               FixedParams:    []string{},
+               Params:         map[string][]string{},
+       },
+
+       {
+               Method: "POST",
+               URL:    &url.URL{Path: "/app/123"},
+               Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "GET",
+               URL:    &url.URL{Path: "/app/123"},
+               Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Show",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "Update",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PROPFIND",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodPropFind",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "MKCOL",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodMkCol",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "COPY",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodCopy",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "MOVE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodMove",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PROPPATCH",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodPropPatch",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+       {
+               Method: "LOCK",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodLock",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "UNLOCK",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodUnLock",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "TRACE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "WebDevMethodTrace",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+
+       {
+               Method: "PURGE",
+               URL:    &url.URL{Path: "/app/123"},
+       }: {
+               ControllerName: "application",
+               MethodName:     "CacheMethodPurge",
+               FixedParams:    []string{},
+               Params:         map[string][]string{"id": {"123"}},
+       },
+}
+
+func TestRouteMatches(t *testing.T) {
+       initControllers()
+       BasePath = "/BasePath"
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
+       if err := router.updateTree(); err != nil {
+               t.Errorf("updateTree failed: %s", err)
+       }
+       for req, expected := range routeMatchTestCases {
+               t.Log("Routing:", req.Method, req.URL)
+
+               context := NewGoContext(nil)
+               context.Request.SetRequest(req)
+               c := NewTestController(nil, req)
+
+               actual := router.Route(c.Request)
+               if !eq(t, "Found route", actual != nil, expected != nil) {
+                       continue
+               }
+               if expected.ControllerName != "" {
+                       eq(t, "ControllerName", actual.ControllerName, appModule.Namespace()+expected.ControllerName)
+               } else {
+                       eq(t, "ControllerName", actual.ControllerName, expected.ControllerName)
+               }
+
+               eq(t, "MethodName", actual.MethodName, strings.ToLower(expected.MethodName))
+               eq(t, "len(Params)", len(actual.Params), len(expected.Params))
+               for key, actualValue := range actual.Params {
+                       eq(t, "Params "+key, actualValue[0], expected.Params[key][0])
+               }
+               eq(t, "len(FixedParams)", len(actual.FixedParams), len(expected.FixedParams))
+               for i, actualValue := range actual.FixedParams {
+                       eq(t, "FixedParams", actualValue, expected.FixedParams[i])
+               }
+       }
+}
+
+// Reverse Routing
+
+type ReverseRouteArgs struct {
+       action string
+       args   map[string]string
+}
+
+var reverseRoutingTestCases = map[*ReverseRouteArgs]*ActionDefinition{
+       {
+               action: "Application.Index",
+               args:   map[string]string{},
+       }: {
+               URL:    "/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.Index",
+       },
+
+       {
+               action: "Application.Show",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.Show",
+       },
+
+       {
+               action: "Implicit.Route",
+               args:   map[string]string{},
+       }: {
+               URL:    "/implicit/route",
+               Method: "GET",
+               Star:   true,
+               Action: "Implicit.Route",
+       },
+
+       {
+               action: "Application.Save",
+               args:   map[string]string{"id": "123", "c": "http://continue"},
+       }: {
+               URL:    "/app/123?c=http%3A%2F%2Fcontinue",
+               Method: "POST",
+               Star:   false,
+               Action: "Application.Save",
+       },
+
+       {
+               action: "Application.WildShow",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app-wild/123/",
+               Method: "GET",
+               Star:   false,
+               Action: "Application.WildShow",
+       },
+
+       {
+               action: "Application.WebDevMethodPropFind",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PROPFIND",
+               Star:   false,
+               Action: "Application.WebDevMethodPropFind",
+       },
+       {
+               action: "Application.WebDevMethodMkCol",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "MKCOL",
+               Star:   false,
+               Action: "Application.WebDevMethodMkCol",
+       },
+       {
+               action: "Application.WebDevMethodCopy",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "COPY",
+               Star:   false,
+               Action: "Application.WebDevMethodCopy",
+       },
+       {
+               action: "Application.WebDevMethodMove",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "MOVE",
+               Star:   false,
+               Action: "Application.WebDevMethodMove",
+       },
+       {
+               action: "Application.WebDevMethodPropPatch",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PROPPATCH",
+               Star:   false,
+               Action: "Application.WebDevMethodPropPatch",
+       },
+       {
+               action: "Application.WebDevMethodLock",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "LOCK",
+               Star:   false,
+               Action: "Application.WebDevMethodLock",
+       },
+       {
+               action: "Application.WebDevMethodUnLock",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "UNLOCK",
+               Star:   false,
+               Action: "Application.WebDevMethodUnLock",
+       },
+       {
+               action: "Application.WebDevMethodTrace",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "TRACE",
+               Star:   false,
+               Action: "Application.WebDevMethodTrace",
+       },
+       {
+               action: "Application.CacheMethodPurge",
+               args:   map[string]string{"id": "123"},
+       }: {
+               URL:    "/app/123",
+               Method: "PURGE",
+               Star:   false,
+               Action: "Application.CacheMethodPurge",
+       },
+}
+
+type testController struct {
+       *Controller
+}
+
+func initControllers() {
+       registerControllers()
+}
+func TestReverseRouting(t *testing.T) {
+       initControllers()
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false)
+       for routeArgs, expected := range reverseRoutingTestCases {
+               actual := router.Reverse(routeArgs.action, routeArgs.args)
+               if !eq(t, fmt.Sprintf("Found route %s %s", routeArgs.action, actual), actual != nil, expected != nil) {
+                       continue
+               }
+               eq(t, "Url", actual.URL, expected.URL)
+               eq(t, "Method", actual.Method, expected.Method)
+               eq(t, "Star", actual.Star, expected.Star)
+               eq(t, "Action", actual.Action, expected.Action)
+       }
+}
+
+func BenchmarkRouter(b *testing.B) {
+       router := NewRouter("")
+       router.Routes, _ = parseRoutes(nil, "", "", TestRoutes, false)
+       if err := router.updateTree(); err != nil {
+               b.Errorf("updateTree failed: %s", err)
+       }
+       b.ResetTimer()
+       for i := 0; i < b.N/len(routeMatchTestCases); i++ {
+               for req := range routeMatchTestCases {
+                       c := NewTestController(nil, req)
+                       r := router.Route(c.Request)
+                       if r == nil {
+                               b.Errorf("Request not found: %s", req.URL.Path)
+                       }
+               }
+       }
+}
+
+// The benchmark from github.com/ant0ine/go-urlrouter
+func BenchmarkLargeRouter(b *testing.B) {
+       router := NewRouter("")
+
+       routePaths := []string{
+               "/",
+               "/signin",
+               "/signout",
+               "/profile",
+               "/settings",
+               "/upload/*file",
+       }
+       for i := 0; i < 10; i++ {
+               for j := 0; j < 5; j++ {
+                       routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id/property%d", i, j))
+               }
+               routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id", i))
+               routePaths = append(routePaths, fmt.Sprintf("/resource%d", i))
+       }
+       routePaths = append(routePaths, "/:any")
+
+       for _, p := range routePaths {
+               router.Routes = append(router.Routes,
+                       NewRoute(appModule, "GET", p, "Controller.Action", "", "", 0))
+       }
+       if err := router.updateTree(); err != nil {
+               b.Errorf("updateTree failed: %s", err)
+       }
+
+       requestUrls := []string{
+               "http://example.org/",
+               "http://example.org/resource9/123",
+               "http://example.org/resource9/123/property1",
+               "http://example.org/doesnotexist",
+       }
+       var reqs []*http.Request
+       for _, url := range requestUrls {
+               req, _ := http.NewRequest("GET", url, nil)
+               reqs = append(reqs, req)
+       }
+
+       b.ResetTimer()
+
+       for i := 0; i < b.N/len(reqs); i++ {
+               for _, req := range reqs {
+                       c := NewTestController(nil, req)
+                       route := router.Route(c.Request)
+                       if route == nil {
+                               b.Errorf("Failed to route: %s", req.URL.Path)
+                       }
+               }
+       }
+}
+
+func BenchmarkRouterFilter(b *testing.B) {
+       startFakeBookingApp()
+       controllers := []*Controller{
+               NewTestController(nil, showRequest),
+               NewTestController(nil, staticRequest),
+       }
+       for _, c := range controllers {
+               c.Params = &Params{}
+               ParseParams(c.Params, c.Request)
+       }
+
+       b.ResetTimer()
+       for i := 0; i < b.N/len(controllers); i++ {
+               for _, c := range controllers {
+                       RouterFilter(c, NilChain)
+               }
+       }
+}
+
+func TestOverrideMethodFilter(t *testing.T) {
+       req, _ := http.NewRequest("POST", "/hotels/3", strings.NewReader("_method=put"))
+       req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
+       c := NewTestController(nil, req)
+
+       if HTTPMethodOverride(c, NilChain); c.Request.Method != "PUT" {
+               t.Errorf("Expected to override current method '%s' in route, found '%s' instead", "", c.Request.Method)
+       }
+}
+
+// Helpers
+
+func eq(t *testing.T, name string, a, b interface{}) bool {
+       if a != b {
+               t.Error(name, ": (actual)", a, " != ", b, "(expected)")
+               return false
+       }
+       return true
+}
diff --git a/src/foundation/api/revel/server-engine.go b/src/foundation/api/revel/server-engine.go
new file mode 100644 (file)
index 0000000..0ac4b8a
--- /dev/null
@@ -0,0 +1,229 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "errors"
+       "io"
+       "mime/multipart"
+       "net/url"
+       "strings"
+       "time"
+)
+
+const (
+       /* Minimum Engine Type Values */
+       _ = iota
+       ENGINE_RESPONSE_STATUS
+       ENGINE_WRITER
+       ENGINE_PARAMETERS
+       ENGINE_PATH
+       ENGINE_REQUEST
+       ENGINE_RESPONSE
+)
+const (
+       /* HTTP Engine Type Values Starts at 1000 */
+       HTTP_QUERY           = ENGINE_PARAMETERS
+       HTTP_PATH            = ENGINE_PATH
+       HTTP_BODY            = iota + 1000
+       HTTP_FORM            = iota + 1000
+       HTTP_MULTIPART_FORM  = iota + 1000
+       HTTP_METHOD          = iota + 1000
+       HTTP_REQUEST_URI     = iota + 1000
+       HTTP_REQUEST_CONTEXT = iota + 1000
+       HTTP_REMOTE_ADDR     = iota + 1000
+       HTTP_HOST            = iota + 1000
+       HTTP_URL             = iota + 1000
+       HTTP_SERVER_HEADER   = iota + 1000
+       HTTP_STREAM_WRITER   = iota + 1000
+       HTTP_WRITER          = ENGINE_WRITER
+)
+
+type (
+       ServerContext interface {
+               GetRequest() ServerRequest
+               GetResponse() ServerResponse
+       }
+
+       // Callback ServerRequest type
+       ServerRequest interface {
+               GetRaw() interface{}
+               Get(theType int) (interface{}, error)
+               Set(theType int, theValue interface{}) bool
+       }
+       // Callback ServerResponse type
+       ServerResponse interface {
+               ServerRequest
+       }
+       // Callback WebSocket type
+       ServerWebSocket interface {
+               ServerResponse
+               MessageSendJSON(v interface{}) error
+               MessageReceiveJSON(v interface{}) error
+               MessageSend(v interface{}) error
+               MessageReceive(v interface{}) error
+       }
+
+       // Expected response for HTTP_SERVER_HEADER type (if implemented)
+       ServerHeader interface {
+               SetCookie(cookie string) // Sets the cookie
+               GetCookie(key string) (value ServerCookie, err error) // Gets the cookie
+               Set(key string, value string)
+               Add(key string, value string)
+               Del(key string)
+               Get(key string) (value []string)
+               GetKeys() (headerKeys []string)
+               SetStatus(statusCode int)
+       }
+
+       // Expected response for FROM_HTTP_COOKIE type (if implemented)
+       ServerCookie interface {
+               GetValue() string
+       }
+
+       // Expected response for HTTP_MULTIPART_FORM
+       ServerMultipartForm interface {
+               GetFiles() map[string][]*multipart.FileHeader
+               GetValues() url.Values
+               RemoveAll() error
+       }
+       StreamWriter interface {
+               WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error
+       }
+
+       ServerEngine interface {
+               // Initialize the server (non blocking)
+               Init(init *EngineInit)
+               // Starts the server. This will block until server is stopped
+               Start()
+               // Fires a new event to the server
+               Event(event Event, args interface{}) EventResponse
+               // Returns the engine instance for specific calls
+               Engine() interface{}
+               // Returns the engine Name
+               Name() string
+               // Returns any stats
+               Stats() map[string]interface{}
+       }
+
+       // The initialization structure passed into the engine
+       EngineInit struct {
+               Address, // The address
+               Network string // The network
+               Port        int                 // The port
+               HTTPMuxList ServerMuxList       // The HTTPMux
+               Callback    func(ServerContext) // The ServerContext callback endpoint
+       }
+
+       // An empty server engine
+       ServerEngineEmpty struct {
+       }
+
+       // The route handler structure
+       ServerMux struct {
+               PathPrefix string      // The path prefix
+               Callback   interface{} // The callback interface as appropriate to the server
+       }
+
+       // A list of handlers used for adding special route functions
+       ServerMuxList []ServerMux
+)
+
+// Sorting function
+func (r ServerMuxList) Len() int {
+       return len(r)
+}
+
+// Sorting function
+func (r ServerMuxList) Less(i, j int) bool {
+       return len(r[i].PathPrefix) > len(r[j].PathPrefix)
+}
+
+// Sorting function
+func (r ServerMuxList) Swap(i, j int) {
+       r[i], r[j] = r[j], r[i]
+}
+
+// Search function, returns the largest path matching this
+func (r ServerMuxList) Find(path string) (interface{}, bool) {
+       for _, p := range r {
+               if p.PathPrefix == path || strings.HasPrefix(path, p.PathPrefix) {
+                       return p.Callback, true
+               }
+       }
+       return nil, false
+}
+
+// Adds this routehandler to the route table. It will be called (if the path prefix matches)
+// before the Revel mux, this can only be called after the ENGINE_BEFORE_INITIALIZED event
+func AddHTTPMux(path string, callback interface{}) {
+       ServerEngineInit.HTTPMuxList = append(ServerEngineInit.HTTPMuxList, ServerMux{PathPrefix: path, Callback: callback})
+}
+
+// Callback point for the server to handle the
+func handleInternal(ctx ServerContext) {
+       start := time.Now()
+       var c *Controller
+       if RevelConfig.Controller.Reuse {
+               c         = RevelConfig.Controller.Stack.Pop().(*Controller)
+               defer func() {
+                       RevelConfig.Controller.Stack.Push(c)
+               }()
+       } else {
+               c = NewControllerEmpty()
+       }
+
+       var (
+
+               req, resp = c.Request, c.Response
+       )
+       c.SetController(ctx)
+       req.WebSocket, _ = ctx.GetResponse().(ServerWebSocket)
+
+       clientIP := ClientIP(req)
+
+       // Once finished in the internal, we can return these to the stack
+
+       c.ClientIP = clientIP
+       c.Log = AppLog.New("ip", clientIP,
+               "path", req.GetPath(), "method", req.Method)
+       // Call the first filter, this will process the request
+       Filters[0](c, Filters[1:])
+       if c.Result != nil {
+               c.Result.Apply(req, resp)
+       } else if c.Response.Status != 0 {
+               c.Response.SetStatus(c.Response.Status)
+       }
+       // Close the Writer if we can
+       if w, ok := resp.GetWriter().(io.Closer); ok {
+               _ = w.Close()
+       }
+
+       // Revel request access log format
+       // RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
+       // Sample format: terminal format
+       // INFO 2017/08/02 22:31:41 server-engine.go:168: Request Stats                            ip=::1 path=/public/img/favicon.png method=GET action=Static.Serve namespace=static\\ start=2017/08/02 22:31:41 status=200 duration_seconds=0.0007656
+       // Recommended storing format to json code which looks like
+       // {"action":"Static.Serve","caller":"server-engine.go:168","duration_seconds":0.00058336,"ip":"::1","lvl":3,
+       // "method":"GET","msg":"Request Stats","namespace":"static\\","path":"/public/img/favicon.png",
+       // "start":"2017-08-02T22:34:08-0700","status":200,"t":"2017-08-02T22:34:08.303112145-07:00"}
+
+       c.Log.Info("Request Stats",
+               "start", start,
+               "status", c.Response.Status,
+               "duration_seconds", time.Since(start).Seconds(), "section", "requestlog",
+       )
+}
+
+var (
+       ENGINE_UNKNOWN_GET = errors.New("Server Engine Invalid Get")
+)
+
+func (e *ServerEngineEmpty) Get(_ string) interface{} {
+       return nil
+}
+func (e *ServerEngineEmpty) Set(_ string, _ interface{}) bool {
+       return false
+}
diff --git a/src/foundation/api/revel/server.go b/src/foundation/api/revel/server.go
new file mode 100644 (file)
index 0000000..0f5c786
--- /dev/null
@@ -0,0 +1,153 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "github.com/revel/revel/session"
+       "os"
+       "strconv"
+       "strings"
+       "github.com/revel/revel/utils"
+)
+
+// Revel's variables server, router, etc
+var (
+       MainRouter         *Router
+       MainTemplateLoader *TemplateLoader
+       MainWatcher        *Watcher
+       serverEngineMap    = map[string]func() ServerEngine{}
+       CurrentEngine      ServerEngine
+       ServerEngineInit   *EngineInit
+       serverLogger       = RevelLog.New("section", "server")
+)
+
+func RegisterServerEngine(name string, loader func() ServerEngine) {
+       serverLogger.Debug("RegisterServerEngine: Registered engine ", "name", name)
+       serverEngineMap[name] = loader
+}
+
+// InitServer initializes the server and returns the handler
+// It can be used as an alternative entry-point if one needs the http handler
+// to be exposed. E.g. to run on multiple addresses and ports or to set custom
+// TLS options.
+func InitServer() {
+       CurrentEngine.Init(ServerEngineInit)
+       initControllerStack()
+       startupHooks.Run()
+
+       // Load templates
+       MainTemplateLoader = NewTemplateLoader(TemplatePaths)
+       if err := MainTemplateLoader.Refresh(); err != nil {
+               serverLogger.Debug("InitServer: Main template loader failed to refresh", "error", err)
+       }
+
+       // The "watch" config variable can turn on and off all watching.
+       // (As a convenient way to control it all together.)
+       if Config.BoolDefault("watch", true) {
+               MainWatcher = NewWatcher()
+               Filters = append([]Filter{WatchFilter}, Filters...)
+       }
+
+       // If desired (or by default), create a watcher for templates and routes.
+       // The watcher calls Refresh() on things on the first request.
+       if MainWatcher != nil && Config.BoolDefault("watch.templates", true) {
+               MainWatcher.Listen(MainTemplateLoader, MainTemplateLoader.paths...)
+       }
+
+}
+
+// Run the server.
+// This is called from the generated main file.
+// If port is non-zero, use that.  Else, read the port from app.conf.
+func Run(port int) {
+       defer func() {
+               if r := recover(); r != nil {
+                       RevelLog.Crit("Recovered error in startup", "error", r)
+                       RaiseEvent(REVEL_FAILURE, r)
+                       panic("Fatal error in startup")
+               }
+       }()
+
+       // Initialize the session logger, must be initiated from this app to avoid
+       // circular references
+       session.InitSession(RevelLog)
+
+       // Create the CurrentEngine instance from the application config
+       InitServerEngine(port, Config.StringDefault("server.engine", GO_NATIVE_SERVER_ENGINE))
+       RaiseEvent(ENGINE_BEFORE_INITIALIZED, nil)
+       InitServer()
+       RaiseEvent(ENGINE_STARTED, nil)
+       // This is needed for the harness to recognize that the server is started, it looks for the word
+       // "Listening" in the stdout stream
+
+       fmt.Fprintf(os.Stdout, "Revel engine is listening on.. %s\n", ServerEngineInit.Address)
+       // Start never returns,
+       CurrentEngine.Start()
+       fmt.Fprintf(os.Stdout, "Revel engine is NOT listening on.. %s\n", ServerEngineInit.Address)
+       RaiseEvent(ENGINE_SHUTDOWN, nil)
+       shutdownHooks.Run()
+       println("\nRevel exited normally\n")
+}
+
+// Build an engine initialization object and start the engine
+func InitServerEngine(port int, serverEngine string) {
+       address := HTTPAddr
+       if address == "" {
+               address = "localhost"
+       }
+       if port == 0 {
+               port = HTTPPort
+       }
+
+       var network = "tcp"
+       var localAddress string
+
+       // If the port is zero, treat the address as a fully qualified local address.
+       // This address must be prefixed with the network type followed by a colon,
+       // e.g. unix:/tmp/app.socket or tcp6:::1 (equivalent to tcp6:0:0:0:0:0:0:0:1)
+       if port == 0 {
+               parts := strings.SplitN(address, ":", 2)
+               network = parts[0]
+               localAddress = parts[1]
+       } else {
+               localAddress = address + ":" + strconv.Itoa(port)
+       }
+
+       if engineLoader, ok := serverEngineMap[serverEngine]; !ok {
+               panic("Server Engine " + serverEngine + " Not found")
+       } else {
+               CurrentEngine = engineLoader()
+               serverLogger.Debug("InitServerEngine: Found server engine and invoking", "name", CurrentEngine.Name())
+               ServerEngineInit = &EngineInit{
+                       Address:  localAddress,
+                       Network:  network,
+                       Port:     port,
+                       Callback: handleInternal,
+               }
+       }
+       AddInitEventHandler(CurrentEngine.Event)
+}
+
+// Initialize the controller stack for the application
+func initControllerStack() {
+       RevelConfig.Controller.Reuse = Config.BoolDefault("revel.controller.reuse",true)
+
+       if RevelConfig.Controller.Reuse {
+               RevelConfig.Controller.Stack = utils.NewStackLock(
+                       Config.IntDefault("revel.controller.stack", 10),
+                       Config.IntDefault("revel.controller.maxstack", 200), func() interface{} {
+                               return NewControllerEmpty()
+                       })
+               RevelConfig.Controller.CachedStackSize = Config.IntDefault("revel.cache.controller.stack", 10)
+               RevelConfig.Controller.CachedStackMaxSize = Config.IntDefault("revel.cache.controller.maxstack", 100)
+               RevelConfig.Controller.CachedMap = map[string]*utils.SimpleLockStack{}
+       }
+}
+
+// Called to stop the server
+func StopServer(value interface{}) EventResponse {
+       return RaiseEvent(ENGINE_SHUTDOWN_REQUEST,value)
+}
\ No newline at end of file
diff --git a/src/foundation/api/revel/server_adapter_go.go b/src/foundation/api/revel/server_adapter_go.go
new file mode 100644 (file)
index 0000000..bacaf91
--- /dev/null
@@ -0,0 +1,647 @@
+package revel
+
+import (
+       "net"
+       "net/http"
+       "time"
+       "context"
+       "golang.org/x/net/websocket"
+       "io"
+       "mime/multipart"
+       "net/url"
+       "os"
+       "os/signal"
+       "path"
+       "sort"
+       "strconv"
+       "strings"
+       "github.com/revel/revel/utils"
+)
+
+// Register the GoHttpServer engine
+func init() {
+       AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+               if typeOf == REVEL_BEFORE_MODULES_LOADED {
+                       RegisterServerEngine(GO_NATIVE_SERVER_ENGINE, func() ServerEngine { return &GoHttpServer{} })
+               }
+               return
+       })
+}
+
+// The Go HTTP server
+type GoHttpServer struct {
+       Server               *http.Server           // The server instance
+       ServerInit           *EngineInit            // The server engine initialization
+       MaxMultipartSize     int64                  // The largest size of file to accept
+       goContextStack       *utils.SimpleLockStack // The context stack Set via server.context.stack, server.context.maxstack
+       goMultipartFormStack *utils.SimpleLockStack // The multipart form stack set via server.form.stack, server.form.maxstack
+       HttpMuxList          ServerMuxList
+       HasAppMux            bool
+       signalChan           chan os.Signal
+}
+
+// Called to initialize the server with this EngineInit
+func (g *GoHttpServer) Init(init *EngineInit) {
+       g.MaxMultipartSize = int64(Config.IntDefault("server.request.max.multipart.filesize", 32)) << 20 /* 32 MB */
+       g.goContextStack = utils.NewStackLock(Config.IntDefault("server.context.stack", 100),
+               Config.IntDefault("server.context.maxstack", 200),
+               func() interface{} {
+                       return NewGoContext(g)
+               })
+       g.goMultipartFormStack = utils.NewStackLock(Config.IntDefault("server.form.stack", 100),
+               Config.IntDefault("server.form.maxstack", 200),
+               func() interface{} { return &GoMultipartForm{} })
+       g.ServerInit = init
+
+       revelHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+               g.Handle(writer, request)
+       })
+
+       // Adds the mux list
+       g.HttpMuxList = init.HTTPMuxList
+       sort.Sort(g.HttpMuxList)
+       g.HasAppMux = len(g.HttpMuxList) > 0
+       g.signalChan = make(chan os.Signal)
+
+       g.Server = &http.Server{
+               Addr:         init.Address,
+               Handler:      revelHandler,
+               ReadTimeout:  time.Duration(Config.IntDefault("http.timeout.read", 0)) * time.Second,
+               WriteTimeout: time.Duration(Config.IntDefault("http.timeout.write", 0)) * time.Second,
+       }
+
+}
+
+// Handler is assigned in the Init
+func (g *GoHttpServer) Start() {
+       go func() {
+               time.Sleep(100 * time.Millisecond)
+               serverLogger.Debugf("Start: Listening on %s...", g.Server.Addr)
+       }()
+       if HTTPSsl {
+               if g.ServerInit.Network != "tcp" {
+                       // This limitation is just to reduce complexity, since it is standard
+                       // to terminate SSL upstream when using unix domain sockets.
+                       serverLogger.Fatal("SSL is only supported for TCP sockets. Specify a port to listen on.")
+               }
+               serverLogger.Fatal("Failed to listen:", "error",
+                       g.Server.ListenAndServeTLS(HTTPSslCert, HTTPSslKey))
+       } else {
+               listener, err := net.Listen(g.ServerInit.Network, g.Server.Addr)
+               if err != nil {
+                       serverLogger.Fatal("Failed to listen:", "error", err)
+               }
+               serverLogger.Warn("Server exiting:", "error", g.Server.Serve(listener))
+       }
+}
+
+// Handle the request and response for the server
+func (g *GoHttpServer) Handle(w http.ResponseWriter, r *http.Request) {
+       // This section is called if the developer has added custom mux to the app
+       if g.HasAppMux && g.handleAppMux(w, r) {
+               return
+       }
+       g.handleMux(w, r)
+}
+
+// Handle the request and response for the servers mux
+func (g *GoHttpServer) handleAppMux(w http.ResponseWriter, r *http.Request) bool {
+       // Check the prefix and split them
+       requestPath := path.Clean(r.URL.Path)
+       if handler, hasHandler := g.HttpMuxList.Find(requestPath); hasHandler {
+               clientIP := HttpClientIP(r)
+               localLog := AppLog.New("ip", clientIP,
+                       "path", r.URL.Path, "method", r.Method)
+               defer func() {
+                       if err := recover(); err != nil {
+                               localLog.Error("An error was caught using the handler", "path", requestPath, "error", err)
+                               w.WriteHeader(http.StatusInternalServerError)
+                       }
+               }()
+               start := time.Now()
+               handler.(http.HandlerFunc)(w, r)
+               localLog.Info("Request Stats",
+                       "start", start,
+                       "duration_seconds", time.Since(start).Seconds(), "section", "requestlog",
+               )
+               return true
+       }
+       return false
+}
+
+// Passes the server request to Revel
+func (g *GoHttpServer) handleMux(w http.ResponseWriter, r *http.Request) {
+       if maxRequestSize := int64(Config.IntDefault("http.maxrequestsize", 0)); maxRequestSize > 0 {
+               r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
+       }
+
+       upgrade := r.Header.Get("Upgrade")
+       context := g.goContextStack.Pop().(*GoContext)
+       defer func() {
+               g.goContextStack.Push(context)
+       }()
+       context.Request.SetRequest(r)
+       context.Response.SetResponse(w)
+
+       if upgrade == "websocket" || upgrade == "Websocket" {
+               websocket.Handler(func(ws *websocket.Conn) {
+                       //Override default Read/Write timeout with sane value for a web socket request
+                       if err := ws.SetDeadline(time.Now().Add(time.Hour * 24)); err != nil {
+                               serverLogger.Error("SetDeadLine failed:", err)
+                       }
+                       r.Method = "WS"
+                       context.Request.WebSocket = ws
+                       context.WebSocket = &GoWebSocket{Conn: ws, GoResponse: *context.Response}
+                       g.ServerInit.Callback(context)
+               }).ServeHTTP(w, r)
+       } else {
+               g.ServerInit.Callback(context)
+       }
+}
+
+// ClientIP method returns client IP address from HTTP request.
+//
+// Note: Set property "app.behind.proxy" to true only if Revel is running
+// behind proxy like nginx, haproxy, apache, etc. Otherwise
+// you may get inaccurate Client IP address. Revel parses the
+// IP address in the order of X-Forwarded-For, X-Real-IP.
+//
+// By default revel will get http.Request's RemoteAddr
+func HttpClientIP(r *http.Request) string {
+       if Config.BoolDefault("app.behind.proxy", false) {
+               // Header X-Forwarded-For
+               if fwdFor := strings.TrimSpace(r.Header.Get(HdrForwardedFor)); fwdFor != "" {
+                       index := strings.Index(fwdFor, ",")
+                       if index == -1 {
+                               return fwdFor
+                       }
+                       return fwdFor[:index]
+               }
+
+               // Header X-Real-Ip
+               if realIP := strings.TrimSpace(r.Header.Get(HdrRealIP)); realIP != "" {
+                       return realIP
+               }
+       }
+
+       if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+               return remoteAddr
+       }
+
+       return ""
+}
+
+// The server key name
+const GO_NATIVE_SERVER_ENGINE = "go"
+
+// Returns the name of this engine
+func (g *GoHttpServer) Name() string {
+       return GO_NATIVE_SERVER_ENGINE
+}
+
+// Returns stats for this engine
+func (g *GoHttpServer) Stats() map[string]interface{} {
+       return map[string]interface{}{
+               "Go Engine Context": g.goContextStack.String(),
+               "Go Engine Forms":   g.goMultipartFormStack.String(),
+       }
+}
+
+// Return the engine instance
+func (g *GoHttpServer) Engine() interface{} {
+       return g.Server
+}
+
+// Handles an event from Revel
+func (g *GoHttpServer) Event(event Event, args interface{}) (r EventResponse) {
+       switch event {
+       case ENGINE_STARTED:
+               signal.Notify(g.signalChan, os.Interrupt, os.Kill)
+               go func() {
+                       _ = <-g.signalChan
+                       serverLogger.Info("Received quit singal Please wait ... ")
+                       RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil)
+               }()
+       case ENGINE_SHUTDOWN_REQUEST:
+               ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(Config.IntDefault("app.cancel.timeout", 60)))
+               defer cancel()
+               g.Server.Shutdown(ctx)
+       default:
+
+       }
+
+       return
+}
+
+type (
+       // The go context
+       GoContext struct {
+               Request   *GoRequest   // The request
+               Response  *GoResponse  // The response
+               WebSocket *GoWebSocket // The websocket
+       }
+
+       // The go request
+       GoRequest struct {
+               Original        *http.Request    // The original
+               FormParsed      bool             // True if form parsed
+               MultiFormParsed bool             // True if multipart form parsed
+               WebSocket       *websocket.Conn  // The websocket
+               ParsedForm      *GoMultipartForm // The parsed form data
+               Goheader        *GoHeader        // The header
+               Engine          *GoHttpServer    // THe server
+       }
+
+       // The response
+       GoResponse struct {
+               Original http.ResponseWriter // The original writer
+               Goheader *GoHeader           // The header
+               Writer   io.Writer           // The writer
+               Request  *GoRequest          // The request
+               Engine   *GoHttpServer       // The engine
+       }
+
+       // The multipart form
+       GoMultipartForm struct {
+               Form *multipart.Form // The form
+       }
+
+       // The go header
+       GoHeader struct {
+               Source     interface{} // The source
+               isResponse bool        // True if response header
+       }
+
+       // The websocket
+       GoWebSocket struct {
+               Conn       *websocket.Conn // The connection
+               GoResponse                 // The response
+       }
+
+       // The cookie
+       GoCookie http.Cookie
+)
+
+// Create a new go context
+func NewGoContext(instance *GoHttpServer) *GoContext {
+       // This bit in here is for the test cases, which pass in a nil value
+       if instance == nil {
+               instance = &GoHttpServer{MaxMultipartSize: 32 << 20}
+               instance.goContextStack = utils.NewStackLock(100, 200,
+                       func() interface{} {
+                               return NewGoContext(instance)
+                       })
+               instance.goMultipartFormStack = utils.NewStackLock(100, 200,
+                       func() interface{} { return &GoMultipartForm{} })
+       }
+       c := &GoContext{Request: &GoRequest{Goheader: &GoHeader{}, Engine: instance}}
+       c.Response = &GoResponse{Goheader: &GoHeader{}, Request: c.Request, Engine: instance}
+       return c
+}
+
+// get the request
+func (c *GoContext) GetRequest() ServerRequest {
+       return c.Request
+}
+
+// Get the response
+func (c *GoContext) GetResponse() ServerResponse {
+       if c.WebSocket != nil {
+               return c.WebSocket
+       }
+       return c.Response
+}
+
+// Destroy the context
+func (c *GoContext) Destroy() {
+       c.Response.Destroy()
+       c.Request.Destroy()
+       if c.WebSocket != nil {
+               c.WebSocket.Destroy()
+               c.WebSocket = nil
+       }
+}
+
+// Communicate with the server engine to exchange data
+func (r *GoRequest) Get(key int) (value interface{}, err error) {
+       switch key {
+       case HTTP_SERVER_HEADER:
+               value = r.GetHeader()
+       case HTTP_MULTIPART_FORM:
+               value, err = r.GetMultipartForm()
+       case HTTP_QUERY:
+               value = r.Original.URL.Query()
+       case HTTP_FORM:
+               value, err = r.GetForm()
+       case HTTP_REQUEST_URI:
+               value = r.Original.URL.String()
+       case HTTP_REQUEST_CONTEXT:
+               value = r.Original.Context()
+       case HTTP_REMOTE_ADDR:
+               value = r.Original.RemoteAddr
+       case HTTP_METHOD:
+               value = r.Original.Method
+       case HTTP_PATH:
+               value = r.Original.URL.Path
+       case HTTP_HOST:
+               value = r.Original.Host
+       case HTTP_URL:
+               value = r.Original.URL
+       case HTTP_BODY:
+               value = r.Original.Body
+       default:
+               err = ENGINE_UNKNOWN_GET
+       }
+
+       return
+}
+
+// Sets the request key with value
+func (r *GoRequest) Set(key int, value interface{}) bool {
+       return false
+}
+
+// Returns the form
+func (r *GoRequest) GetForm() (url.Values, error) {
+       if !r.FormParsed {
+               if e := r.Original.ParseForm(); e != nil {
+                       return nil, e
+               }
+               r.FormParsed = true
+       }
+
+       return r.Original.Form, nil
+}
+
+// Returns the form
+func (r *GoRequest) GetMultipartForm() (ServerMultipartForm, error) {
+       if !r.MultiFormParsed {
+               if e := r.Original.ParseMultipartForm(r.Engine.MaxMultipartSize); e != nil {
+                       return nil, e
+               }
+               r.ParsedForm = r.Engine.goMultipartFormStack.Pop().(*GoMultipartForm)
+               r.ParsedForm.Form = r.Original.MultipartForm
+       }
+
+       return r.ParsedForm, nil
+}
+
+// Returns the header
+func (r *GoRequest) GetHeader() ServerHeader {
+       return r.Goheader
+}
+
+// Returns the raw value
+func (r *GoRequest) GetRaw() interface{} {
+       return r.Original
+}
+
+// Sets the request
+func (r *GoRequest) SetRequest(req *http.Request) {
+       r.Original = req
+       r.Goheader.Source = r
+       r.Goheader.isResponse = false
+
+}
+
+// Destroy the request
+func (r *GoRequest) Destroy() {
+       r.Goheader.Source = nil
+       r.Original = nil
+       r.FormParsed = false
+       r.MultiFormParsed = false
+       r.ParsedForm = nil
+}
+
+// Gets the key from the response
+func (r *GoResponse) Get(key int) (value interface{}, err error) {
+       switch key {
+       case HTTP_SERVER_HEADER:
+               value = r.Header()
+       case HTTP_STREAM_WRITER:
+               value = r
+       case HTTP_WRITER:
+               value = r.Writer
+       default:
+               err = ENGINE_UNKNOWN_GET
+       }
+       return
+}
+
+// Sets the key with the value
+func (r *GoResponse) Set(key int, value interface{}) (set bool) {
+       switch key {
+       case ENGINE_RESPONSE_STATUS:
+               r.Header().SetStatus(value.(int))
+               set = true
+       case HTTP_WRITER:
+               r.SetWriter(value.(io.Writer))
+               set = true
+       }
+       return
+}
+
+// Sets the header
+func (r *GoResponse) Header() ServerHeader {
+       return r.Goheader
+}
+
+// Gets the original response
+func (r *GoResponse) GetRaw() interface{} {
+       return r.Original
+}
+
+// Sets the writer
+func (r *GoResponse) SetWriter(writer io.Writer) {
+       r.Writer = writer
+}
+
+// Write output to stream
+func (r *GoResponse) WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error {
+       // Check to see if the output stream is modified, if not send it using the
+       // Native writer
+       written := false
+       if _, ok := r.Writer.(http.ResponseWriter); ok {
+               if rs, ok := reader.(io.ReadSeeker); ok {
+                       http.ServeContent(r.Original, r.Request.Original, name, modtime, rs)
+                       written = true
+               }
+       }
+       if !written {
+               // Else, do a simple io.Copy.
+               ius := r.Request.Original.Header.Get("If-Unmodified-Since")
+               if t, err := http.ParseTime(ius); err == nil && !modtime.IsZero() {
+                       // The Date-Modified header truncates sub-second precision, so
+                       // use mtime < t+1s instead of mtime <= t to check for unmodified.
+                       if modtime.Before(t.Add(1 * time.Second)) {
+                               h := r.Original.Header()
+                               delete(h, "Content-Type")
+                               delete(h, "Content-Length")
+                               if h.Get("Etag") != "" {
+                                       delete(h, "Last-Modified")
+                               }
+                               r.Original.WriteHeader(http.StatusNotModified)
+                               return nil
+                       }
+               }
+
+               if contentlen != -1 {
+                       header := ServerHeader(r.Goheader)
+                       if writer, found := r.Writer.(*CompressResponseWriter); found {
+                               header = ServerHeader(writer.Header)
+                       }
+                       header.Set("Content-Length", strconv.FormatInt(contentlen, 10))
+               }
+               if _, err := io.Copy(r.Writer, reader); err != nil {
+                       r.Original.WriteHeader(http.StatusInternalServerError)
+                       return err
+               } else {
+                       r.Original.WriteHeader(http.StatusOK)
+               }
+       }
+       return nil
+}
+
+// Frees response
+func (r *GoResponse) Destroy() {
+       if c, ok := r.Writer.(io.Closer); ok {
+               c.Close()
+       }
+       r.Goheader.Source = nil
+       r.Original = nil
+       r.Writer = nil
+}
+
+// Sets the response
+func (r *GoResponse) SetResponse(w http.ResponseWriter) {
+       r.Original = w
+       r.Writer = w
+       r.Goheader.Source = r
+       r.Goheader.isResponse = true
+
+}
+
+// Sets the cookie
+func (r *GoHeader) SetCookie(cookie string) {
+       if r.isResponse {
+               r.Source.(*GoResponse).Original.Header().Add("Set-Cookie", cookie)
+       }
+}
+
+// Gets the cookie
+func (r *GoHeader) GetCookie(key string) (value ServerCookie, err error) {
+       if !r.isResponse {
+               var cookie *http.Cookie
+               if cookie, err = r.Source.(*GoRequest).Original.Cookie(key); err == nil {
+                       value = GoCookie(*cookie)
+
+               }
+
+       }
+       return
+}
+
+// Sets (replaces) header key
+func (r *GoHeader) Set(key string, value string) {
+       if r.isResponse {
+               r.Source.(*GoResponse).Original.Header().Set(key, value)
+       }
+}
+
+// Adds the header key
+func (r *GoHeader) Add(key string, value string) {
+       if r.isResponse {
+               r.Source.(*GoResponse).Original.Header().Add(key, value)
+       }
+}
+
+// Deletes the header key
+func (r *GoHeader) Del(key string) {
+       if r.isResponse {
+               r.Source.(*GoResponse).Original.Header().Del(key)
+       }
+}
+
+// Gets the header key
+func (r *GoHeader) Get(key string) (value []string) {
+       if !r.isResponse {
+               value = r.Source.(*GoRequest).Original.Header[key]
+               if len(value) == 0 {
+                       if ihead := r.Source.(*GoRequest).Original.Header.Get(key); ihead != "" {
+                               value = append(value, ihead)
+                       }
+               }
+       } else {
+               value = r.Source.(*GoResponse).Original.Header()[key]
+       }
+       return
+}
+
+// Returns list of header keys
+func (r *GoHeader) GetKeys() (value []string) {
+       if !r.isResponse {
+               for key := range r.Source.(*GoRequest).Original.Header {
+                       value = append(value, key)
+               }
+       } else {
+               for key := range r.Source.(*GoResponse).Original.Header() {
+                       value = append(value, key)
+               }
+       }
+       return
+}
+
+// Sets the status of the header
+func (r *GoHeader) SetStatus(statusCode int) {
+       if r.isResponse {
+               r.Source.(*GoResponse).Original.WriteHeader(statusCode)
+       }
+}
+
+// Return cookies value
+func (r GoCookie) GetValue() string {
+       return r.Value
+}
+
+// Return files from the form
+func (f *GoMultipartForm) GetFiles() map[string][]*multipart.FileHeader {
+       return f.Form.File
+}
+
+// Return values from the form
+func (f *GoMultipartForm) GetValues() url.Values {
+       return url.Values(f.Form.Value)
+}
+
+// Remove all values from the form freeing memory
+func (f *GoMultipartForm) RemoveAll() error {
+       return f.Form.RemoveAll()
+}
+
+/**
+ * Message send JSON
+ */
+func (g *GoWebSocket) MessageSendJSON(v interface{}) error {
+       return websocket.JSON.Send(g.Conn, v)
+}
+
+/**
+ * Message receive JSON
+ */
+func (g *GoWebSocket) MessageReceiveJSON(v interface{}) error {
+       return websocket.JSON.Receive(g.Conn, v)
+}
+
+/**
+ * Message Send
+ */
+func (g *GoWebSocket) MessageSend(v interface{}) error {
+       return websocket.Message.Send(g.Conn, v)
+}
+
+/**
+ * Message receive
+ */
+func (g *GoWebSocket) MessageReceive(v interface{}) error {
+       return websocket.Message.Receive(g.Conn, v)
+}
diff --git a/src/foundation/api/revel/server_test.go b/src/foundation/api/revel/server_test.go
new file mode 100644 (file)
index 0000000..e37fa24
--- /dev/null
@@ -0,0 +1,148 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "github.com/stretchr/testify/assert"
+       "net/http"
+       "net/http/httptest"
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+       "time"
+)
+
+// This tries to benchmark the usual request-serving pipeline to get an overall
+// performance metric.
+//
+// Each iteration runs one mock request to display a hotel's detail page by id.
+//
+// Contributing parts:
+// - Routing
+// - Controller lookup / invocation
+// - Parameter binding
+// - Session, flash, i18n cookies
+// - Render() call magic
+// - Template rendering
+func BenchmarkServeAction(b *testing.B) {
+       benchmarkRequest(b, showRequest)
+}
+
+func BenchmarkServeJson(b *testing.B) {
+       benchmarkRequest(b, jsonRequest)
+}
+
+func BenchmarkServePlaintext(b *testing.B) {
+       benchmarkRequest(b, plaintextRequest)
+}
+
+// This tries to benchmark the static serving overhead when serving an "average
+// size" 7k file.
+func BenchmarkServeStatic(b *testing.B) {
+       benchmarkRequest(b, staticRequest)
+}
+
+func benchmarkRequest(b *testing.B, req *http.Request) {
+       startFakeBookingApp()
+       b.ResetTimer()
+       resp := httptest.NewRecorder()
+       for i := 0; i < b.N; i++ {
+               CurrentEngine.(*GoHttpServer).Handle(resp, req)
+       }
+}
+
+// Test that the booking app can be successfully run for a test.
+func TestFakeServer(t *testing.T) {
+       startFakeBookingApp()
+
+       resp := httptest.NewRecorder()
+
+       // First, test that the expected responses are actually generated
+       CurrentEngine.(*GoHttpServer).Handle(resp, showRequest)
+       if !strings.Contains(resp.Body.String(), "300 Main St.") {
+               t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body)
+               t.FailNow()
+       }
+       resp.Body.Reset()
+
+       CurrentEngine.(*GoHttpServer).Handle(resp, staticRequest)
+       sessvarsSize := getFileSize(t, filepath.Join(BasePath, "public", "js", "sessvars.js"))
+       if int64(resp.Body.Len()) != sessvarsSize {
+               t.Errorf("Expected sessvars.js to have %d bytes, got %d:\n%s", sessvarsSize, resp.Body.Len(), resp.Body)
+               t.FailNow()
+       }
+       resp.Body.Reset()
+
+       CurrentEngine.(*GoHttpServer).Handle(resp, jsonRequest)
+       if !strings.Contains(resp.Body.String(), `"Address":"300 Main St."`) {
+               t.Errorf("Failed to find hotel address in JSON response:\n%s", resp.Body)
+               t.FailNow()
+       }
+       resp.Body.Reset()
+
+       CurrentEngine.(*GoHttpServer).Handle(resp, plaintextRequest)
+       if resp.Body.String() != "Hello, World!" {
+               t.Errorf("Failed to find greeting in plaintext response:\n%s", resp.Body)
+               t.FailNow()
+       }
+
+       resp.Body = nil
+}
+
+func getFileSize(t *testing.T, name string) int64 {
+       fi, err := os.Stat(name)
+       if err != nil {
+               t.Errorf("Unable to stat file:\n%s", name)
+               t.FailNow()
+       }
+       return fi.Size()
+}
+
+// Ensure on app start runs in order
+func TestOnAppStart(t *testing.T) {
+       str := ""
+       a := assert.New(t)
+       OnAppStart(func() {
+               str += " World"
+       }, 2)
+
+       OnAppStart(func() {
+               str += "Hello"
+       }, 1)
+
+       startFakeBookingApp()
+
+       a.Equal("Hello World", str, "Failed to order OnAppStart")
+}
+
+// Ensure on app stop runs in order
+func TestOnAppStop(t *testing.T) {
+       a := assert.New(t)
+       startFakeBookingApp()
+       i := ""
+       OnAppStop(func() {
+               i += "cruel world"
+               t.Logf("i: %v \n", i)
+       }, 2)
+       OnAppStop(func() {
+               i += "goodbye "
+               t.Logf("i: %v \n", i)
+       }, 1)
+       go func() {
+               time.Sleep(2 * time.Second)
+               RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil)
+       }()
+       Run(0)
+       a.Equal("goodbye cruel world", i, "Did not get shutdown events")
+
+}
+
+var (
+       showRequest, _      = http.NewRequest("GET", "/hotels/3", nil)
+       staticRequest, _    = http.NewRequest("GET", "/public/js/sessvars.js", nil)
+       jsonRequest, _      = http.NewRequest("GET", "/hotels/3/booking", nil)
+       plaintextRequest, _ = http.NewRequest("GET", "/hotels", nil)
+)
diff --git a/src/foundation/api/revel/session/init.go b/src/foundation/api/revel/session/init.go
new file mode 100644 (file)
index 0000000..56069e5
--- /dev/null
@@ -0,0 +1,10 @@
+package session
+
+// The logger for the session
+import "github.com/revel/revel/logger"
+
+var sessionLog logger.MultiLogger
+
+func InitSession(coreLogger logger.MultiLogger) {
+       sessionLog = coreLogger.New("section", "session")
+}
diff --git a/src/foundation/api/revel/session/session.go b/src/foundation/api/revel/session/session.go
new file mode 100644 (file)
index 0000000..c5696bc
--- /dev/null
@@ -0,0 +1,364 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package session
+
+import (
+       "encoding/hex"
+       "encoding/json"
+       "errors"
+       "github.com/twinj/uuid"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+const (
+       // The key for the identity of the session
+       SessionIDKey = "_ID"
+       // The expiration date of the session
+       TimestampKey = "_TS"
+       // The value name indicating how long the session should persist - ie should it persist after the browser closes
+       // this is set under the TimestampKey if the session data should expire immediately
+       SessionValueName = "session"
+       // The key container for the json objects of the data, any non strings found in the map will be placed in here
+       // serialized by key using JSON
+       SessionObjectKeyName = "_object_"
+       // The mapped session object
+       SessionMapKeyName = "_map_"
+       // The suffix of the session cookie
+       SessionCookieSuffix = "_SESSION"
+)
+
+// Session data, can be any data, there are reserved keywords used by the storage data
+// SessionIDKey Is the key name for the session
+// TimestampKey Is the time that the session should expire
+//
+type Session map[string]interface{}
+
+func NewSession() Session {
+       return Session{}
+}
+
+// ID retrieves from the cookie or creates a time-based UUID identifying this
+// session.
+func (s Session) ID() string {
+       if sessionIDStr, ok := s[SessionIDKey]; ok {
+               return sessionIDStr.(string)
+       }
+
+       buffer := uuid.NewV4()
+
+       s[SessionIDKey] = hex.EncodeToString(buffer.Bytes())
+       return s[SessionIDKey].(string)
+}
+
+// getExpiration return a time.Time with the session's expiration date.
+// It uses the passed in expireAfterDuration to add with the current time if the timeout is not
+// browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero()
+func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time {
+       if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName {
+               // Expire after closing browser
+               return time.Time{}
+       }
+       return time.Now().Add(expireAfterDuration)
+}
+
+// SetNoExpiration sets session to expire when browser session ends
+func (s Session) SetNoExpiration() {
+       s[TimestampKey] = SessionValueName
+}
+
+// SetDefaultExpiration sets session to expire after default duration
+func (s Session) SetDefaultExpiration() {
+       delete(s, TimestampKey)
+}
+
+// sessionTimeoutExpiredOrMissing returns a boolean of whether the session
+// cookie is either not present or present but beyond its time to live; i.e.,
+// whether there is not a valid session.
+func (s Session) SessionTimeoutExpiredOrMissing() bool {
+       if exp, present := s[TimestampKey]; !present {
+               return true
+       } else if exp == SessionValueName {
+               return false
+       } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() {
+               return true
+       }
+       return false
+}
+
+// Constant error if session value is not found
+var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found")
+
+// Get an object or property from the session
+// it may be embedded inside the session.
+func (s Session) Get(key string) (newValue interface{}, err error) {
+       // First check to see if it is in the session
+       if v, found := s[key]; found {
+               return v, nil
+       }
+       return s.GetInto(key, nil, false)
+}
+
+// Get into the specified value.
+// If value exists in the session it will just return the value
+func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) {
+       if v, found := s[key]; found && !force {
+               return v, nil
+       }
+       splitKey := strings.Split(key, ".")
+       rootKey := splitKey[0]
+
+       // Force always recreates the object from the session data map
+       if force {
+               if target == nil {
+                       if result, err = s.sessionDataFromMap(key); err != nil {
+                               return
+                       }
+               } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil {
+                       return
+               }
+
+               return s.getNestedProperty(splitKey, result)
+       }
+
+       // Attempt to find the key in the session, this is the most generalized form
+       v, found := s[rootKey]
+       if !found {
+               if target == nil {
+                       // Try to fetch it from the session
+
+                       if v, err = s.sessionDataFromMap(rootKey); err != nil {
+                               return
+                       }
+               } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil {
+                       return
+               }
+       }
+
+       return s.getNestedProperty(splitKey, v)
+}
+
+// Returns the default value if the key is not found
+func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} {
+       v, e := s.GetInto(key, value, false)
+       if e != nil {
+               v = defaultValue
+       }
+       return v
+}
+
+// Extract the values from the session
+func (s Session) GetProperty(key string, value interface{}) (interface{}, error) {
+       // Capitalize the first letter
+       key = strings.Title(key)
+
+       sessionLog.Info("getProperty", "key", key, "value", value)
+
+       // For a map it is easy
+       if reflect.TypeOf(value).Kind() == reflect.Map {
+               val := reflect.ValueOf(value)
+               valueOf := val.MapIndex(reflect.ValueOf(key))
+               if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) {
+                       return nil, nil
+               }
+               //idx := val.MapIndex(reflect.ValueOf(key))
+               if !valueOf.IsValid() {
+                       return nil, nil
+               }
+
+               return valueOf.Interface(), nil
+       }
+
+       objValue := s.reflectValue(value)
+       field := objValue.FieldByName(key)
+       if !field.IsValid() {
+               return nil, SESSION_VALUE_NOT_FOUND
+       }
+
+       return field.Interface(), nil
+}
+
+// Places the object into the session, a nil value will cause remove the key from the session
+// (or you can use the Session.Del(key) function
+func (s Session) Set(key string, value interface{}) error {
+       if value == nil {
+               s.Del(key)
+               return nil
+       }
+
+       s[key] = value
+       return nil
+}
+
+// Delete the key from the sessionObjects and Session
+func (s Session) Del(key string) {
+       sessionJsonMap := s.getSessionJsonMap()
+       delete(sessionJsonMap, key)
+       delete(s, key)
+}
+
+// Extracts the session as a map of [string keys] and json values
+func (s Session) getSessionJsonMap() map[string]string {
+       if sessionJson, found := s[SessionObjectKeyName]; found {
+               if _, valid := sessionJson.(map[string]string); !valid {
+                       sessionLog.Error("Session object key corrupted, reset", "was", sessionJson)
+                       s[SessionObjectKeyName] = map[string]string{}
+               }
+               // serialized data inside the session _objects
+       } else {
+               s[SessionObjectKeyName] = map[string]string{}
+       }
+
+       return s[SessionObjectKeyName].(map[string]string)
+}
+
+// Convert the map to a simple map[string]string map
+// this will marshal any non string objects encountered and store them the the jsonMap
+// The expiration time will also be assigned
+func (s Session) Serialize() map[string]string {
+       sessionJsonMap := s.getSessionJsonMap()
+       newMap := map[string]string{}
+       newObjectMap := map[string]string{}
+       for key, value := range sessionJsonMap {
+               newObjectMap[key] = value
+       }
+       for key, value := range s {
+               if key == SessionObjectKeyName || key == SessionMapKeyName {
+                       continue
+               }
+               if reflect.ValueOf(value).Kind() == reflect.String {
+                       newMap[key] = value.(string)
+                       continue
+               }
+               println("Serialize the data for", key)
+               if data, err := json.Marshal(value); err != nil {
+                       sessionLog.Error("Unable to marshal session ", "key", key, "error", err)
+                       continue
+               } else {
+                       newObjectMap[key] = string(data)
+               }
+       }
+       if len(newObjectMap) > 0 {
+               if data, err := json.Marshal(newObjectMap); err != nil {
+                       sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err)
+
+               } else {
+                       newMap[SessionObjectKeyName] = string(data)
+               }
+       }
+
+       return newMap
+}
+
+// Set the session object from the loaded data
+func (s Session) Load(data map[string]string) {
+       for key, value := range data {
+               if key == SessionObjectKeyName {
+                       target := map[string]string{}
+                       if err := json.Unmarshal([]byte(value), &target); err != nil {
+                               sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err)
+                       } else {
+                               s[key] = target
+                       }
+               } else {
+                       s[key] = value
+               }
+
+       }
+}
+
+// Checks to see if the session is empty
+func (s Session) Empty() bool {
+       i := 0
+       for k := range s {
+               i++
+               if k == SessionObjectKeyName || k == SessionMapKeyName {
+                       continue
+               }
+       }
+       return i == 0
+}
+
+func (s *Session) reflectValue(obj interface{}) reflect.Value {
+       var val reflect.Value
+
+       if reflect.TypeOf(obj).Kind() == reflect.Ptr {
+               val = reflect.ValueOf(obj).Elem()
+       } else {
+               val = reflect.ValueOf(obj)
+       }
+
+       return val
+}
+
+// Starting at position 1 drill into the object
+func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) {
+       for x := 1; x < len(keys); x++ {
+               newValue, err = s.GetProperty(keys[x], newValue)
+               if err != nil || newValue == nil {
+                       return newValue, err
+               }
+       }
+       return newValue, nil
+}
+
+// Always converts the data from the session mapped objects into the target,
+// it will store the results under the session key name SessionMapKeyName
+func (s Session) sessionDataFromMap(key string) (result interface{}, err error) {
+       var mapValue map[string]interface{}
+       uncastMapValue, found := s[SessionMapKeyName]
+       if !found {
+               mapValue = map[string]interface{}{}
+               s[SessionMapKeyName] = mapValue
+       } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found {
+               // Unusual means that the value in the session was not expected
+               sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue)
+               mapValue = map[string]interface{}{}
+               s[SessionMapKeyName] = mapValue
+       }
+
+       // Try to extract the key from the map
+       result, found = mapValue[key]
+       if !found {
+               result, err = s.convertSessionData(key, nil)
+               if err == nil {
+                       mapValue[key] = result
+               }
+       }
+       return
+}
+
+// Unpack the object from the session map and store it in the session when done, if no error occurs
+func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) {
+       result, err = s.convertSessionData(key, newValue)
+       if err != nil {
+               return
+       }
+       s[key] = result
+       return
+}
+
+// Converts from the session json map into the target,
+func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) {
+       sessionJsonMap := s.getSessionJsonMap()
+       v, found := sessionJsonMap[key]
+       if !found {
+               return target, SESSION_VALUE_NOT_FOUND
+       }
+
+       // Create a target if needed
+       if target == nil {
+               target = map[string]interface{}{}
+               if err := json.Unmarshal([]byte(v), &target); err != nil {
+                       return target, err
+               }
+       } else if err := json.Unmarshal([]byte(v), target); err != nil {
+               return target, err
+       }
+       result = target
+       return
+}
diff --git a/src/foundation/api/revel/session/session_cookie_test.go b/src/foundation/api/revel/session/session_cookie_test.go
new file mode 100644 (file)
index 0000000..ca6c829
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package session_test
+
+import (
+       "testing"
+
+       "github.com/revel/revel"
+       "github.com/revel/revel/session"
+       "github.com/stretchr/testify/assert"
+       "net/http"
+       "time"
+)
+
+func TestCookieRestore(t *testing.T) {
+       a := assert.New(t)
+       session.InitSession(revel.RevelLog)
+
+       cse := revel.NewSessionCookieEngine()
+       originSession := session.NewSession()
+       setSharedDataTest(originSession)
+       originSession["foo"] = "foo"
+       originSession["bar"] = "bar"
+       cookie := cse.GetCookie(originSession)
+       if !cookie.Expires.IsZero() {
+               t.Error("incorrect cookie expire", cookie.Expires)
+       }
+
+       restoredSession := session.NewSession()
+       cse.DecodeCookie(revel.GoCookie(*cookie), restoredSession)
+       a.Equal("foo",restoredSession["foo"])
+       a.Equal("bar",restoredSession["bar"])
+       testSharedData(originSession, restoredSession, t, a)
+}
+
+func TestCookieSessionExpire(t *testing.T) {
+       session.InitSession(revel.RevelLog)
+       cse := revel.NewSessionCookieEngine()
+       cse.ExpireAfterDuration = time.Hour
+       session := session.NewSession()
+       session["user"] = "Tom"
+       var cookie *http.Cookie
+       for i := 0; i < 3; i++ {
+               cookie = cse.GetCookie(session)
+               time.Sleep(time.Second)
+
+               cse.DecodeCookie(revel.GoCookie(*cookie), session)
+       }
+       expectExpire := time.Now().Add(cse.ExpireAfterDuration)
+       if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() {
+               t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second))
+       }
+       if cookie.Expires.Unix() > expectExpire.Unix() {
+               t.Error("expect expires", cookie.Expires, "before", expectExpire)
+       }
+
+       // Test that the expiration time is zero for a "browser" session
+       session.SetNoExpiration()
+       cookie = cse.GetCookie(session)
+       if !cookie.Expires.IsZero() {
+               t.Error("expect cookie expires is zero")
+       }
+
+       // Check the default session is set
+       session.SetDefaultExpiration()
+       cookie = cse.GetCookie(session)
+       expectExpire = time.Now().Add(cse.ExpireAfterDuration)
+       if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() {
+               t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second))
+       }
+       if cookie.Expires.Unix() > expectExpire.Unix() {
+               t.Error("expect expires", cookie.Expires, "before", expectExpire)
+       }
+}
diff --git a/src/foundation/api/revel/session/session_test.go b/src/foundation/api/revel/session/session_test.go
new file mode 100644 (file)
index 0000000..6be1ce5
--- /dev/null
@@ -0,0 +1,64 @@
+// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package session_test
+
+import (
+       "fmt"
+       "github.com/revel/revel"
+       "github.com/revel/revel/session"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+// test the commands
+func TestSessionString(t *testing.T) {
+       session.InitSession(revel.RevelLog)
+       a := assert.New(t)
+       s := session.NewSession()
+       s.Set("happy", "day")
+       a.Equal("day", s.GetDefault("happy", nil, ""), fmt.Sprintf("Session Data %#v\n", s))
+
+}
+
+func TestSessionStruct(t *testing.T) {
+       session.InitSession(revel.RevelLog)
+       a := assert.New(t)
+       s := session.NewSession()
+       setSharedDataTest(s)
+       a.Equal("test", s.GetDefault("happy.a.aa", nil, ""), fmt.Sprintf("Session Data %#v\n", s))
+
+       stringMap := s.Serialize()
+       s1 := session.NewSession()
+       s1.Load(stringMap)
+       testSharedData(s, s1, t, a)
+
+}
+
+func setSharedDataTest(s session.Session) {
+       data := struct {
+               A struct {
+                       Aa string
+               }
+               B int
+               C string
+               D float32
+       }{A: struct {
+               Aa string
+       }{Aa: "test"},
+               B: 5,
+               C: "test",
+               D: -325.25}
+       s.Set("happy", data)
+}
+func testSharedData(s, s1 session.Session, t *testing.T, a *assert.Assertions) {
+       // Compress the session to a string
+       t.Logf("Original session %#v\n", s)
+       t.Logf("New built session %#v\n", s1)
+       data,err := s1.Get("happy.a.aa")
+       a.Nil(err,"Expected nil")
+       a.Equal("test", data, fmt.Sprintf("Session Data %#v\n", s))
+       t.Logf("After test session %#v\n", s1)
+
+}
diff --git a/src/foundation/api/revel/session_adapter_cookie.go b/src/foundation/api/revel/session_adapter_cookie.go
new file mode 100644 (file)
index 0000000..ebe871d
--- /dev/null
@@ -0,0 +1,144 @@
+package revel
+
+import (
+       "fmt"
+       "github.com/revel/revel/session"
+       "net/http"
+       "net/url"
+       "strconv"
+       "strings"
+       "time"
+)
+
+
+type (
+       // The session cookie engine
+       SessionCookieEngine struct {
+               ExpireAfterDuration time.Duration
+       }
+)
+
+// A logger for the session engine
+var sessionEngineLog = RevelLog.New("section", "session-engine")
+
+// Create a new instance to test
+func init() {
+       RegisterSessionEngine(initCookieEngine, "revel-cookie")
+}
+
+// For testing purposes this engine is used
+func NewSessionCookieEngine() *SessionCookieEngine {
+       ce := &SessionCookieEngine{}
+       return ce
+}
+
+// Called when the the application starts, retrieves data from the app config so cannot be run until then
+func initCookieEngine() SessionEngine {
+       ce := &SessionCookieEngine{}
+
+       var err error
+       if expiresString, ok := Config.String("session.expires"); !ok {
+               ce.ExpireAfterDuration = 30 * 24 * time.Hour
+       } else if expiresString == session.SessionValueName {
+               ce.ExpireAfterDuration = 0
+       } else if ce.ExpireAfterDuration, err = time.ParseDuration(expiresString); err != nil {
+               panic(fmt.Errorf("session.expires invalid: %s", err))
+       }
+
+       return ce
+}
+
+// Decode the session information from the cookie retrieved from the controller request
+func (cse *SessionCookieEngine) Decode(c *Controller) {
+       // Decode the session from a cookie.
+       c.Session = map[string]interface{}{}
+       sessionMap := c.Session
+       if cookie, err := c.Request.Cookie(CookiePrefix + session.SessionCookieSuffix); err != nil {
+               return
+       } else {
+               cse.DecodeCookie(cookie, sessionMap)
+               c.Session = sessionMap
+       }
+}
+
+// Encode the session information to the cookie, set the cookie on the controller
+func (cse *SessionCookieEngine) Encode(c *Controller) {
+
+       c.SetCookie(cse.GetCookie(c.Session))
+}
+
+// Exposed only for testing purposes
+func (cse *SessionCookieEngine) DecodeCookie(cookie ServerCookie, s session.Session) {
+       // Decode the session from a cookie.
+       // Separate the data from the signature.
+       cookieValue := cookie.GetValue()
+       hyphen := strings.Index(cookieValue, "-")
+       if hyphen == -1 || hyphen >= len(cookieValue)-1 {
+               return
+       }
+       sig, data := cookieValue[:hyphen], cookieValue[hyphen+1:]
+
+       // Verify the signature.
+       if !Verify(data, sig) {
+               sessionEngineLog.Warn("Session cookie signature failed")
+               return
+       }
+
+       // Parse the cookie into a temp map, and then load it into the session object
+       tempMap := map[string]string{}
+       ParseKeyValueCookie(data, func(key, val string) {
+               tempMap[key] = val
+       })
+       s.Load(tempMap)
+
+       // Check timeout after unpacking values - if timeout missing (or removed) destroy all session
+       // objects
+       if s.SessionTimeoutExpiredOrMissing() {
+               // If this fails we need to delete all the keys from the session
+               for key := range s {
+                       delete(s, key)
+               }
+       }
+}
+
+// Convert session to cookie
+func (cse *SessionCookieEngine) GetCookie(s session.Session) *http.Cookie {
+       var sessionValue string
+       ts := s.GetExpiration(cse.ExpireAfterDuration)
+       if ts.IsZero() {
+               s[session.TimestampKey] = session.SessionValueName
+       } else {
+               s[session.TimestampKey] = strconv.FormatInt(ts.Unix(), 10)
+       }
+
+       // Convert the key to a string map
+       stringMap := s.Serialize()
+
+       for key, value := range stringMap {
+               if strings.ContainsAny(key, ":\x00") {
+                       panic("Session keys may not have colons or null bytes")
+               }
+               if strings.Contains(value, "\x00") {
+                       panic("Session values may not have null bytes")
+               }
+               sessionValue += "\x00" + key + ":" + value + "\x00"
+       }
+
+       if len(sessionValue) > 1024*4 {
+               sessionEngineLog.Error("SessionCookieEngine.Cookie, session data has exceeded 4k limit (%d) cookie data will not be reliable", "length", len(sessionValue))
+       }
+
+       sessionData := url.QueryEscape(sessionValue)
+       sessionCookie := &http.Cookie{
+               Name:     CookiePrefix + session.SessionCookieSuffix,
+               Value:    Sign(sessionData) + "-" + sessionData,
+               Domain:   CookieDomain,
+               Path:     "/",
+               HttpOnly: true,
+               Secure:   CookieSecure,
+               Expires:  ts.UTC(),
+               MaxAge:   int(cse.ExpireAfterDuration.Seconds()),
+       }
+       return sessionCookie
+
+}
diff --git a/src/foundation/api/revel/session_engine.go b/src/foundation/api/revel/session_engine.go
new file mode 100644 (file)
index 0000000..1f95d7b
--- /dev/null
@@ -0,0 +1,35 @@
+package revel
+
+// The session engine provides an interface to allow for storage of session data
+type (
+       SessionEngine interface {
+               Decode(c *Controller) // Called to decode the session information on the controller
+               Encode(c *Controller) // Called to encode the session information on the controller
+       }
+)
+
+var (
+       sessionEngineMap     = map[string]func() SessionEngine{}
+       CurrentSessionEngine SessionEngine
+)
+
+// Initialize session engine on startup
+func init() {
+       OnAppStart(initSessionEngine, 5)
+}
+
+func RegisterSessionEngine(f func() SessionEngine, name string) {
+       sessionEngineMap[name] = f
+}
+
+// Called when application is starting up
+func initSessionEngine() {
+       // Check for session engine to use and assign it
+       sename := Config.StringDefault("session.engine", "revel-cookie")
+       if se, found := sessionEngineMap[sename]; found {
+               CurrentSessionEngine = se()
+       } else {
+               sessionLog.Warn("Session engine '%s' not found, using default session engine revel-cookie", sename)
+               CurrentSessionEngine = sessionEngineMap["revel-cookie"]()
+       }
+}
diff --git a/src/foundation/api/revel/session_filter.go b/src/foundation/api/revel/session_filter.go
new file mode 100644 (file)
index 0000000..f10470a
--- /dev/null
@@ -0,0 +1,25 @@
+package revel
+
+// SessionFilter is a Revel Filter that retrieves and sets the session cookie.
+// Within Revel, it is available as a Session attribute on Controller instances.
+// The name of the Session cookie is set as CookiePrefix + "_SESSION".
+import ()
+
+var sessionLog = RevelLog.New("section", "session")
+
+func SessionFilter(c *Controller, fc []Filter) {
+       CurrentSessionEngine.Decode(c)
+       sessionWasEmpty := c.Session.Empty()
+
+       // Make session vars available in templates as {{.session.xyz}}
+       c.ViewArgs["session"] = c.Session
+       c.ViewArgs["_controller"] = c
+
+       fc[0](c, fc[1:])
+
+       // If session is not empty or if session was not empty then
+       // pass it back to the session engine to be encoded
+       if !c.Session.Empty() || !sessionWasEmpty {
+               CurrentSessionEngine.Encode(c)
+       }
+}
diff --git a/src/foundation/api/revel/template.go b/src/foundation/api/revel/template.go
new file mode 100644 (file)
index 0000000..c5bd91e
--- /dev/null
@@ -0,0 +1,477 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "bufio"
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strconv"
+       "strings"
+       "sync"
+       "sync/atomic"
+)
+
+// ErrorCSSClass httml CSS error class name
+var ErrorCSSClass = "hasError"
+
+// TemplateLoader object handles loading and parsing of templates.
+// Everything below the application's views directory is treated as a template.
+type TemplateLoader struct {
+       // Paths to search for templates, in priority order.
+       paths []string
+       // load version seed for templates
+       loadVersionSeed int
+       // A templateRuntime of looked up template results
+       runtimeLoader atomic.Value
+       // Lock to prevent concurrent map writes
+       templateMutex sync.Mutex
+}
+
+type Template interface {
+       // The name of the template.
+       Name() string // Name of template
+       // The content of the template as a string (Used in error handling).
+       Content() []string // Content
+       // Called by the server to render the template out the io.Writer, context contains the view args to be passed to the template.
+       Render(wr io.Writer, context interface{}) error
+       // The full path to the file on the disk.
+       Location() string // Disk location
+}
+
+var invalidSlugPattern = regexp.MustCompile(`[^a-z0-9 _-]`)
+var whiteSpacePattern = regexp.MustCompile(`\s+`)
+var templateLog = RevelLog.New("section", "template")
+
+// TemplateOutputArgs returns the result of the template rendered using the passed in arguments.
+func TemplateOutputArgs(templatePath string, args map[string]interface{}) (data []byte, err error) {
+       // Get the Template.
+       lang, _ := args[CurrentLocaleViewArg].(string)
+       template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
+       if err != nil {
+               return nil, err
+       }
+       tr := &RenderTemplateResult{
+               Template: template,
+               ViewArgs: args,
+       }
+       b, err := tr.ToBytes()
+       if err != nil {
+               return nil, err
+       }
+       return b.Bytes(), nil
+}
+
+func NewTemplateLoader(paths []string) *TemplateLoader {
+       loader := &TemplateLoader{
+               paths: paths,
+       }
+       return loader
+}
+
+// WatchDir returns true of directory doesn't start with . (dot)
+// otherwise false
+func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool {
+       // Watch all directories, except the ones starting with a dot.
+       return !strings.HasPrefix(info.Name(), ".")
+}
+
+// WatchFile returns true of file doesn't start with . (dot)
+// otherwise false
+func (loader *TemplateLoader) WatchFile(basename string) bool {
+       // Watch all files, except the ones starting with a dot.
+       return !strings.HasPrefix(basename, ".")
+}
+
+// DEPRECATED Use TemplateLang, will be removed in future release
+func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) {
+       runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
+       return runtimeLoader.TemplateLang(name, "")
+}
+
+func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) {
+       runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime)
+       return runtimeLoader.TemplateLang(name, lang)
+}
+
+// Refresh method scans the views directory and parses all templates as Go Templates.
+// If a template fails to parse, the error is set on the loader.
+// (It's awkward to refresh a single Go Template)
+func (loader *TemplateLoader) Refresh() (err *Error) {
+       loader.templateMutex.Lock()
+       defer loader.templateMutex.Unlock()
+
+       loader.loadVersionSeed++
+       runtimeLoader := &templateRuntime{loader: loader,
+               version:     loader.loadVersionSeed,
+               templateMap: map[string]Template{}}
+
+       templateLog.Debug("Refresh: Refreshing templates from ", "path", loader.paths)
+       if err = loader.initializeEngines(runtimeLoader, Config.StringDefault("template.engines", GO_TEMPLATE)); err != nil {
+               return
+       }
+       for _, engine := range runtimeLoader.templatesAndEngineList {
+               engine.Event(TEMPLATE_REFRESH_REQUESTED, nil)
+       }
+       RaiseEvent(TEMPLATE_REFRESH_REQUESTED, nil)
+       defer func() {
+               for _, engine := range runtimeLoader.templatesAndEngineList {
+                       engine.Event(TEMPLATE_REFRESH_COMPLETED, nil)
+               }
+               RaiseEvent(TEMPLATE_REFRESH_COMPLETED, nil)
+
+               // Reset the runtimeLoader
+               loader.runtimeLoader.Store(runtimeLoader)
+       }()
+
+       // Resort the paths, make sure the revel path is the last path,
+       // so anything can override it
+       revelTemplatePath := filepath.Join(RevelPath, "templates")
+       // Go through the paths
+       for i, o := range loader.paths {
+               if o == revelTemplatePath && i != len(loader.paths)-1 {
+                       loader.paths[i] = loader.paths[len(loader.paths)-1]
+                       loader.paths[len(loader.paths)-1] = revelTemplatePath
+               }
+       }
+       templateLog.Debug("Refresh: Refreshing templates from", "path", loader.paths)
+
+       runtimeLoader.compileError = nil
+       runtimeLoader.TemplatePaths = map[string]string{}
+
+       for _, basePath := range loader.paths {
+               // Walk only returns an error if the template loader is completely unusable
+               // (namely, if one of the TemplateFuncs does not have an acceptable signature).
+
+               // Handling symlinked directories
+               var fullSrcDir string
+               f, err := os.Lstat(basePath)
+               if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
+                       fullSrcDir, err = filepath.EvalSymlinks(basePath)
+                       if err != nil {
+                               templateLog.Panic("Refresh: Eval symlinks error ", "error", err)
+                       }
+               } else {
+                       fullSrcDir = basePath
+               }
+
+               var templateWalker filepath.WalkFunc
+
+               templateWalker = func(path string, info os.FileInfo, err error) error {
+                       if err != nil {
+                               templateLog.Error("Refresh: error walking templates:", "error", err)
+                               return nil
+                       }
+
+                       // Walk into watchable directories
+                       if info.IsDir() {
+                               if !loader.WatchDir(info) {
+                                       return filepath.SkipDir
+                               }
+                               return nil
+                       }
+
+                       // Only add watchable
+                       if !loader.WatchFile(info.Name()) {
+                               return nil
+                       }
+
+                       fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath)
+                       if err != nil {
+                               // Add in this template name to the list of templates unable to be compiled
+                               runtimeLoader.compileErrorNameList = append(runtimeLoader.compileErrorNameList, filepath.ToSlash(path[len(fullSrcDir)+1:]))
+                       }
+                       // Store / report the first error encountered.
+                       if err != nil && runtimeLoader.compileError == nil {
+                               runtimeLoader.compileError, _ = err.(*Error)
+
+                               if nil == runtimeLoader.compileError {
+                                       _, line, description := ParseTemplateError(err)
+
+                                       runtimeLoader.compileError = &Error{
+                                               Title:       "Template Compilation Error",
+                                               Path:        path,
+                                               Description: description,
+                                               Line:        line,
+                                               SourceLines: strings.Split(string(fileBytes), "\n"),
+                                       }
+                               }
+                               templateLog.Errorf("Refresh: Template compilation error (In %s around line %d):\n\t%s",
+                                       path, runtimeLoader.compileError.Line, err.Error())
+                       } else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
+
+                               if compileError, ok := err.(*Error); ok {
+                                       templateLog.Errorf("Template compilation error (In %s around line %d):\n\t%s",
+                                               path, compileError.Line, err.Error())
+                               } else {
+                                       templateLog.Errorf("Template compilation error (In %s ):\n\t%s",
+                                               path, err.Error())
+                               }
+                       }
+                       return nil
+               }
+
+               if _, err = os.Lstat(fullSrcDir); os.IsNotExist(err) {
+                       // #1058 Given views/template path is not exists
+                       // so no need to walk, move on to next path
+                       continue
+               }
+
+               funcErr := Walk(fullSrcDir, templateWalker)
+
+               // If there was an error with the Funcs, set it and return immediately.
+               if funcErr != nil {
+                       runtimeLoader.compileError = NewErrorFromPanic(funcErr)
+                       return runtimeLoader.compileError
+               }
+       }
+
+       // Note: compileError may or may not be set.
+       return runtimeLoader.compileError
+}
+
+type templateRuntime struct {
+       loader *TemplateLoader
+       // load version for templates
+       version int
+       // Template data and implementation
+       templatesAndEngineList []TemplateEngine
+       // If an error was encountered parsing the templates, it is stored here.
+       compileError *Error
+       // A list of the names of the templates with errors
+       compileErrorNameList []string
+       // Map from template name to the path from whence it was loaded.
+       TemplatePaths map[string]string
+       // A map of looked up template results
+       templateMap map[string]Template
+}
+
+// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
+// reads the template file into memory, replaces namespace keys with module (if found
+func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) {
+       templateName := filepath.ToSlash(path[len(fullSrcDir)+1:])
+       // Convert template names to use forward slashes, even on Windows.
+       if os.PathSeparator == '\\' {
+               templateName = strings.Replace(templateName, `\`, `/`, -1) // `
+       }
+
+       // Check to see if template was found
+       if place, found := runtimeLoader.TemplatePaths[templateName]; found {
+               templateLog.Debug("findAndAddTemplate: Not Loading, template is already exists: ", "name", templateName, "old",
+                       place, "new", path)
+               return
+       }
+
+       fileBytes, err = ioutil.ReadFile(path)
+       if err != nil {
+               templateLog.Error("findAndAddTemplate: Failed reading file:", "path", path, "error", err)
+               return
+       }
+       // Parse template file and replace the "_LOCAL_|" in the template with the module name
+       // allow for namespaces to be renamed "_LOCAL_(.*?)|"
+       if module := ModuleFromPath(path, false); module != nil {
+               fileBytes = namespaceReplace(fileBytes, module)
+       }
+
+       // if we have an engine picked for this template process it now
+       baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes)
+
+       // Try to find a default engine for the file
+       for _, engine := range runtimeLoader.templatesAndEngineList {
+               if engine.Handles(baseTemplate) {
+                       _, err = runtimeLoader.loadIntoEngine(engine, baseTemplate)
+                       return
+               }
+       }
+
+       // Try all engines available
+       var defaultError error
+       for _, engine := range runtimeLoader.templatesAndEngineList {
+               if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded {
+                       return
+               } else {
+                       templateLog.Debugf("findAndAddTemplate: Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr.Error())
+                       if defaultError == nil {
+                               defaultError = loaderr
+                       }
+               }
+       }
+
+       // Assign the error from the first parser
+       err = defaultError
+
+       // No engines could be found return the err
+       if err == nil {
+               err = fmt.Errorf("Failed to parse template file using engines %s", path)
+       }
+
+       return
+}
+
+func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) {
+       if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found {
+               // Duplicate template found in map
+               templateLog.Debug("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:",
+                       loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath)
+               return
+       }
+
+       if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil {
+               // Duplicate template found for engine
+               templateLog.Debug("loadIntoEngine: template already exists: ", "template", baseTemplate.TemplateName, "inengine ", engine.Name(), "old",
+                       loadedTemplate.Location(), "new", baseTemplate.FilePath)
+               loaded = true
+               return
+       }
+       if err = engine.ParseAndAdd(baseTemplate); err == nil {
+               if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil {
+                       runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl
+               }
+               runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath
+               templateLog.Debugf("loadIntoEngine:Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath)
+               loaded = true
+       } else {
+               templateLog.Debug("loadIntoEngine: Engine failed to compile", "engine", engine.Name(), "file", baseTemplate.FilePath, "error", err)
+       }
+       return
+}
+
+// Parse the line, and description from an error message like:
+// html/template:Application/Register.html:36: no such template "footer.html"
+func ParseTemplateError(err error) (templateName string, line int, description string) {
+       if e, ok := err.(*Error); ok {
+               return "", e.Line, e.Description
+       }
+
+       description = err.Error()
+       i := regexp.MustCompile(`:\d+:`).FindStringIndex(description)
+       if i != nil {
+               line, err = strconv.Atoi(description[i[0]+1 : i[1]-1])
+               if err != nil {
+                       templateLog.Error("ParseTemplateError: Failed to parse line number from error message:", "error", err)
+               }
+               templateName = description[:i[0]]
+               if colon := strings.Index(templateName, ":"); colon != -1 {
+                       templateName = templateName[colon+1:]
+               }
+               templateName = strings.TrimSpace(templateName)
+               description = description[i[1]+1:]
+       }
+       return templateName, line, description
+}
+
+// Template returns the Template with the given name.  The name is the template's path
+// relative to a template loader root.
+//
+// An Error is returned if there was any problem with any of the templates.  (In
+// this case, if a template is returned, it may still be usable.)
+func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) {
+       if runtimeLoader.compileError != nil {
+               for _, errName := range runtimeLoader.compileErrorNameList {
+                       if name == errName {
+                               return nil, runtimeLoader.compileError
+                       }
+               }
+       }
+
+       // Fetch the template from the map
+       tmpl = runtimeLoader.templateLoad(name, lang)
+       if tmpl == nil {
+               err = fmt.Errorf("Template %s not found.", name)
+       }
+
+       return
+}
+
+// Load and also updates map if name is not found (to speed up next lookup)
+func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) {
+       langName := name
+       found := false
+       if lang != "" {
+               // Look up and return the template.
+               langName = name + "." + lang
+               tmpl, found = runtimeLoader.templateMap[langName]
+               if found {
+                       return
+               }
+               tmpl, found = runtimeLoader.templateMap[name]
+       } else {
+               tmpl, found = runtimeLoader.templateMap[name]
+               if found {
+                       return
+               }
+       }
+
+       if !found {
+               // Neither name is found
+               // Look up and return the template.
+               for _, engine := range runtimeLoader.templatesAndEngineList {
+                       if tmpl = engine.Lookup(langName); tmpl != nil {
+                               found = true
+                               break
+                       }
+                       if tmpl = engine.Lookup(name); tmpl != nil {
+                               found = true
+                               break
+                       }
+               }
+               if !found {
+                       return
+               }
+       }
+
+       // If we found anything store it in the map, we need to copy so we do not
+       // run into concurrency issues
+       runtimeLoader.loader.templateMutex.Lock()
+       defer runtimeLoader.loader.templateMutex.Unlock()
+
+       // In case another thread has loaded the map, reload the atomic value and check
+       newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime)
+       if newRuntimeLoader.version != runtimeLoader.version {
+               return
+       }
+
+       newTemplateMap := map[string]Template{}
+       for k, v := range newRuntimeLoader.templateMap {
+               newTemplateMap[k] = v
+       }
+       newTemplateMap[langName] = tmpl
+       if _, found := newTemplateMap[name]; !found {
+               newTemplateMap[name] = tmpl
+       }
+       runtimeCopy := &templateRuntime{}
+       *runtimeCopy = *newRuntimeLoader
+       runtimeCopy.templateMap = newTemplateMap
+
+       // Set the atomic value
+       runtimeLoader.loader.runtimeLoader.Store(runtimeCopy)
+       return
+}
+
+func (i *TemplateView) Location() string {
+       return i.FilePath
+}
+
+func (i *TemplateView) Content() (content []string) {
+       if i.FileBytes != nil {
+               // Parse the bytes
+               buffer := bytes.NewBuffer(i.FileBytes)
+               reader := bufio.NewScanner(buffer)
+               for reader.Scan() {
+                       content = append(content, string(reader.Bytes()))
+               }
+       }
+
+       return content
+}
+
+func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView {
+       return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath}
+}
diff --git a/src/foundation/api/revel/template_adapter_go.go b/src/foundation/api/revel/template_adapter_go.go
new file mode 100644 (file)
index 0000000..4111307
--- /dev/null
@@ -0,0 +1,129 @@
+package revel
+
+import (
+       "html/template"
+       "io"
+       "log"
+       "strings"
+)
+
+const GO_TEMPLATE = "go"
+
+// Called on startup, initialized when the REVEL_BEFORE_MODULES_LOADED is called
+func init() {
+       AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+               if typeOf == REVEL_BEFORE_MODULES_LOADED {
+                       RegisterTemplateLoader(GO_TEMPLATE, func(loader *TemplateLoader) (TemplateEngine, error) {
+                               // Set the template delimiters for the project if present, then split into left
+                               // and right delimiters around a space character
+
+                               TemplateDelims := Config.StringDefault("template.go.delimiters", "")
+                               var splitDelims []string
+                               if TemplateDelims != "" {
+                                       splitDelims = strings.Split(TemplateDelims, " ")
+                                       if len(splitDelims) != 2 {
+                                               log.Fatalln("app.conf: Incorrect format for template.delimiters")
+                                       }
+                               }
+
+                               return &GoEngine{
+                                       loader:          loader,
+                                       templateSet:     template.New("__root__").Funcs(TemplateFuncs),
+                                       templatesByName: map[string]*GoTemplate{},
+                                       splitDelims:     splitDelims,
+                               }, nil
+                       })
+               }
+               return
+       })
+}
+
+// Adapter for Go Templates.
+type GoTemplate struct {
+       *template.Template
+       engine *GoEngine
+       *TemplateView
+}
+
+// return a 'revel.Template' from Go's template.
+func (gotmpl GoTemplate) Render(wr io.Writer, arg interface{}) error {
+       return gotmpl.Execute(wr, arg)
+}
+
+// The main template engine for Go
+type GoEngine struct {
+       // The template loader
+       loader *TemplateLoader
+       // THe current template set
+       templateSet *template.Template
+       // A map of templates by name
+       templatesByName map[string]*GoTemplate
+       // The delimiter that is used to indicate template code, defaults to {{
+       splitDelims []string
+       // True if map is case insensitive
+       CaseInsensitive bool
+}
+
+// Convert the path to lower case if needed
+func (i *GoEngine) ConvertPath(path string) string {
+       if i.CaseInsensitive {
+               return strings.ToLower(path)
+       }
+       return path
+}
+
+// Returns true if this engine can handle the response
+func (i *GoEngine) Handles(templateView *TemplateView) bool {
+       return EngineHandles(i, templateView)
+}
+
+// Parses the template vide and adds it to the template set
+func (engine *GoEngine) ParseAndAdd(baseTemplate *TemplateView) error {
+       // If alternate delimiters set for the project, change them for this set
+       if engine.splitDelims != nil && strings.Index(baseTemplate.Location(), ViewsPath) > -1 {
+               engine.templateSet.Delims(engine.splitDelims[0], engine.splitDelims[1])
+       } else {
+               // Reset to default otherwise
+               engine.templateSet.Delims("", "")
+       }
+       templateSource := string(baseTemplate.FileBytes)
+       templateName := engine.ConvertPath(baseTemplate.TemplateName)
+       tpl, err := engine.templateSet.New(baseTemplate.TemplateName).Parse(templateSource)
+       if nil != err {
+               _, line, description := ParseTemplateError(err)
+               return &Error{
+                       Title:       "Template Compilation Error",
+                       Path:        baseTemplate.TemplateName,
+                       Description: description,
+                       Line:        line,
+                       SourceLines: strings.Split(templateSource, "\n"),
+               }
+       }
+       engine.templatesByName[templateName] = &GoTemplate{Template: tpl, engine: engine, TemplateView: baseTemplate}
+       return nil
+}
+
+// Lookups the template name, to see if it is contained in this engine
+func (engine *GoEngine) Lookup(templateName string) Template {
+       // Case-insensitive matching of template file name
+       if tpl, found := engine.templatesByName[engine.ConvertPath(templateName)]; found {
+               return tpl
+       }
+       return nil
+}
+
+// Return the engine name
+func (engine *GoEngine) Name() string {
+       return GO_TEMPLATE
+}
+
+// An event listener to listen for Revel INIT events
+func (engine *GoEngine) Event(action Event, i interface{}) {
+       if action == TEMPLATE_REFRESH_REQUESTED {
+               // At this point all the templates have been passed into the
+               engine.templatesByName = map[string]*GoTemplate{}
+               engine.templateSet = template.New("__root__").Funcs(TemplateFuncs)
+               // Check to see what should be used for case sensitivity
+               engine.CaseInsensitive = Config.BoolDefault("go.template.caseinsensitive", true)
+       }
+}
diff --git a/src/foundation/api/revel/template_engine.go b/src/foundation/api/revel/template_engine.go
new file mode 100644 (file)
index 0000000..a62f5f2
--- /dev/null
@@ -0,0 +1,118 @@
+package revel
+
+import (
+       "bufio"
+       "bytes"
+       "errors"
+       "fmt"
+       "path/filepath"
+       "strings"
+)
+
+type TemplateEngine interface {
+       // prase template string and add template to the set.
+       ParseAndAdd(basePath *TemplateView) error
+
+       // returns Template corresponding to the given templateName, or nil
+       Lookup(templateName string) Template
+
+       // Fired by the template loader when events occur
+       Event(event Event, arg interface{})
+
+       // returns true if this engine should be used to parse the file specified in baseTemplate
+       Handles(templateView *TemplateView) bool
+
+       // returns the name of the engine
+       Name() string
+}
+
+// The template view information
+type TemplateView struct {
+       TemplateName string // The name of the view
+       FilePath     string // The file path (view relative)
+       BasePath     string // The file system base path
+       FileBytes    []byte // The file loaded
+       EngineType   string // The name of the engine used to render the view
+}
+
+var templateLoaderMap = map[string]func(loader *TemplateLoader) (TemplateEngine, error){}
+
+// Allow for templates to be registered during init but not initialized until application has been started
+func RegisterTemplateLoader(key string, loader func(loader *TemplateLoader) (TemplateEngine, error)) (err error) {
+       if _, found := templateLoaderMap[key]; found {
+               err = fmt.Errorf("Template loader %s already exists", key)
+       }
+       templateLog.Debug("Registered template engine loaded", "name", key)
+       templateLoaderMap[key] = loader
+       return
+}
+
+// Sets the template name from Config
+// Sets the template API methods for parsing and storing templates before rendering
+func (loader *TemplateLoader) CreateTemplateEngine(templateEngineName string) (TemplateEngine, error) {
+       if "" == templateEngineName {
+               templateEngineName = GO_TEMPLATE
+       }
+       factory := templateLoaderMap[templateEngineName]
+       if nil == factory {
+               fmt.Printf("registered factories %#v\n %s \n", templateLoaderMap, templateEngineName)
+               return nil, errors.New("Unknown template engine name - " + templateEngineName + ".")
+       }
+       templateEngine, err := factory(loader)
+       if nil != err {
+               return nil, errors.New("Failed to init template engine (" + templateEngineName + "), " + err.Error())
+       }
+
+       templateLog.Debug("CreateTemplateEngine: init templates", "name", templateEngineName)
+       return templateEngine, nil
+}
+
+// Passing in a comma delimited list of engine names to be used with this loader to parse the template files
+func (loader *TemplateLoader) initializeEngines(runtimeLoader *templateRuntime, templateEngineNameList string) (err *Error) {
+       // Walk through the template loader's paths and build up a template set.
+       if templateEngineNameList == "" {
+               templateEngineNameList = GO_TEMPLATE
+
+       }
+       runtimeLoader.templatesAndEngineList = []TemplateEngine{}
+       for _, engine := range strings.Split(templateEngineNameList, ",") {
+               engine := strings.TrimSpace(strings.ToLower(engine))
+
+               if templateLoader, err := loader.CreateTemplateEngine(engine); err != nil {
+                       runtimeLoader.compileError = &Error{
+                               Title:       "Panic (Template Loader)",
+                               Description: err.Error(),
+                       }
+                       return runtimeLoader.compileError
+               } else {
+                       // Always assign a default engine, switch it if it is specified in the config
+                       runtimeLoader.templatesAndEngineList = append(runtimeLoader.templatesAndEngineList, templateLoader)
+               }
+       }
+       return
+}
+
+func EngineHandles(engine TemplateEngine, templateView *TemplateView) bool {
+       if line, _, e := bufio.NewReader(bytes.NewBuffer(templateView.FileBytes)).ReadLine(); e == nil && string(line[:3]) == "#! " {
+               // Extract the shebang and look at the rest of the line
+               // #! pong2
+               // #! go
+               templateType := strings.TrimSpace(string(line[2:]))
+               if engine.Name() == templateType {
+                       // Advance the read file bytes so it does not include the shebang
+                       templateView.FileBytes = templateView.FileBytes[len(line)+1:]
+                       templateView.EngineType = templateType
+                       return true
+               }
+       }
+       filename := filepath.Base(templateView.FilePath)
+       bits := strings.Split(filename, ".")
+       if len(bits) > 2 {
+               templateType := strings.TrimSpace(bits[len(bits)-2])
+               if engine.Name() == templateType {
+                       templateView.EngineType = templateType
+                       return true
+               }
+       }
+       return false
+}
diff --git a/src/foundation/api/revel/template_functions.go b/src/foundation/api/revel/template_functions.go
new file mode 100644 (file)
index 0000000..98bb988
--- /dev/null
@@ -0,0 +1,341 @@
+package revel
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "github.com/xeonx/timeago"
+       "html"
+       "html/template"
+       "reflect"
+       "strings"
+       "time"
+)
+
+var (
+       // The functions available for use in the templates.
+       TemplateFuncs = map[string]interface{}{
+               "url": ReverseURL,
+               "set": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS {
+                       viewArgs[key] = value
+                       return template.JS("")
+               },
+               "append": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS {
+                       if viewArgs[key] == nil {
+                               viewArgs[key] = []interface{}{value}
+                       } else {
+                               viewArgs[key] = append(viewArgs[key].([]interface{}), value)
+                       }
+                       return template.JS("")
+               },
+               "field": NewField,
+               "firstof": func(args ...interface{}) interface{} {
+                       for _, val := range args {
+                               switch val.(type) {
+                               case nil:
+                                       continue
+                               case string:
+                                       if val == "" {
+                                               continue
+                                       }
+                                       return val
+                               default:
+                                       return val
+                               }
+                       }
+                       return nil
+               },
+               "option": func(f *Field, val interface{}, label string) template.HTML {
+                       selected := ""
+                       if f.Flash() == val || (f.Flash() == "" && f.Value() == val) {
+                               selected = " selected"
+                       }
+
+                       return template.HTML(fmt.Sprintf(`<option value="%s"%s>%s</option>`,
+                               html.EscapeString(fmt.Sprintf("%v", val)), selected, html.EscapeString(label)))
+               },
+               "radio": func(f *Field, val string) template.HTML {
+                       checked := ""
+                       if f.Flash() == val {
+                               checked = " checked"
+                       }
+                       return template.HTML(fmt.Sprintf(`<input type="radio" name="%s" value="%s"%s>`,
+                               html.EscapeString(f.Name), html.EscapeString(val), checked))
+               },
+               "checkbox": func(f *Field, val string) template.HTML {
+                       checked := ""
+                       if f.Flash() == val {
+                               checked = " checked"
+                       }
+                       return template.HTML(fmt.Sprintf(`<input type="checkbox" name="%s" value="%s"%s>`,
+                               html.EscapeString(f.Name), html.EscapeString(val), checked))
+               },
+               // Pads the given string with &nbsp;'s up to the given width.
+               "pad": func(str string, width int) template.HTML {
+                       if len(str) >= width {
+                               return template.HTML(html.EscapeString(str))
+                       }
+                       return template.HTML(html.EscapeString(str) + strings.Repeat("&nbsp;", width-len(str)))
+               },
+
+               "errorClass": func(name string, viewArgs map[string]interface{}) template.HTML {
+                       errorMap, ok := viewArgs["errors"].(map[string]*ValidationError)
+                       if !ok || errorMap == nil {
+                               templateLog.Warn("errorClass: Called 'errorClass' without 'errors' in the view args.")
+                               return template.HTML("")
+                       }
+                       valError, ok := errorMap[name]
+                       if !ok || valError == nil {
+                               return template.HTML("")
+                       }
+                       return template.HTML(ErrorCSSClass)
+               },
+
+               "msg": func(viewArgs map[string]interface{}, message string, args ...interface{}) template.HTML {
+                       str, ok := viewArgs[CurrentLocaleViewArg].(string)
+                       if !ok {
+                               return ""
+                       }
+                       return template.HTML(MessageFunc(str, message, args...))
+               },
+
+               // Replaces newlines with <br>
+               "nl2br": func(text string) template.HTML {
+                       return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
+               },
+
+               // Skips sanitation on the parameter.  Do not use with dynamic data.
+               "raw": func(text string) template.HTML {
+                       return template.HTML(text)
+               },
+
+               // Pluralize, a helper for pluralizing words to correspond to data of dynamic length.
+               // items - a slice of items, or an integer indicating how many items there are.
+               // pluralOverrides - optional arguments specifying the output in the
+               //     singular and plural cases.  by default "" and "s"
+               "pluralize": func(items interface{}, pluralOverrides ...string) string {
+                       singular, plural := "", "s"
+                       if len(pluralOverrides) >= 1 {
+                               singular = pluralOverrides[0]
+                               if len(pluralOverrides) == 2 {
+                                       plural = pluralOverrides[1]
+                               }
+                       }
+
+                       switch v := reflect.ValueOf(items); v.Kind() {
+                       case reflect.Int:
+                               if items.(int) != 1 {
+                                       return plural
+                               }
+                       case reflect.Slice:
+                               if v.Len() != 1 {
+                                       return plural
+                               }
+                       default:
+                               templateLog.Error("pluralize: unexpected type: ", "value", v)
+                       }
+                       return singular
+               },
+
+               // Format a date according to the application's default date(time) format.
+               "date": func(date time.Time) string {
+                       return date.Format(DateFormat)
+               },
+               "datetime": func(date time.Time) string {
+                       return date.Format(DateTimeFormat)
+               },
+               // Fetch an object from the session.
+               "session": func(key string, viewArgs map[string]interface{}) interface{} {
+                       if viewArgs != nil {
+                               if c, found := viewArgs["_controller"]; found {
+                                       if v, err := c.(*Controller).Session.Get(key); err == nil {
+                                               return v
+                                       } else {
+                                               templateLog.Errorf("template.session, key %s error %v", key, err)
+                                       }
+                               } else {
+                                       templateLog.Warnf("template.session, key %s requested without controller", key)
+                               }
+                       } else {
+                               templateLog.Warnf("template.session, key %s requested passing in view args", key)
+                       }
+                       return ""
+               },
+
+               "slug": Slug,
+               "even": func(a int) bool { return (a % 2) == 0 },
+
+               // Using https://github.com/xeonx/timeago
+               "timeago": TimeAgo,
+               "i18ntemplate": func(args ...interface{}) (template.HTML, error) {
+                       templateName, lang := "", ""
+                       var viewArgs interface{}
+                       switch len(args) {
+                       case 0:
+                               templateLog.Error("i18ntemplate: No arguments passed to template call")
+                       case 1:
+                               // Assume only the template name is passed in
+                               templateName = args[0].(string)
+                       case 2:
+                               // Assume template name and viewArgs is passed in
+                               templateName = args[0].(string)
+                               viewArgs = args[1]
+                               // Try to extract language from the view args
+                               if viewargsmap, ok := viewArgs.(map[string]interface{}); ok {
+                                       lang, _ = viewargsmap[CurrentLocaleViewArg].(string)
+                               }
+                       default:
+                               // Assume third argument is the region
+                               templateName = args[0].(string)
+                               viewArgs = args[1]
+                               lang, _ = args[2].(string)
+                               if len(args) > 3 {
+                                       templateLog.Error("i18ntemplate: Received more parameters then needed for", "template", templateName)
+                               }
+                       }
+
+                       var buf bytes.Buffer
+                       // Get template
+                       tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang)
+                       if err == nil {
+                               err = tmpl.Render(&buf, viewArgs)
+                       } else {
+                               templateLog.Error("i18ntemplate: Failed to render i18ntemplate ", "name", templateName, "error", err)
+                       }
+                       return template.HTML(buf.String()), err
+               },
+       }
+)
+
+/////////////////////
+// Template functions
+/////////////////////
+
+// ReverseURL returns a url capable of invoking a given controller method:
+// "Application.ShowApp 123" => "/app/123"
+func ReverseURL(args ...interface{}) (template.URL, error) {
+       if len(args) == 0 {
+               return "", errors.New("no arguments provided to reverse route")
+       }
+
+       action := args[0].(string)
+       if action == "Root" {
+               return template.URL(AppRoot), nil
+       }
+
+       pathData, found := splitActionPath(nil, action, true)
+
+       if !found {
+               return "", fmt.Errorf("reversing '%s', expected 'Controller.Action'", action)
+       }
+
+       // Look up the types.
+
+       if pathData.TypeOfController == nil {
+               return "", fmt.Errorf("Failed reversing %s: controller not found %#v", action, pathData)
+       }
+
+       // Note method name is case insensitive search
+       methodType := pathData.TypeOfController.Method(pathData.MethodName)
+       if methodType == nil {
+               return "", errors.New("revel/controller: In " + action + " failed to find function " + pathData.MethodName)
+       }
+
+       if len(methodType.Args) < len(args)-1 {
+               return "", fmt.Errorf("reversing %s: route defines %d args, but received %d",
+                       action, len(methodType.Args), len(args)-1)
+       }
+       // Unbind the arguments.
+       argsByName := make(map[string]string)
+       // Bind any static args first
+       fixedParams := len(pathData.FixedParamsByName)
+
+       for i, argValue := range args[1:] {
+               Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue)
+       }
+
+       return template.URL(MainRouter.Reverse(args[0].(string), argsByName).URL), nil
+}
+
+func Slug(text string) string {
+       separator := "-"
+       text = strings.ToLower(text)
+       text = invalidSlugPattern.ReplaceAllString(text, "")
+       text = whiteSpacePattern.ReplaceAllString(text, separator)
+       text = strings.Trim(text, separator)
+       return text
+}
+
+var timeAgoLangs = map[string]timeago.Config{}
+
+func TimeAgo(args ...interface{}) string {
+
+       datetime := time.Now()
+       lang := ""
+       var viewArgs interface{}
+       switch len(args) {
+       case 0:
+               templateLog.Error("TimeAgo: No arguments passed to timeago")
+       case 1:
+               // only the time is passed in
+               datetime = args[0].(time.Time)
+       case 2:
+               // time and region is passed in
+               datetime = args[0].(time.Time)
+               switch v := reflect.ValueOf(args[1]); v.Kind() {
+               case reflect.String:
+                       // second params type string equals region
+                       lang, _ = args[1].(string)
+               case reflect.Map:
+                       // second params type map equals viewArgs
+                       viewArgs = args[1]
+                       if viewargsmap, ok := viewArgs.(map[string]interface{}); ok {
+                               lang, _ = viewargsmap[CurrentLocaleViewArg].(string)
+                       }
+               default:
+                       templateLog.Error("TimeAgo: unexpected type: ", "value", v)
+               }
+       default:
+               // Assume third argument is the region
+               datetime = args[0].(time.Time)
+               if reflect.ValueOf(args[1]).Kind() != reflect.Map {
+                       templateLog.Error("TimeAgo: unexpected type", "value", args[1])
+               }
+               if reflect.ValueOf(args[2]).Kind() != reflect.String {
+                       templateLog.Error("TimeAgo: unexpected type: ", "value", args[2])
+               }
+               viewArgs = args[1]
+               lang, _ = args[2].(string)
+               if len(args) > 3 {
+                       templateLog.Error("TimeAgo: Received more parameters then needed for timeago")
+               }
+       }
+       if lang == "" {
+               lang, _ = Config.String(defaultLanguageOption)
+               if lang == "en" {
+                       timeAgoLangs[lang] = timeago.English
+               }
+       }
+       _, ok := timeAgoLangs[lang]
+       if !ok {
+               timeAgoLangs[lang] = timeago.Config{
+                       PastPrefix:   "",
+                       PastSuffix:   " " + MessageFunc(lang, "ago"),
+                       FuturePrefix: MessageFunc(lang, "in") + " ",
+                       FutureSuffix: "",
+                       Periods: []timeago.FormatPeriod{
+                               {time.Second, MessageFunc(lang, "about a second"), MessageFunc(lang, "%d seconds")},
+                               {time.Minute, MessageFunc(lang, "about a minute"), MessageFunc(lang, "%d minutes")},
+                               {time.Hour, MessageFunc(lang, "about an hour"), MessageFunc(lang, "%d hours")},
+                               {timeago.Day, MessageFunc(lang, "one day"), MessageFunc(lang, "%d days")},
+                               {timeago.Month, MessageFunc(lang, "one month"), MessageFunc(lang, "%d months")},
+                               {timeago.Year, MessageFunc(lang, "one year"), MessageFunc(lang, "%d years")},
+                       },
+                       Zero:          MessageFunc(lang, "about a second"),
+                       Max:           73 * time.Hour,
+                       DefaultLayout: "2006-01-02",
+               }
+
+       }
+       return timeAgoLangs[lang].Format(datetime)
+}
diff --git a/src/foundation/api/revel/templates/errors/403.html b/src/foundation/api/revel/templates/errors/403.html
new file mode 100644 (file)
index 0000000..23e01a2
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <title>Forbidden</title>
+       </head>
+       <body>
+       {{with .Error}}
+       <h1>
+               {{.Title}}
+       </h1>
+       <p>
+               {{.Description}}
+       </p>
+       {{end}}
+       </body>
+</html>
diff --git a/src/foundation/api/revel/templates/errors/403.json b/src/foundation/api/revel/templates/errors/403.json
new file mode 100644 (file)
index 0000000..13e1d15
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "title": "{{js .Error.Title}}",
+    "description": "{{js .Error.Description}}"
+}
diff --git a/src/foundation/api/revel/templates/errors/403.txt b/src/foundation/api/revel/templates/errors/403.txt
new file mode 100644 (file)
index 0000000..cbfc955
--- /dev/null
@@ -0,0 +1,3 @@
+{{.Error.Title}}
+
+{{.Error.Description}}
diff --git a/src/foundation/api/revel/templates/errors/403.xml b/src/foundation/api/revel/templates/errors/403.xml
new file mode 100644 (file)
index 0000000..0696e50
--- /dev/null
@@ -0,0 +1 @@
+<forbidden>{{.Error.Description}}</forbidden>
diff --git a/src/foundation/api/revel/templates/errors/404-dev.html b/src/foundation/api/revel/templates/errors/404-dev.html
new file mode 100644 (file)
index 0000000..e5f0e06
--- /dev/null
@@ -0,0 +1,63 @@
+<style type="text/css">
+       html, body {
+               margin: 0;
+               padding: 0;
+               font-family: Helvetica, Arial, Sans;
+               background: #EEEEEE;
+       }
+       .block {
+               padding: 20px;
+               border-bottom: 1px solid #aaa;
+       }
+       #header h1 {
+               font-weight: normal;
+               font-size: 28px;
+               margin: 0;
+       }
+       #more {
+               color: #666;
+               font-size: 80%;
+               border: none;
+       }
+       #header {
+               background: #FFFFCC;
+       }
+       #header p {
+               color: #333;
+       }
+       #routes {
+               background: #f6f6f6;
+       }
+       #routes h2 {
+               font-weight: normal;
+               font-size: 18px;
+               margin: 0 0 10px 0;
+       }
+       #routes ol {
+
+       }
+       #routes li {
+               font-size: 14px;
+               font-family: monospace;
+               color: #333;
+       }
+</style>
+
+<div id="header" class="block">
+       {{with .Error}}
+       <h1>
+               {{.Title}}
+       </h1>
+       <p>
+               {{.Description}}
+       </p>
+       {{end}}
+</div>
+<div id="routes" class="block">
+       <h2>These routes have been tried, in this order :</h2>
+       <ol>
+               {{range .Router.Routes}}
+                       <li>{{pad .Method 10}}{{pad .Path 50}}{{.Action}} {{with .ModuleSource}}(Route Module:{{.Name}}){{end}}</li>
+               {{end}}
+       </ol>
+</div>
diff --git a/src/foundation/api/revel/templates/errors/404.html b/src/foundation/api/revel/templates/errors/404.html
new file mode 100644 (file)
index 0000000..f548ebb
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <title>Not found</title>
+       </head>
+       <body>
+
+{{if .DevMode}}
+
+{{template "errors/404-dev.html" .}}
+
+{{else}}
+
+       {{with .Error}}
+       <h1>
+               {{.Title}}
+       </h1>
+       <p>
+               {{.Description}}
+       </p>
+       {{end}}
+
+{{end}}
+
+       </body>
+</html>
diff --git a/src/foundation/api/revel/templates/errors/404.json b/src/foundation/api/revel/templates/errors/404.json
new file mode 100644 (file)
index 0000000..13e1d15
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "title": "{{js .Error.Title}}",
+    "description": "{{js .Error.Description}}"
+}
diff --git a/src/foundation/api/revel/templates/errors/404.txt b/src/foundation/api/revel/templates/errors/404.txt
new file mode 100644 (file)
index 0000000..cbfc955
--- /dev/null
@@ -0,0 +1,3 @@
+{{.Error.Title}}
+
+{{.Error.Description}}
diff --git a/src/foundation/api/revel/templates/errors/404.xml b/src/foundation/api/revel/templates/errors/404.xml
new file mode 100644 (file)
index 0000000..29839a5
--- /dev/null
@@ -0,0 +1 @@
+<notfound>{{.Error.Description}}</notfound>
diff --git a/src/foundation/api/revel/templates/errors/405.html b/src/foundation/api/revel/templates/errors/405.html
new file mode 100644 (file)
index 0000000..58f19bc
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <title>Method not allowed</title>
+       </head>
+       <body>
+       {{with .Error}}
+       <h1>
+               {{.Title}}
+       </h1>
+       <p>
+               {{.Description}}
+       </p>
+       {{end}}
+       </body>
+</html>
diff --git a/src/foundation/api/revel/templates/errors/405.json b/src/foundation/api/revel/templates/errors/405.json
new file mode 100644 (file)
index 0000000..13e1d15
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "title": "{{js .Error.Title}}",
+    "description": "{{js .Error.Description}}"
+}
diff --git a/src/foundation/api/revel/templates/errors/405.txt b/src/foundation/api/revel/templates/errors/405.txt
new file mode 100644 (file)
index 0000000..cbfc955
--- /dev/null
@@ -0,0 +1,3 @@
+{{.Error.Title}}
+
+{{.Error.Description}}
diff --git a/src/foundation/api/revel/templates/errors/405.xml b/src/foundation/api/revel/templates/errors/405.xml
new file mode 100644 (file)
index 0000000..c5bea69
--- /dev/null
@@ -0,0 +1 @@
+<method-not-allowed>{{.Error.Description}}</method-not-allowed>
diff --git a/src/foundation/api/revel/templates/errors/500-dev.html b/src/foundation/api/revel/templates/errors/500-dev.html
new file mode 100644 (file)
index 0000000..4f0d46c
--- /dev/null
@@ -0,0 +1,133 @@
+               <style type="text/css">
+               html, body {
+                       margin: 0;
+                       padding: 0;
+                       font-family: Helvetica, Arial, Sans;
+                       background: #EEEEEE;
+               }
+               .block {
+                       padding: 20px;
+                       border-bottom: 1px solid #aaa;
+               }
+               #header h1 {
+                       font-weight: normal;
+                       font-size: 28px;
+                       margin: 0;
+               }
+               #more {
+                       color: #666;
+                       font-size: 80%;
+                       border: none;
+               }
+               #header {
+                       background: #fcd2da;
+               }
+               #header p {
+                       color: #333;
+               }
+               #source {
+                       background: #f6f6f6;
+               }
+               #source h2 {
+                       font-weight: normal;
+                       font-size: 18px;
+                       margin: 0 0 10px 0;
+               }
+               #source .lineNumber {
+                       float: left;
+                       display: block;
+                       width: 40px;
+                       text-align: right;
+                       margin-right: 10px;
+                       font-size: 14px;
+                       font-family: monospace;
+                       background: #333;
+                       color: #fff;
+               }
+               #source .line {
+                       clear: both;
+                       color: #333;
+                       margin-bottom: 1px;
+               }
+               #source pre {
+                       font-size: 14px;
+                       margin: 0;
+                       overflow-x: hidden;
+               }
+               #source .error {
+                       color: #c00 !important;
+               }
+               #source .error .lineNumber {
+                       background: #c00;
+               }
+               #source a {
+                       text-decoration: none;
+               }
+               #source a:hover * {
+                       cursor: pointer !important;
+               }
+               #source a:hover pre {
+                       background: #FAFFCF !important;
+               }
+               #source em {
+                       font-style: normal;
+                       text-decoration: underline;
+                       font-weight: bold;
+               }
+               #source strong {
+                       font-style: normal;
+                       font-weight: bold;
+               }
+               #stack {
+                       background: #eee;
+                       padding:0 1em 1em;
+               }
+               #stack h3 {
+                       font-weight: normal;
+               }
+               #stack code {
+                       font-family:monospace;
+                       white-space: pre;
+               }
+               </style>
+               {{with .Error}}
+               <div id="header" class="block">
+                       <h1>{{.Title}}</h1>
+                       <p>
+                               {{if .SourceType}}
+                                       The {{.SourceType}} <strong>{{.Path}}</strong> does not compile: <strong>{{.Description}}</strong>
+                               {{else}}
+                                       {{.Description}}
+                               {{end}}
+                       </p>
+               </div>
+               {{if .Path}}
+               <div id="source" class="block">
+                       <h2>In {{.Path}}
+                               {{if .Line}}
+                                       (around {{if .Line}}line {{.Line}}{{end}}{{if .Column}} column {{.Column}}{{end}})
+                               {{end}}
+                       </h2>
+                       {{range .ContextSource}}
+                               <div class="line {{if .IsError}}error{{end}}">
+                                       <span class="lineNumber">{{.Line}}:</span>
+                                       <pre>{{.Source}}</pre>
+                               </div>
+                       {{end}}
+               </div>
+               {{end}}
+               {{if .Stack}}
+               <div id="stack">
+                       <h3>Call Stack</h3>
+                       <code>{{.Stack}}</code>
+               </div>
+               {{end}}
+               {{if .MetaError}}
+                       <div id="source" class="block">
+                               <h2>Additionally, an error occurred while handling this error.</h2>
+                               <div class="line error">
+                                       {{.MetaError}}
+                               </div>
+                       </div>
+               {{end}}
+               {{end}}
diff --git a/src/foundation/api/revel/templates/errors/500.html b/src/foundation/api/revel/templates/errors/500.html
new file mode 100644 (file)
index 0000000..ad63527
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+       <head>
+               <title>Application error</title>
+       </head>
+       <body>
+               {{if .DevMode}}
+               {{template "errors/500-dev.html" .}}
+               {{else}}
+               <h1>Oops, an error occured</h1>
+               <p>
+                       This exception has been logged.
+               </p>
+               {{end}}
+       </body>
+</html>
diff --git a/src/foundation/api/revel/templates/errors/500.json b/src/foundation/api/revel/templates/errors/500.json
new file mode 100644 (file)
index 0000000..13e1d15
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "title": "{{js .Error.Title}}",
+    "description": "{{js .Error.Description}}"
+}
diff --git a/src/foundation/api/revel/templates/errors/500.txt b/src/foundation/api/revel/templates/errors/500.txt
new file mode 100644 (file)
index 0000000..dde13f4
--- /dev/null
@@ -0,0 +1,15 @@
+{{.Error.Title}}
+{{.Error.Description}}
+
+{{if eq .RunMode "dev"}}
+{{with .Error}}
+{{if .Path}}
+----------
+In {{.Path}} {{if .Line}}(around line {{.Line}}){{end}}
+
+{{range .ContextSource}}
+{{if .IsError}}>{{else}} {{end}} {{.Line}}: {{.Source}}{{end}}
+
+{{end}}
+{{end}}
+{{end}}
diff --git a/src/foundation/api/revel/templates/errors/500.xml b/src/foundation/api/revel/templates/errors/500.xml
new file mode 100644 (file)
index 0000000..444b4f8
--- /dev/null
@@ -0,0 +1,4 @@
+<error>
+       <title>{{.Error.Title}}</title>
+       <description>{{.Error.Description}}</description>
+</error>
diff --git a/src/foundation/api/revel/testdata/app/views/footer.html b/src/foundation/api/revel/testdata/app/views/footer.html
new file mode 100644 (file)
index 0000000..9307824
--- /dev/null
@@ -0,0 +1,8 @@
+    </div>
+
+    <div id="footer">
+      Created with the <a href="http://github.com/revel/revel">Revel framework</a> and really inspirated from the booking sample application provided by <a href="http://www.playframework.org">play framework</a>, which was really inspired by the booking sample application provided by the <a href="http://seamframework.org/">seam framework</a>.
+    </div>
+
+  </body>
+</html>
diff --git a/src/foundation/api/revel/testdata/app/views/header.html b/src/foundation/api/revel/testdata/app/views/header.html
new file mode 100644 (file)
index 0000000..583bb34
--- /dev/null
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <title>{{.title}}</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <link rel="stylesheet" type="text/css" media="screen" href="/public/css/main.css">
+    {{range .moreStyles}}
+      <link rel="stylesheet" type="text/css" href="/public/{{.}}">
+    {{end}}
+    <script src="/public/js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
+    <script src="/public/js/sessvars.js" type="text/javascript" charset="utf-8"></script>
+    {{range .moreScripts}}
+      <script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
+    {{end}}
+  </head>
+  <body>
+
+    <div id="header">
+      <h1>revel framework booking demo</h1>
+      {{if .user}}
+        <div id="options">
+          Connected as {{.user.Username}}
+          |
+          <a href="{{url "Hotels.Index"}}">Search</a>
+          |
+          <a href="{{url "Hotels.Settings"}}">Settings</a>
+          |
+          <a href="{{url "Application.Logout"}}">Logout</a>
+        </div>
+      {{end}}
+    </div>
+
+    <div id="content">
+      {{if .flash.error}}
+        <p class="fError">
+          <strong>{{.flash.error}}</strong>
+        </p>
+      {{end}}
+      {{if .flash.success}}
+        <p class="fSuccess">
+          <strong>{{.flash.success}}</strong>
+        </p>
+      {{end}}
+
diff --git a/src/foundation/api/revel/testdata/app/views/hotels/show.html b/src/foundation/api/revel/testdata/app/views/hotels/show.html
new file mode 100644 (file)
index 0000000..6d10898
--- /dev/null
@@ -0,0 +1,37 @@
+{{template "header.html" .}}
+
+<h1>View hotel</h1>
+
+{{with .hotel}}
+<form action="{{url "Hotels.Book" .HotelID}}">
+
+  <p>
+    <strong>Name:</strong> {{.Name}}
+  </p>
+  <p>
+    <strong>Address:</strong> {{.Address}}
+  </p>
+  <p>
+    <strong>City:</strong> {{.City}}
+  </p>
+  <p>
+    <strong>State:</strong> {{.State}}
+  </p>
+  <p>
+    <strong>Zip:</strong> {{.Zip}}
+  </p>
+  <p>
+    <strong>Country:</strong> {{.Country}}
+  </p>
+  <p>
+    <strong>Nightly rate:</strong> {{.Price}}
+  </p>
+
+  <p class="buttons">
+    <input type="submit" value="Book Hotel">
+    <a href="{{url "Hotels.Index"}}">Back to search</a>
+  </p>
+</form>
+{{end}}
+
+{{template "footer.html" .}}
diff --git a/src/foundation/api/revel/testdata/conf/app.conf b/src/foundation/api/revel/testdata/conf/app.conf
new file mode 100644 (file)
index 0000000..6efa0cf
--- /dev/null
@@ -0,0 +1,44 @@
+# Application
+app.name=Booking example
+app.secret=secret
+
+# Server
+http.addr=
+http.port=9000
+http.ssl=false
+http.sslcert=
+http.sslkey=
+
+# Logging
+log.trace.output = stderr
+log.info.output  = stderr
+log.warn.output  = stderr
+log.error.output = stderr
+
+log.trace.prefix = "TRACE "
+log.info.prefix  = "INFO  "
+log.warn.prefix  = "WARN  "
+log.error.prefix = "ERROR "
+
+db.import = github.com/mattn/go-sqlite3
+db.driver = sqlite3
+db.spec   = :memory:
+
+build.tags=gorp
+
+# module.jobs=github.com/revel/modules/jobs
+module.static=github.com/revel/modules/static
+
+[dev]
+mode.dev=true
+watch=true
+module.testrunner=github.com/revel/modules/testrunner
+
+[prod]
+watch=false
+module.testrunner=
+
+log.trace.output = off
+log.info.output  = off
+log.warn.output  = stderr
+log.error.output = stderr
diff --git a/src/foundation/api/revel/testdata/conf/routes b/src/foundation/api/revel/testdata/conf/routes
new file mode 100644 (file)
index 0000000..ec40efd
--- /dev/null
@@ -0,0 +1,16 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+module:testrunner
+
+GET     /hotels                                 Hotels.Index
+GET     /hotels/:id                             Hotels.Show
+GET     /hotels/:id/booking                     Hotels.Book
+
+# Map static resources from the /app/public folder to the /public path
+GET     /public/*filepath                       Static.Serve("public")
+GET     /favicon.ico                            Static.Serve("public/img","favicon.png")
+
+# Catch all
+*       /:controller/:action                    :controller.:action
diff --git a/src/foundation/api/revel/testdata/i18n/config/test_app.conf b/src/foundation/api/revel/testdata/i18n/config/test_app.conf
new file mode 100644 (file)
index 0000000..40e12b6
--- /dev/null
@@ -0,0 +1,33 @@
+app.name={{ .AppName }}
+app.secret={{ .Secret }}
+http.addr=
+http.port=9000
+cookie.prefix=REVEL
+
+i18n.default_language=en
+i18n.cookie=APP_LANG
+
+[dev]
+results.pretty=true
+results.staging=true
+watch=true
+
+module.testrunner = github.com/revel/modules/testrunner
+module.static=github.com/revel/modules/static
+
+log.trace.output = off
+log.info.output  = stderr
+log.warn.output  = stderr
+log.error.output = stderr
+
+[prod]
+results.pretty=false
+results.staging=false
+watch=false
+
+module.testrunner =
+
+log.trace.output = off
+log.info.output  = off
+log.warn.output  = %(app.name)s.log
+log.error.output = %(app.name)s.log
diff --git a/src/foundation/api/revel/testdata/i18n/messages/dutch_messages.nl b/src/foundation/api/revel/testdata/i18n/messages/dutch_messages.nl
new file mode 100644 (file)
index 0000000..2e75b47
--- /dev/null
@@ -0,0 +1,9 @@
+greeting=Hallo 
+greeting.name=Rob
+greeting.suffix=, welkom bij Revel!
+
+[NL]
+greeting=Goeiedag
+
+[BE]
+greeting=Hallokes
diff --git a/src/foundation/api/revel/testdata/i18n/messages/english_messages.en b/src/foundation/api/revel/testdata/i18n/messages/english_messages.en
new file mode 100644 (file)
index 0000000..653bfd9
--- /dev/null
@@ -0,0 +1,21 @@
+greeting=Hello 
+greeting.name=Rob
+greeting.suffix=, welcome to Revel!
+
+folded=Greeting is '%(greeting)s'
+folded.arguments=%(greeting.name)s is %d years old
+
+arguments.string=My name is %s
+arguments.hex=The number %d in hexadecimal notation would be %x
+arguments.none=No arguments here son
+
+only_exists_in_default=Default
+
+[AU]
+greeting=G'day
+
+[US]
+greeting=Howdy
+
+[GB]
+greeting=All right
\ No newline at end of file
diff --git a/src/foundation/api/revel/testdata/i18n/messages/english_messages2.en b/src/foundation/api/revel/testdata/i18n/messages/english_messages2.en
new file mode 100644 (file)
index 0000000..ea74539
--- /dev/null
@@ -0,0 +1 @@
+greeting2=Yo!
diff --git a/src/foundation/api/revel/testdata/i18n/messages/invalid_message_file_name.txt b/src/foundation/api/revel/testdata/i18n/messages/invalid_message_file_name.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/foundation/api/revel/testdata/public/js/sessvars.js b/src/foundation/api/revel/testdata/public/js/sessvars.js
new file mode 100644 (file)
index 0000000..9eaafea
--- /dev/null
@@ -0,0 +1 @@
+console.log('Test file');
diff --git a/src/foundation/api/revel/testing/testsuite.go b/src/foundation/api/revel/testing/testsuite.go
new file mode 100644 (file)
index 0000000..ff02472
--- /dev/null
@@ -0,0 +1,411 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package testing
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "mime"
+       "mime/multipart"
+       "net/http"
+       "net/http/cookiejar"
+       "net/textproto"
+       "net/url"
+       "os"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       "github.com/revel/revel"
+
+       "github.com/revel/revel/session"
+       "golang.org/x/net/websocket"
+       "net/http/httptest"
+)
+
+type TestSuite struct {
+       Client        *http.Client
+       Response      *http.Response
+       ResponseBody  []byte
+       Session       session.Session
+       SessionEngine revel.SessionEngine
+}
+
+type TestRequest struct {
+       *http.Request
+       testSuite *TestSuite
+}
+
+// This is populated by the generated code in the run/run/go file
+var TestSuites []interface{} // Array of structs that embed TestSuite
+
+// NewTestSuite returns an initialized TestSuite ready for use. It is invoked
+// by the test harness to initialize the embedded field in application tests.
+func NewTestSuite() TestSuite {
+       return NewTestSuiteEngine(revel.NewSessionCookieEngine())
+}
+
+// Define a new test suite with a custom session engine
+func NewTestSuiteEngine(engine revel.SessionEngine) TestSuite {
+       jar, _ := cookiejar.New(nil)
+       ts := TestSuite{
+               Client:        &http.Client{Jar: jar},
+               Session:       session.NewSession(),
+               SessionEngine: engine,
+       }
+
+       return ts
+}
+
+// NewTestRequest returns an initialized *TestRequest. It is used for extending
+// testsuite package making it possibe to define own methods. Example:
+//     type MyTestSuite struct {
+//             testing.TestSuite
+//     }
+//
+//     func (t *MyTestSuite) PutFormCustom(...) {
+//             req := http.NewRequest(...)
+//             ...
+//             return t.NewTestRequest(req)
+//     }
+func (t *TestSuite) NewTestRequest(req *http.Request) *TestRequest {
+       request := &TestRequest{
+               Request:   req,
+               testSuite: t,
+       }
+       return request
+}
+
+// Host returns the address and port of the server, e.g. "127.0.0.1:8557"
+func (t *TestSuite) Host() string {
+       if revel.ServerEngineInit.Address[0] == ':' {
+               return "127.0.0.1" + revel.ServerEngineInit.Address
+       }
+       return revel.ServerEngineInit.Address
+}
+
+// BaseUrl returns the base http/https URL of the server, e.g. "http://127.0.0.1:8557".
+// The scheme is set to https if http.ssl is set to true in the configuration file.
+func (t *TestSuite) BaseUrl() string {
+       if revel.HTTPSsl {
+               return "https://" + t.Host()
+       }
+       return "http://" + t.Host()
+}
+
+// WebSocketUrl returns the base websocket URL of the server, e.g. "ws://127.0.0.1:8557"
+func (t *TestSuite) WebSocketUrl() string {
+       return "ws://" + t.Host()
+}
+
+// Get issues a GET request to the given path and stores the result in Response
+// and ResponseBody.
+func (t *TestSuite) Get(path string) {
+       t.GetCustom(t.BaseUrl() + path).Send()
+}
+
+// GetCustom returns a GET request to the given URI in a form of its wrapper.
+func (t *TestSuite) GetCustom(uri string) *TestRequest {
+       req, err := http.NewRequest("GET", uri, nil)
+       if err != nil {
+               panic(err)
+       }
+       return t.NewTestRequest(req)
+}
+
+// Delete issues a DELETE request to the given path and stores the result in
+// Response and ResponseBody.
+func (t *TestSuite) Delete(path string) {
+       t.DeleteCustom(t.BaseUrl() + path).Send()
+}
+
+// DeleteCustom returns a DELETE request to the given URI in a form of its
+// wrapper.
+func (t *TestSuite) DeleteCustom(uri string) *TestRequest {
+       req, err := http.NewRequest("DELETE", uri, nil)
+       if err != nil {
+               panic(err)
+       }
+       return t.NewTestRequest(req)
+}
+
+// Put issues a PUT request to the given path, sending the given Content-Type
+// and data, storing the result in Response and ResponseBody. "data" may be nil.
+func (t *TestSuite) Put(path string, contentType string, reader io.Reader) {
+       t.PutCustom(t.BaseUrl()+path, contentType, reader).Send()
+}
+
+// PutCustom returns a PUT request to the given URI with specified Content-Type
+// and data in a form of wrapper. "data" may be nil.
+func (t *TestSuite) PutCustom(uri string, contentType string, reader io.Reader) *TestRequest {
+       req, err := http.NewRequest("PUT", uri, reader)
+       if err != nil {
+               panic(err)
+       }
+       req.Header.Set("Content-Type", contentType)
+       return t.NewTestRequest(req)
+}
+
+// PutForm issues a PUT request to the given path as a form put of the given key
+// and values, and stores the result in Response and ResponseBody.
+func (t *TestSuite) PutForm(path string, data url.Values) {
+       t.PutFormCustom(t.BaseUrl()+path, data).Send()
+}
+
+// PutFormCustom returns a PUT request to the given URI as a form put of the
+// given key and values. The request is in a form of TestRequest wrapper.
+func (t *TestSuite) PutFormCustom(uri string, data url.Values) *TestRequest {
+       return t.PutCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
+}
+
+// Patch issues a PATCH request to the given path, sending the given
+// Content-Type and data, and stores the result in Response and ResponseBody.
+// "data" may be nil.
+func (t *TestSuite) Patch(path string, contentType string, reader io.Reader) {
+       t.PatchCustom(t.BaseUrl()+path, contentType, reader).Send()
+}
+
+// PatchCustom returns a PATCH request to the given URI with specified
+// Content-Type and data in a form of wrapper. "data" may be nil.
+func (t *TestSuite) PatchCustom(uri string, contentType string, reader io.Reader) *TestRequest {
+       req, err := http.NewRequest("PATCH", uri, reader)
+       if err != nil {
+               panic(err)
+       }
+       req.Header.Set("Content-Type", contentType)
+       return t.NewTestRequest(req)
+}
+
+// Post issues a POST request to the given path, sending the given Content-Type
+// and data, storing the result in Response and ResponseBody. "data" may be nil.
+func (t *TestSuite) Post(path string, contentType string, reader io.Reader) {
+       t.PostCustom(t.BaseUrl()+path, contentType, reader).Send()
+}
+
+// PostCustom returns a POST request to the given URI with specified
+// Content-Type and data in a form of wrapper. "data" may be nil.
+func (t *TestSuite) PostCustom(uri string, contentType string, reader io.Reader) *TestRequest {
+       req, err := http.NewRequest("POST", uri, reader)
+       if err != nil {
+               panic(err)
+       }
+       req.Header.Set("Content-Type", contentType)
+       return t.NewTestRequest(req)
+}
+
+// PostForm issues a POST request to the given path as a form post of the given
+// key and values, and stores the result in Response and ResponseBody.
+func (t *TestSuite) PostForm(path string, data url.Values) {
+       t.PostFormCustom(t.BaseUrl()+path, data).Send()
+}
+
+// PostFormCustom returns a POST request to the given URI as a form post of the
+// given key and values. The request is in a form of TestRequest wrapper.
+func (t *TestSuite) PostFormCustom(uri string, data url.Values) *TestRequest {
+       return t.PostCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
+}
+
+// PostFile issues a multipart request to the given path sending given params
+// and files, and stores the result in Response and ResponseBody.
+func (t *TestSuite) PostFile(path string, params url.Values, filePaths url.Values) {
+       t.PostFileCustom(t.BaseUrl()+path, params, filePaths).Send()
+}
+
+// PostFileCustom returns a multipart request to the given URI in a form of its
+// wrapper with the given params and files.
+func (t *TestSuite) PostFileCustom(uri string, params url.Values, filePaths url.Values) *TestRequest {
+       body := &bytes.Buffer{}
+       writer := multipart.NewWriter(body)
+
+       for key, values := range filePaths {
+               for _, value := range values {
+                       createFormFile(writer, key, value)
+               }
+       }
+
+       for key, values := range params {
+               for _, value := range values {
+                       err := writer.WriteField(key, value)
+                       t.AssertEqual(nil, err)
+               }
+       }
+       err := writer.Close()
+       t.AssertEqual(nil, err)
+
+       return t.PostCustom(uri, writer.FormDataContentType(), body)
+}
+
+// Send issues any request and reads the response. If successful, the caller may
+// examine the Response and ResponseBody properties. Session data will be
+// added.
+func (r *TestRequest) Send() {
+       writer := httptest.NewRecorder()
+       context := revel.NewGoContext(nil)
+       context.Request.SetRequest(r.Request)
+       context.Response.SetResponse(writer)
+       controller := revel.NewController(context)
+       controller.Session = r.testSuite.Session
+
+       r.testSuite.SessionEngine.Encode(controller)
+       response := http.Response{Header: writer.Header()}
+       cookies := response.Cookies()
+       for _, c := range cookies {
+               r.AddCookie(c)
+       }
+       r.MakeRequest()
+}
+
+// MakeRequest issues any request and read the response. If successful, the
+// caller may examine the Response and ResponseBody properties. You will need to
+// manage session / cookie data manually
+func (r *TestRequest) MakeRequest() {
+       var err error
+       if r.testSuite.Response, err = r.testSuite.Client.Do(r.Request); err != nil {
+               panic(err)
+       }
+       if r.testSuite.ResponseBody, err = ioutil.ReadAll(r.testSuite.Response.Body); err != nil {
+               panic(err)
+       }
+
+       // Create the controller again to receive the response for processing.
+       context := revel.NewGoContext(nil)
+       // Set the request with the header from the response..
+       newRequest := &http.Request{URL: r.URL, Header: r.testSuite.Response.Header}
+       for _, cookie := range r.testSuite.Client.Jar.Cookies(r.Request.URL) {
+               newRequest.AddCookie(cookie)
+       }
+       context.Request.SetRequest(newRequest)
+       context.Response.SetResponse(httptest.NewRecorder())
+       controller := revel.NewController(context)
+
+       // Decode the session data from the controller and assign it to the session
+       r.testSuite.SessionEngine.Decode(controller)
+       r.testSuite.Session = controller.Session
+}
+
+// WebSocket creates a websocket connection to the given path and returns it
+func (t *TestSuite) WebSocket(path string) *websocket.Conn {
+       origin := t.BaseUrl() + "/"
+       urlPath := t.WebSocketUrl() + path
+       ws, err := websocket.Dial(urlPath, "", origin)
+       if err != nil {
+               panic(err)
+       }
+       return ws
+}
+
+func (t *TestSuite) AssertOk() {
+       t.AssertStatus(http.StatusOK)
+}
+
+func (t *TestSuite) AssertNotFound() {
+       t.AssertStatus(http.StatusNotFound)
+}
+
+func (t *TestSuite) AssertStatus(status int) {
+       if t.Response.StatusCode != status {
+               panic(fmt.Errorf("Status: (expected) %d != %d (actual)", status, t.Response.StatusCode))
+       }
+}
+
+func (t *TestSuite) AssertContentType(contentType string) {
+       t.AssertHeader("Content-Type", contentType)
+}
+
+func (t *TestSuite) AssertHeader(name, value string) {
+       actual := t.Response.Header.Get(name)
+       if actual != value {
+               panic(fmt.Errorf("Header %s: (expected) %s != %s (actual)", name, value, actual))
+       }
+}
+
+func (t *TestSuite) AssertEqual(expected, actual interface{}) {
+       if !revel.Equal(expected, actual) {
+               panic(fmt.Errorf("(expected) %v != %v (actual)", expected, actual))
+       }
+}
+
+func (t *TestSuite) AssertNotEqual(expected, actual interface{}) {
+       if revel.Equal(expected, actual) {
+               panic(fmt.Errorf("(expected) %v == %v (actual)", expected, actual))
+       }
+}
+
+func (t *TestSuite) Assert(exp bool) {
+       t.Assertf(exp, "Assertion failed")
+}
+
+func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{}) {
+       if !exp {
+               panic(fmt.Errorf(formatStr, args...))
+       }
+}
+
+// AssertContains asserts that the response contains the given string.
+func (t *TestSuite) AssertContains(s string) {
+       if !bytes.Contains(t.ResponseBody, []byte(s)) {
+               panic(fmt.Errorf("Assertion failed. Expected response to contain %s", s))
+       }
+}
+
+// AssertNotContains asserts that the response does not contain the given string.
+func (t *TestSuite) AssertNotContains(s string) {
+       if bytes.Contains(t.ResponseBody, []byte(s)) {
+               panic(fmt.Errorf("Assertion failed. Expected response not to contain %s", s))
+       }
+}
+
+// AssertContainsRegex asserts that the response matches the given regular expression.
+func (t *TestSuite) AssertContainsRegex(regex string) {
+       r := regexp.MustCompile(regex)
+
+       if !r.Match(t.ResponseBody) {
+               panic(fmt.Errorf("Assertion failed. Expected response to match regexp %s", regex))
+       }
+}
+
+func createFormFile(writer *multipart.Writer, fieldname, filename string) {
+       // Try to open the file.
+       file, err := os.Open(filename)
+       if err != nil {
+               panic(err)
+       }
+       defer func() {
+               _ = file.Close()
+       }()
+
+       // Create a new form-data header with the provided field name and file name.
+       // Determine Content-Type of the file by its extension.
+       h := textproto.MIMEHeader{}
+       h.Set("Content-Disposition", fmt.Sprintf(
+               `form-data; name="%s"; filename="%s"`,
+               escapeQuotes(fieldname),
+               escapeQuotes(filepath.Base(filename)),
+       ))
+       h.Set("Content-Type", "application/octet-stream")
+       if ct := mime.TypeByExtension(filepath.Ext(filename)); ct != "" {
+               h.Set("Content-Type", ct)
+       }
+       part, err := writer.CreatePart(h)
+       if err != nil {
+               panic(err)
+       }
+
+       // Copy the content of the file we have opened not reading the whole
+       // file into memory.
+       _, err = io.Copy(part, file)
+       if err != nil {
+               panic(err)
+       }
+}
+
+var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
+
+// This function was borrowed from mime/multipart package.
+func escapeQuotes(s string) string {
+       return quoteEscaper.Replace(s)
+}
diff --git a/src/foundation/api/revel/testing/testsuite_test.go b/src/foundation/api/revel/testing/testsuite_test.go
new file mode 100644 (file)
index 0000000..e82066c
--- /dev/null
@@ -0,0 +1,303 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package testing
+
+import (
+       "bytes"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
+       "net/url"
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/revel/revel"
+       "github.com/revel/revel/session"
+)
+
+func TestMisc(t *testing.T) {
+       testSuite := createNewTestSuite(t)
+
+       // test Host value
+       if !strings.EqualFold("127.0.0.1:9001", testSuite.Host()) {
+               t.Error("Incorrect Host value found.")
+       }
+
+       // test BaseUrl
+       if !strings.EqualFold("http://127.0.0.1:9001", testSuite.BaseUrl()) {
+               t.Error("Incorrect BaseUrl http value found.")
+       }
+       revel.HTTPSsl = true
+       if !strings.EqualFold("https://127.0.0.1:9001", testSuite.BaseUrl()) {
+               t.Error("Incorrect BaseUrl https value found.")
+       }
+       revel.HTTPSsl = false
+
+       // test WebSocketUrl
+       if !strings.EqualFold("ws://127.0.0.1:9001", testSuite.WebSocketUrl()) {
+               t.Error("Incorrect WebSocketUrl value found.")
+       }
+
+       testSuite.AssertNotEqual("Yes", "No")
+       testSuite.Assert(true)
+}
+
+func TestGet(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Get("/")
+       testSuite.AssertOk()
+       testSuite.AssertContains("this is testcase homepage")
+       testSuite.AssertNotContains("not exists")
+}
+
+func TestGetNotFound(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Get("/notfound")
+       testSuite.AssertNotFound()
+       // testSuite.AssertContains("this is testcase homepage")
+       // testSuite.AssertNotContains("not exists")
+}
+
+// This test is known to fail
+func TestGetCustom(t *testing.T) {
+       testSuite := createNewTestSuite(t)
+       for x := 0; x < 5; x++ {
+               testSuite.GetCustom("http://httpbin.org/get").Send()
+               if testSuite.Response.StatusCode == http.StatusOK {
+                       break
+               }
+               println("Failed request from http://httpbin.org/get", testSuite.Response.StatusCode)
+       }
+
+       testSuite.AssertOk()
+       testSuite.AssertContentType("application/json")
+       testSuite.AssertContains("httpbin.org")
+       testSuite.AssertContainsRegex("gzip|deflate")
+}
+
+func TestDelete(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Delete("/purchases/10001")
+       testSuite.AssertOk()
+}
+
+func TestPut(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Put("/purchases/10002",
+               "application/json",
+               bytes.NewReader([]byte(`{"sku":"163645GHT", "desc":"This is test product"}`)),
+       )
+       testSuite.AssertStatus(http.StatusNoContent)
+}
+
+func TestPutForm(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       data := url.Values{}
+       data.Add("name", "beacon1name")
+       data.Add("value", "beacon1value")
+
+       testSuite.PutForm("/send", data)
+       testSuite.AssertStatus(http.StatusNoContent)
+}
+
+func TestPatch(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Patch("/purchases/10003",
+               "application/json",
+               bytes.NewReader([]byte(`{"desc": "This is test patch for product"}`)),
+       )
+       testSuite.AssertStatus(http.StatusNoContent)
+}
+
+func TestPost(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       testSuite.Post("/login",
+               "application/json",
+               bytes.NewReader([]byte(`{"username":"testuser", "password":"testpass"}`)),
+       )
+       testSuite.AssertOk()
+       testSuite.AssertContains("login successful")
+}
+
+func TestPostForm(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       data := url.Values{}
+       data.Add("username", "testuser")
+       data.Add("password", "testpassword")
+
+       testSuite.PostForm("/login", data)
+       testSuite.AssertOk()
+       testSuite.AssertContains("login successful")
+}
+
+func TestPostFileUpload(t *testing.T) {
+       ts := createTestServer(testHandle)
+       defer ts.Close()
+
+       testSuite := createNewTestSuite(t)
+
+       params := url.Values{}
+       params.Add("first_name", "Jeevanandam")
+       params.Add("last_name", "M.")
+
+       currentDir, _ := os.Getwd()
+       basePath := filepath.Dir(currentDir)
+
+       filePaths := url.Values{}
+       filePaths.Add("revel_file", filepath.Join(basePath, "revel.go"))
+       filePaths.Add("server_file", filepath.Join(basePath, "server.go"))
+       filePaths.Add("readme_file", filepath.Join(basePath, "README.md"))
+
+       testSuite.PostFile("/upload", params, filePaths)
+
+       testSuite.AssertOk()
+       testSuite.AssertContains("File: revel.go")
+       testSuite.AssertContains("File: server.go")
+       testSuite.AssertNotContains("File: not_exists.go")
+       testSuite.AssertEqual("text/plain; charset=utf-8", testSuite.Response.Header.Get("Content-Type"))
+
+}
+
+func createNewTestSuite(t *testing.T) *TestSuite {
+       suite := NewTestSuite()
+
+       if suite.Client == nil || suite.SessionEngine == nil {
+               t.Error("Unable to create a testsuite")
+       }
+
+       return &suite
+}
+
+func testHandle(w http.ResponseWriter, r *http.Request) {
+       if r.Method == "GET" {
+               if r.URL.Path == "/" {
+                       _, _ = w.Write([]byte(`this is testcase homepage`))
+                       return
+               }
+       }
+
+       if r.Method == "POST" {
+               if r.URL.Path == "/login" {
+                       http.SetCookie(w, &http.Cookie{
+                               Name:     session.SessionCookieSuffix,
+                               Value:    "This is simple session value",
+                               Path:     "/",
+                               HttpOnly: true,
+                               Secure:   false,
+                               Expires:  time.Now().Add(time.Minute * 5).UTC(),
+                       })
+
+                       w.Header().Set("Content-Type", "application/json")
+                       _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
+                       return
+               }
+
+               handleFileUpload(w, r)
+               return
+       }
+
+       if r.Method == "DELETE" {
+               if r.URL.Path == "/purchases/10001" {
+                       w.WriteHeader(http.StatusOK)
+                       return
+               }
+       }
+
+       if r.Method == "PUT" {
+               if r.URL.Path == "/purchases/10002" {
+                       w.WriteHeader(http.StatusNoContent)
+                       return
+               }
+
+               if r.URL.Path == "/send" {
+                       w.WriteHeader(http.StatusNoContent)
+                       return
+               }
+       }
+
+       if r.Method == "PATCH" {
+               if r.URL.Path == "/purchases/10003" {
+                       w.WriteHeader(http.StatusNoContent)
+                       return
+               }
+       }
+
+       w.WriteHeader(http.StatusNotFound)
+}
+
+func handleFileUpload(w http.ResponseWriter, r *http.Request) {
+       if r.URL.Path == "/upload" {
+               _ = r.ParseMultipartForm(10e6)
+               var buf bytes.Buffer
+               for _, fhdrs := range r.MultipartForm.File {
+                       for _, hdr := range fhdrs {
+                               dotPos := strings.LastIndex(hdr.Filename, ".")
+                               fname := fmt.Sprintf("%s-%v%s", hdr.Filename[:dotPos], time.Now().Unix(), hdr.Filename[dotPos:])
+                               _, _ = buf.WriteString(fmt.Sprintf(
+                                       "Firstname: %v\nLastname: %v\nFile: %v\nHeader: %v\nUploaded as: %v\n",
+                                       r.FormValue("first_name"),
+                                       r.FormValue("last_name"),
+                                       hdr.Filename,
+                                       hdr.Header,
+                                       fname))
+                       }
+               }
+
+               _, _ = w.Write(buf.Bytes())
+
+               return
+       }
+}
+
+func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
+       testServer := httptest.NewServer(http.HandlerFunc(fn))
+       revel.ServerEngineInit.Address = testServer.URL[7:]
+       return testServer
+}
+
+func init() {
+       if revel.ServerEngineInit == nil {
+               revel.ServerEngineInit = &revel.EngineInit{
+                       Address: ":9001",
+                       Network: "http",
+                       Port:    9001,
+               }
+       }
+}
diff --git a/src/foundation/api/revel/util.go b/src/foundation/api/revel/util.go
new file mode 100644 (file)
index 0000000..340c599
--- /dev/null
@@ -0,0 +1,280 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "net"
+       "net/http"
+       "net/url"
+       "os"
+       "path/filepath"
+       "reflect"
+       "regexp"
+       "strings"
+
+       "github.com/revel/config"
+)
+
+const (
+       // DefaultFileContentType Revel's default response content type
+       DefaultFileContentType = "application/octet-stream"
+)
+
+var (
+       cookieKeyValueParser = regexp.MustCompile("\x00([^:]*):([^\x00]*)\x00")
+       HdrForwardedFor      = http.CanonicalHeaderKey("X-Forwarded-For")
+       HdrRealIP            = http.CanonicalHeaderKey("X-Real-Ip")
+       utilLog              = RevelLog.New("section", "util")
+       mimeConfig           *config.Context
+)
+
+// ExecutableTemplate adds some more methods to the default Template.
+type ExecutableTemplate interface {
+       Execute(io.Writer, interface{}) error
+}
+
+// ExecuteTemplate execute a template and returns the result as a string.
+func ExecuteTemplate(tmpl ExecutableTemplate, data interface{}) string {
+       var b bytes.Buffer
+       if err := tmpl.Execute(&b, data); err != nil {
+               utilLog.Error("ExecuteTemplate: Execute failed", "error", err)
+       }
+       return b.String()
+}
+
+// MustReadLines reads the lines of the given file.  Panics in the case of error.
+func MustReadLines(filename string) []string {
+       r, err := ReadLines(filename)
+       if err != nil {
+               panic(err)
+       }
+       return r
+}
+
+// ReadLines reads the lines of the given file.  Panics in the case of error.
+func ReadLines(filename string) ([]string, error) {
+       dataBytes, err := ioutil.ReadFile(filename)
+       if err != nil {
+               return nil, err
+       }
+       return strings.Split(string(dataBytes), "\n"), nil
+}
+
+func ContainsString(list []string, target string) bool {
+       for _, el := range list {
+               if el == target {
+                       return true
+               }
+       }
+       return false
+}
+
+// FindMethod returns the reflect.Method, given a Receiver type and Func value.
+func FindMethod(recvType reflect.Type, funcVal reflect.Value) *reflect.Method {
+       // It is not possible to get the name of the method from the Func.
+       // Instead, compare it to each method of the Controller.
+       for i := 0; i < recvType.NumMethod(); i++ {
+               method := recvType.Method(i)
+               if method.Func.Pointer() == funcVal.Pointer() {
+                       return &method
+               }
+       }
+       return nil
+}
+
+// ParseKeyValueCookie takes the raw (escaped) cookie value and parses out key values.
+func ParseKeyValueCookie(val string, cb func(key, val string)) {
+       val, _ = url.QueryUnescape(val)
+       if matches := cookieKeyValueParser.FindAllStringSubmatch(val, -1); matches != nil {
+               for _, match := range matches {
+                       cb(match[1], match[2])
+               }
+       }
+}
+
+// LoadMimeConfig load mime-types.conf on init.
+func LoadMimeConfig() {
+       var err error
+       mimeConfig, err = config.LoadContext("mime-types.conf", ConfPaths)
+       if err != nil {
+               utilLog.Fatal("Failed to load mime type config:", "error", err)
+       }
+}
+
+// ContentTypeByFilename returns a MIME content type based on the filename's extension.
+// If no appropriate one is found, returns "application/octet-stream" by default.
+// Additionally, specifies the charset as UTF-8 for text/* types.
+func ContentTypeByFilename(filename string) string {
+       dot := strings.LastIndex(filename, ".")
+       if dot == -1 || dot+1 >= len(filename) {
+               return DefaultFileContentType
+       }
+
+       extension := filename[dot+1:]
+       contentType := mimeConfig.StringDefault(extension, "")
+       if contentType == "" {
+               return DefaultFileContentType
+       }
+
+       if strings.HasPrefix(contentType, "text/") {
+               return contentType + "; charset=utf-8"
+       }
+
+       return contentType
+}
+
+// DirExists returns true if the given path exists and is a directory.
+func DirExists(filename string) bool {
+       fileInfo, err := os.Stat(filename)
+       return err == nil && fileInfo.IsDir()
+}
+
+func FirstNonEmpty(strs ...string) string {
+       for _, str := range strs {
+               if len(str) > 0 {
+                       return str
+               }
+       }
+       return ""
+}
+
+// Equal is a helper for comparing value equality, following these rules:
+//  - Values with equivalent types are compared with reflect.DeepEqual
+//  - int, uint, and float values are compared without regard to the type width.
+//    for example, Equal(int32(5), int64(5)) == true
+//  - strings and byte slices are converted to strings before comparison.
+//  - else, return false.
+func Equal(a, b interface{}) bool {
+       if reflect.TypeOf(a) == reflect.TypeOf(b) {
+               return reflect.DeepEqual(a, b)
+       }
+       switch a.(type) {
+       case int, int8, int16, int32, int64:
+               switch b.(type) {
+               case int, int8, int16, int32, int64:
+                       return reflect.ValueOf(a).Int() == reflect.ValueOf(b).Int()
+               }
+       case uint, uint8, uint16, uint32, uint64:
+               switch b.(type) {
+               case uint, uint8, uint16, uint32, uint64:
+                       return reflect.ValueOf(a).Uint() == reflect.ValueOf(b).Uint()
+               }
+       case float32, float64:
+               switch b.(type) {
+               case float32, float64:
+                       return reflect.ValueOf(a).Float() == reflect.ValueOf(b).Float()
+               }
+       case string:
+               switch b.(type) {
+               case []byte:
+                       return a.(string) == string(b.([]byte))
+               }
+       case []byte:
+               switch b.(type) {
+               case string:
+                       return b.(string) == string(a.([]byte))
+               }
+       }
+       return false
+}
+
+// ClientIP method returns client IP address from HTTP request.
+//
+// Note: Set property "app.behind.proxy" to true only if Revel is running
+// behind proxy like nginx, haproxy, apache, etc. Otherwise
+// you may get inaccurate Client IP address. Revel parses the
+// IP address in the order of X-Forwarded-For, X-Real-IP.
+//
+// By default revel will get http.Request's RemoteAddr
+func ClientIP(r *Request) string {
+       if Config.BoolDefault("app.behind.proxy", false) {
+               // Header X-Forwarded-For
+               if fwdFor := strings.TrimSpace(r.GetHttpHeader(HdrForwardedFor)); fwdFor != "" {
+                       index := strings.Index(fwdFor, ",")
+                       if index == -1 {
+                               return fwdFor
+                       }
+                       return fwdFor[:index]
+               }
+
+               // Header X-Real-Ip
+               if realIP := strings.TrimSpace(r.GetHttpHeader(HdrRealIP)); realIP != "" {
+                       return realIP
+               }
+       }
+
+       if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+               return remoteAddr
+       }
+
+       return ""
+}
+
+// Walk method extends filepath.Walk to also follow symlinks.
+// Always returns the path of the file or directory.
+func Walk(root string, walkFn filepath.WalkFunc) error {
+       return fsWalk(root, root, walkFn)
+}
+
+// createDir method creates nested directories if not exists
+func createDir(path string) error {
+       if _, err := os.Stat(path); err != nil {
+               if os.IsNotExist(err) {
+                       if err = os.MkdirAll(path, 0755); err != nil {
+                               return fmt.Errorf("Failed to create directory '%v': %v", path, err)
+                       }
+               } else {
+                       return fmt.Errorf("Failed to create directory '%v': %v", path, err)
+               }
+       }
+       return nil
+}
+
+func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
+       fsWalkFunc := func(path string, info os.FileInfo, err error) error {
+               if err != nil {
+                       return err
+               }
+
+               var name string
+               name, err = filepath.Rel(fname, path)
+               if err != nil {
+                       return err
+               }
+
+               path = filepath.Join(linkName, name)
+
+               if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
+                       var symlinkPath string
+                       symlinkPath, err = filepath.EvalSymlinks(path)
+                       if err != nil {
+                               return err
+                       }
+
+                       // https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
+                       info, err = os.Lstat(symlinkPath)
+
+                       if err != nil {
+                               return walkFn(path, info, err)
+                       }
+
+                       if info.IsDir() {
+                               return fsWalk(symlinkPath, path, walkFn)
+                       }
+               }
+
+               return walkFn(path, info, err)
+       }
+       err := filepath.Walk(fname, fsWalkFunc)
+       return err
+}
+
+func init() {
+       OnAppStart(LoadMimeConfig)
+}
diff --git a/src/foundation/api/revel/util_test.go b/src/foundation/api/revel/util_test.go
new file mode 100644 (file)
index 0000000..96f19ef
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "path/filepath"
+       "reflect"
+       "testing"
+)
+
+func TestContentTypeByFilename(t *testing.T) {
+       testCases := map[string]string{
+               "xyz.jpg":       "image/jpeg",
+               "helloworld.c":  "text/x-c; charset=utf-8",
+               "helloworld.":   "application/octet-stream",
+               "helloworld":    "application/octet-stream",
+               "hello.world.c": "text/x-c; charset=utf-8",
+       }
+       srcPath, _ := findSrcPaths(RevelImportPath)
+       ConfPaths = []string{filepath.Join(
+               srcPath,
+               filepath.FromSlash(RevelImportPath),
+               "conf"),
+       }
+       LoadMimeConfig()
+       for filename, expected := range testCases {
+               actual := ContentTypeByFilename(filename)
+               if actual != expected {
+                       t.Errorf("%s: %s, Expected %s", filename, actual, expected)
+               }
+       }
+}
+
+func TestEqual(t *testing.T) {
+       type testStruct struct{}
+       type testStruct2 struct{}
+       i, i2 := 8, 9
+       s, s2 := "@朕µ\n\tüöäß", "@朕µ\n\tüöäss"
+       slice, slice2 := []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}
+       slice3, slice4 := []int{5, 4, 3, 2, 1}, []int{5, 4, 3, 2, 1}
+
+       tm := map[string][]interface{}{
+               "slices":   {slice, slice2},
+               "slices2":  {slice3, slice4},
+               "types":    {new(testStruct), new(testStruct)},
+               "types2":   {new(testStruct2), new(testStruct2)},
+               "ints":     {int(i), int8(i), int16(i), int32(i), int64(i)},
+               "ints2":    {int(i2), int8(i2), int16(i2), int32(i2), int64(i2)},
+               "uints":    {uint(i), uint8(i), uint16(i), uint32(i), uint64(i)},
+               "uints2":   {uint(i2), uint8(i2), uint16(i2), uint32(i2), uint64(i2)},
+               "floats":   {float32(i), float64(i)},
+               "floats2":  {float32(i2), float64(i2)},
+               "strings":  {[]byte(s), s},
+               "strings2": {[]byte(s2), s2},
+       }
+
+       testRow := func(row, row2 string, expected bool) {
+               for _, a := range tm[row] {
+                       for _, b := range tm[row2] {
+                               ok := Equal(a, b)
+                               if ok != expected {
+                                       ak := reflect.TypeOf(a).Kind()
+                                       bk := reflect.TypeOf(b).Kind()
+                                       t.Errorf("eq(%s=%v,%s=%v) want %t got %t", ak, a, bk, b, expected, ok)
+                               }
+                       }
+               }
+       }
+
+       testRow("slices", "slices", true)
+       testRow("slices", "slices2", false)
+       testRow("slices2", "slices", false)
+
+       testRow("types", "types", true)
+       testRow("types2", "types", false)
+       testRow("types", "types2", false)
+
+       testRow("ints", "ints", true)
+       testRow("ints", "ints2", false)
+       testRow("ints2", "ints", false)
+
+       testRow("uints", "uints", true)
+       testRow("uints2", "uints", false)
+       testRow("uints", "uints2", false)
+
+       testRow("floats", "floats", true)
+       testRow("floats2", "floats", false)
+       testRow("floats", "floats2", false)
+
+       testRow("strings", "strings", true)
+       testRow("strings2", "strings", false)
+       testRow("strings", "strings2", false)
+}
diff --git a/src/foundation/api/revel/utils/simplestack.go b/src/foundation/api/revel/utils/simplestack.go
new file mode 100644 (file)
index 0000000..bb22430
--- /dev/null
@@ -0,0 +1,103 @@
+package utils
+
+import (
+       "fmt"
+       "sync"
+)
+
+type (
+       SimpleLockStack struct {
+               Current  *SimpleLockStackElement
+               Creator  func() interface{}
+               len      int
+               capacity int
+               active   int
+               maxsize  int
+               lock     sync.Mutex
+       }
+       SimpleLockStackElement struct {
+               Value    interface{}
+               Previous *SimpleLockStackElement
+               Next     *SimpleLockStackElement
+       }
+       ObjectDestroy interface {
+               Destroy()
+       }
+)
+
+func NewStackLock(startsize, maxsize int, creator func() interface{}) *SimpleLockStack {
+       ss := &SimpleLockStack{lock: sync.Mutex{}, Current: &SimpleLockStackElement{Value: creator()}, Creator: creator, maxsize: maxsize}
+       if startsize > 0 {
+               elements := make([]SimpleLockStackElement, startsize-1)
+               current := ss.Current
+               for i := range elements {
+                       e := elements[i]
+                       if creator != nil {
+                               e.Value = creator()
+                       }
+                       current.Next = &e
+                       e.Previous = current
+                       current = &e
+               }
+               ss.capacity, ss.len, ss.active = startsize, startsize, 0
+
+               ss.Current = current
+       }
+       return ss
+}
+func (s *SimpleLockStack) Pop() (value interface{}) {
+       s.lock.Lock()
+       defer s.lock.Unlock()
+       if s.len == 0 {
+               // Pool is empty, create a new item to return
+               if s.Creator != nil {
+                       value = s.Creator()
+               }
+       } else {
+               value = s.Current.Value
+               s.len--
+               if s.Current.Previous != nil {
+                       s.Current = s.Current.Previous
+               }
+       }
+       // println("Pop ",value, s.len, s.active, s.capacity, s.Current.Next)
+       s.active++
+       return
+}
+func (s *SimpleLockStack) Push(value interface{}) {
+       if d, ok := value.(ObjectDestroy); ok {
+               d.Destroy()
+       }
+       s.lock.Lock()
+       defer s.lock.Unlock()
+       if s.len == 0 {
+               s.Current.Value = value
+       } else if s.len < s.maxsize {
+               if s.Current.Next == nil {
+                       s.Current.Next = &SimpleLockStackElement{Value: value, Previous: s.Current}
+                       s.capacity++
+               } else {
+                       s.Current.Next.Value = value
+               }
+               s.Current = s.Current.Next
+       } else {
+               // If we exceeded the capacity of stack do not store the created object
+               return
+       }
+       s.len++
+       s.active--
+       //println("Push ",value, s.len, s.active, s.capacity)
+       return
+}
+func (s *SimpleLockStack) Len() int {
+       return s.len
+}
+func (s *SimpleLockStack) Capacity() int {
+       return s.capacity
+}
+func (s *SimpleLockStack) Active() int {
+       return s.active
+}
+func (s *SimpleLockStack) String() string {
+       return fmt.Sprintf("SS: Capacity:%d Active:%d Stored:%d", s.capacity, s.active, s.len)
+}
diff --git a/src/foundation/api/revel/utils/simplestack_test.go b/src/foundation/api/revel/utils/simplestack_test.go
new file mode 100644 (file)
index 0000000..3a93740
--- /dev/null
@@ -0,0 +1,127 @@
+package utils
+
+import (
+       "testing"
+)
+
+type SimpleStackTest struct {
+       index int
+}
+
+func TestUnique(b *testing.T) {
+       stack := NewStackLock(10, 40, func() interface{} {
+               newone := &SimpleStackTest{}
+               return newone
+       })
+       values := []interface{}{}
+       for x := 0; x < 10; x++ {
+               values = append(values, stack.Pop())
+       }
+       if stack.active != 10 {
+               b.Errorf("Failed to match 10 active %v ", stack.active)
+       }
+       value1 := stack.Pop().(*SimpleStackTest)
+       value1.index = stack.active
+       value2 := stack.Pop().(*SimpleStackTest)
+       value2.index = stack.active
+       value3 := stack.Pop().(*SimpleStackTest)
+       value3.index = stack.active
+
+       if !isDifferent(value1, value2, value3) {
+               b.Errorf("Failed to get unique values")
+       }
+
+       if stack.active != 13 {
+               b.Errorf("Failed to match 13 active %v ", stack.active)
+       }
+
+       for _, v := range values {
+               stack.Push(v)
+       }
+       if stack.len != 10 {
+               b.Errorf("Failed to match 10 len %v ", stack.len)
+       }
+       if stack.capacity != 10 {
+               b.Errorf("Failed to capacity 10 len %v ", stack.capacity)
+       }
+
+       stack.Push(value1)
+       stack.Push(value2)
+       stack.Push(value3)
+       if stack.capacity != 13 {
+               b.Errorf("Failed to capacity 13 len %v ", stack.capacity)
+       }
+
+       value1 = stack.Pop().(*SimpleStackTest)
+       value2 = stack.Pop().(*SimpleStackTest)
+       value3 = stack.Pop().(*SimpleStackTest)
+       println(value1, value2, value3)
+       if !isDifferent(value1, value2, value3) {
+               b.Errorf("Failed to get unique values")
+       }
+
+}
+func TestLimits(b *testing.T) {
+       stack := NewStackLock(10, 20, func() interface{} {
+               newone := &SimpleStackTest{}
+               return newone
+       })
+       values := []interface{}{}
+       for x := 0; x < 50; x++ {
+               values = append(values, stack.Pop())
+       }
+       if stack.active != 50 {
+               b.Errorf("Failed to match 50 active %v ", stack.active)
+       }
+       for _, v := range values {
+               stack.Push(v)
+       }
+       if stack.Capacity() != 20 {
+               b.Errorf("Failed to match 20 capcity %v ", stack.Capacity())
+       }
+
+}
+func isDifferent(values ...*SimpleStackTest) bool {
+       if len(values) == 2 {
+               return values[0] != values[1]
+       }
+       for _, v := range values[1:] {
+               if values[0] == v {
+                       return false
+               }
+       }
+       return isDifferent(values[1:]...)
+}
+
+func BenchmarkCreateWrite(b *testing.B) {
+       stack := NewStackLock(0, 40, func() interface{} { return &SimpleStackTest{} })
+       for x := 0; x < b.N; x++ {
+               stack.Push(x)
+       }
+}
+func BenchmarkAllocWrite(b *testing.B) {
+       stack := NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} })
+       for x := 0; x < b.N; x++ {
+               stack.Push(x)
+       }
+}
+func BenchmarkCreate(b *testing.B) {
+       NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} })
+}
+func BenchmarkParrallel(b *testing.B) {
+       stack := NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} })
+       b.RunParallel(func(pb *testing.PB) {
+               for pb.Next() {
+                       for x := 0; x < 50000; x++ {
+                               stack.Push(x)
+                       }
+               }
+       })
+       b.RunParallel(func(pb *testing.PB) {
+               for pb.Next() {
+                       for x := 0; x < 50000; x++ {
+                               stack.Pop()
+                       }
+               }
+       })
+}
diff --git a/src/foundation/api/revel/validation.go b/src/foundation/api/revel/validation.go
new file mode 100644 (file)
index 0000000..49cf3a9
--- /dev/null
@@ -0,0 +1,334 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+       "regexp"
+       "runtime"
+)
+
+// ValidationError simple struct to store the Message & Key of a validation error
+type ValidationError struct {
+       Message, Key string
+}
+
+// String returns the Message field of the ValidationError struct.
+func (e *ValidationError) String() string {
+       if e == nil {
+               return ""
+       }
+       return e.Message
+}
+
+// Validation context manages data validation and error messages.
+type Validation struct {
+       Errors     []*ValidationError
+       Request    *Request
+       Translator func(locale, message string, args ...interface{}) string
+       keep       bool
+}
+
+// Keep tells revel to set a flash cookie on the client to make the validation
+// errors available for the next request.
+// This is helpful  when redirecting the client after the validation failed.
+// It is good practice to always redirect upon a HTTP POST request. Thus
+// one should use this method when HTTP POST validation failed and redirect
+// the user back to the form.
+func (v *Validation) Keep() {
+       v.keep = true
+}
+
+// Clear *all* ValidationErrors
+func (v *Validation) Clear() {
+       v.Errors = []*ValidationError{}
+}
+
+// HasErrors returns true if there are any (ie > 0) errors. False otherwise.
+func (v *Validation) HasErrors() bool {
+       return len(v.Errors) > 0
+}
+
+// ErrorMap returns the errors mapped by key.
+// If there are multiple validation errors associated with a single key, the
+// first one "wins".  (Typically the first validation will be the more basic).
+func (v *Validation) ErrorMap() map[string]*ValidationError {
+       m := map[string]*ValidationError{}
+       for _, e := range v.Errors {
+               if _, ok := m[e.Key]; !ok {
+                       m[e.Key] = e
+               }
+       }
+       return m
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) Error(message string, args ...interface{}) *ValidationResult {
+       result := v.ValidationResult(false).Message(message, args...)
+       v.Errors = append(v.Errors, result.Error)
+       return result
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) ErrorKey(message string, args ...interface{}) *ValidationResult {
+       result := v.ValidationResult(false).MessageKey(message, args...)
+       v.Errors = append(v.Errors, result.Error)
+       return result
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) ValidationResult(ok bool) *ValidationResult {
+       if ok {
+               return &ValidationResult{Ok: ok}
+       } else {
+               return &ValidationResult{Ok: ok, Error: &ValidationError{}, Locale: v.Request.Locale, Translator: v.Translator}
+       }
+}
+
+// ValidationResult is returned from every validation method.
+// It provides an indication of success, and a pointer to the Error (if any).
+type ValidationResult struct {
+       Error      *ValidationError
+       Ok         bool
+       Locale     string
+       Translator func(locale, message string, args ...interface{}) string
+}
+
+// Key sets the ValidationResult's Error "key" and returns itself for chaining
+func (r *ValidationResult) Key(key string) *ValidationResult {
+       if r.Error != nil {
+               r.Error.Key = key
+       }
+       return r
+}
+
+// Message sets the error message for a ValidationResult. Returns itself to
+// allow chaining.  Allows Sprintf() type calling with multiple parameters
+func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult {
+       if r.Error != nil {
+               if len(args) == 0 {
+                       r.Error.Message = message
+               } else {
+                       r.Error.Message = fmt.Sprintf(message, args...)
+               }
+       }
+       return r
+}
+
+// Allow a message key to be passed into the validation result. The Validation has already
+// setup the translator to translate the message key
+func (r *ValidationResult) MessageKey(message string, args ...interface{}) *ValidationResult {
+       if r.Error == nil {
+               return r
+       }
+
+       // If translator found, use that to create the message, otherwise call Message method
+       if r.Translator != nil {
+               r.Error.Message = r.Translator(r.Locale, message, args...)
+       } else {
+               r.Message(message, args...)
+       }
+
+       return r
+}
+
+// Required tests that the argument is non-nil and non-empty (if string or list)
+func (v *Validation) Required(obj interface{}) *ValidationResult {
+       return v.apply(Required{}, obj)
+}
+
+func (v *Validation) Min(n int, min int) *ValidationResult {
+       return v.MinFloat(float64(n), float64(min))
+}
+
+func (v *Validation) MinFloat(n float64, min float64) *ValidationResult {
+       return v.apply(Min{min}, n)
+}
+
+func (v *Validation) Max(n int, max int) *ValidationResult {
+       return v.MaxFloat(float64(n), float64(max))
+}
+
+func (v *Validation) MaxFloat(n float64, max float64) *ValidationResult {
+       return v.apply(Max{max}, n)
+}
+
+func (v *Validation) Range(n, min, max int) *ValidationResult {
+       return v.RangeFloat(float64(n), float64(min), float64(max))
+}
+
+func (v *Validation) RangeFloat(n, min, max float64) *ValidationResult {
+       return v.apply(Range{Min{min}, Max{max}}, n)
+}
+
+func (v *Validation) MinSize(obj interface{}, min int) *ValidationResult {
+       return v.apply(MinSize{min}, obj)
+}
+
+func (v *Validation) MaxSize(obj interface{}, max int) *ValidationResult {
+       return v.apply(MaxSize{max}, obj)
+}
+
+func (v *Validation) Length(obj interface{}, n int) *ValidationResult {
+       return v.apply(Length{n}, obj)
+}
+
+func (v *Validation) Match(str string, regex *regexp.Regexp) *ValidationResult {
+       return v.apply(Match{regex}, str)
+}
+
+func (v *Validation) Email(str string) *ValidationResult {
+       return v.apply(Email{Match{emailPattern}}, str)
+}
+
+func (v *Validation) IPAddr(str string, cktype ...int) *ValidationResult {
+       return v.apply(IPAddr{cktype}, str)
+}
+
+func (v *Validation) MacAddr(str string) *ValidationResult {
+       return v.apply(IPAddr{}, str)
+}
+
+func (v *Validation) Domain(str string) *ValidationResult {
+       return v.apply(Domain{}, str)
+}
+
+func (v *Validation) URL(str string) *ValidationResult {
+       return v.apply(URL{}, str)
+}
+
+func (v *Validation) PureText(str string, m int) *ValidationResult {
+       return v.apply(PureText{m}, str)
+}
+
+func (v *Validation) FilePath(str string, m int) *ValidationResult {
+       return v.apply(FilePath{m}, str)
+}
+
+func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult {
+       if chk.IsSatisfied(obj) {
+               return v.ValidationResult(true)
+       }
+
+       // Get the default key.
+       var key string
+       if pc, _, line, ok := runtime.Caller(2); ok {
+               f := runtime.FuncForPC(pc)
+               if defaultKeys, ok := DefaultValidationKeys[f.Name()]; ok {
+                       key = defaultKeys[line]
+               }
+       } else {
+               utilLog.Error("Validation: Failed to get Caller information to look up Validation key")
+       }
+
+       // Add the error to the validation context.
+       err := &ValidationError{
+               Message: chk.DefaultMessage(),
+               Key:     key,
+       }
+       v.Errors = append(v.Errors, err)
+
+       // Also return it in the result.
+       vr := v.ValidationResult(false)
+       vr.Error = err
+       return vr
+}
+
+// Check applies a group of validators to a field, in order, and return the
+// ValidationResult from the first one that fails, or the last one that
+// succeeds.
+func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult {
+       var result *ValidationResult
+       for _, check := range checks {
+               result = v.apply(check, obj)
+               if !result.Ok {
+                       return result
+               }
+       }
+       return result
+}
+
+// ValidationFilter revel Filter function to be hooked into the filter chain.
+func ValidationFilter(c *Controller, fc []Filter) {
+       // If json request, we shall assume json response is intended,
+       // as such no validation cookies should be tied response
+       if c.Params != nil && c.Params.JSON != nil {
+               c.Validation = &Validation{Request: c.Request, Translator: MessageFunc}
+               fc[0](c, fc[1:])
+       } else {
+               errors, err := restoreValidationErrors(c.Request)
+               c.Validation = &Validation{
+                       Errors:     errors,
+                       keep:       false,
+                       Request:    c.Request,
+                       Translator: MessageFunc,
+               }
+               hasCookie := (err != http.ErrNoCookie)
+
+               fc[0](c, fc[1:])
+
+               // Add Validation errors to ViewArgs.
+               c.ViewArgs["errors"] = c.Validation.ErrorMap()
+
+               // Store the Validation errors
+               var errorsValue string
+               if c.Validation.keep {
+                       for _, err := range c.Validation.Errors {
+                               if err.Message != "" {
+                                       errorsValue += "\x00" + err.Key + ":" + err.Message + "\x00"
+                               }
+                       }
+               }
+
+               // When there are errors from Validation and Keep() has been called, store the
+               // values in a cookie. If there previously was a cookie but no errors, remove
+               // the cookie.
+               if errorsValue != "" {
+                       c.SetCookie(&http.Cookie{
+                               Name:     CookiePrefix + "_ERRORS",
+                               Value:    url.QueryEscape(errorsValue),
+                               Domain:   CookieDomain,
+                               Path:     "/",
+                               HttpOnly: true,
+                               Secure:   CookieSecure,
+                       })
+               } else if hasCookie {
+                       c.SetCookie(&http.Cookie{
+                               Name:     CookiePrefix + "_ERRORS",
+                               MaxAge:   -1,
+                               Domain:   CookieDomain,
+                               Path:     "/",
+                               HttpOnly: true,
+                               Secure:   CookieSecure,
+                       })
+               }
+       }
+}
+
+// Restore Validation.Errors from a request.
+func restoreValidationErrors(req *Request) ([]*ValidationError, error) {
+       var (
+               err    error
+               cookie ServerCookie
+               errors = make([]*ValidationError, 0, 5)
+       )
+       if cookie, err = req.Cookie(CookiePrefix + "_ERRORS"); err == nil {
+               ParseKeyValueCookie(cookie.GetValue(), func(key, val string) {
+                       errors = append(errors, &ValidationError{
+                               Key:     key,
+                               Message: val,
+                       })
+               })
+       }
+       return errors, err
+}
+
+// DefaultValidationKeys register default validation keys for all calls to Controller.Validation.Func().
+// Map from (package).func => (line => name of first arg to Validation func)
+// E.g. "myapp/controllers.helper" or "myapp/controllers.(*Application).Action"
+// This is set on initialization in the generated main.go file.
+var DefaultValidationKeys map[string]map[int]string
diff --git a/src/foundation/api/revel/validation_test.go b/src/foundation/api/revel/validation_test.go
new file mode 100644 (file)
index 0000000..a3a7ac7
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "net/http"
+       "net/http/httptest"
+       "testing"
+)
+
+// getRecordedCookie returns the recorded cookie from a ResponseRecorder with
+// the given name. It utilizes the cookie reader found in the standard library.
+func getRecordedCookie(recorder *httptest.ResponseRecorder, name string) (*http.Cookie, error) {
+       r := &http.Response{Header: recorder.HeaderMap}
+       for _, cookie := range r.Cookies() {
+               if cookie.Name == name {
+                       return cookie, nil
+               }
+       }
+       return nil, http.ErrNoCookie
+}
+
+// r.Original.URL.String()
+func validationTester(req *Request, fn func(c *Controller)) *httptest.ResponseRecorder {
+       recorder := httptest.NewRecorder()
+       c := NewTestController(recorder, req.In.GetRaw().(*http.Request))
+       c.Request = req
+
+       ValidationFilter(c, []Filter{I18nFilter, func(c *Controller, _ []Filter) {
+               fn(c)
+       }})
+       return recorder
+}
+
+// Test that errors are encoded into the _ERRORS cookie.
+func TestValidationWithError(t *testing.T) {
+       recorder := validationTester(buildEmptyRequest().Request, func(c *Controller) {
+               c.Validation.Required("")
+               if !c.Validation.HasErrors() {
+                       t.Fatal("errors should be present")
+               }
+               c.Validation.Keep()
+       })
+
+       if cookie, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != nil {
+               t.Fatal(err)
+       } else if cookie.MaxAge < 0 {
+               t.Fatalf("cookie should not expire")
+       }
+}
+
+// Test that no cookie is sent if errors are found, but Keep() is not called.
+func TestValidationNoKeep(t *testing.T) {
+       recorder := validationTester(buildEmptyRequest().Request, func(c *Controller) {
+               c.Validation.Required("")
+               if !c.Validation.HasErrors() {
+                       t.Fatal("errors should not be present")
+               }
+       })
+
+       if _, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != http.ErrNoCookie {
+               t.Fatal(err)
+       }
+}
+
+// Test that a previously set _ERRORS cookie is deleted if no errors are found.
+func TestValidationNoKeepCookiePreviouslySet(t *testing.T) {
+       req := buildRequestWithCookie("REVEL_ERRORS", "invalid").Request
+       recorder := validationTester(req, func(c *Controller) {
+               c.Validation.Required("success")
+               if c.Validation.HasErrors() {
+                       t.Fatal("errors should not be present")
+               }
+       })
+
+       if cookie, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != nil {
+               t.Fatal(err)
+       } else if cookie.MaxAge >= 0 {
+               t.Fatalf("cookie should be deleted")
+       }
+}
+
+func TestValidateMessageKey(t *testing.T) {
+       Init("prod", "github.com/revel/revel/testdata", "")
+       loadMessages(testDataPath)
+
+       // Assert that we have the expected number of languages
+       if len(MessageLanguages()) != 2 {
+               t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages()))
+       }
+       req := buildRequestWithAcceptLanguages("nl").Request
+
+       validationTester(req, func(c *Controller) {
+               c.Validation.Required("").MessageKey("greeting")
+               if msg := c.Validation.Errors[0].Message; msg != "Hallo" {
+                       t.Errorf("Failed expected message Hallo got %s", msg)
+               }
+
+               if !c.Validation.HasErrors() {
+                       t.Fatal("errors should not be present")
+               }
+       })
+
+}
diff --git a/src/foundation/api/revel/validators.go b/src/foundation/api/revel/validators.go
new file mode 100644 (file)
index 0000000..31aef42
--- /dev/null
@@ -0,0 +1,633 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "errors"
+       "fmt"
+       "html"
+       "net"
+       "net/url"
+       "reflect"
+       "regexp"
+       "strconv"
+       "strings"
+       "unicode/utf8"
+)
+
+type Validator interface {
+       IsSatisfied(interface{}) bool
+       DefaultMessage() string
+}
+
+type Required struct{}
+
+func ValidRequired() Required {
+       return Required{}
+}
+
+func (r Required) IsSatisfied(obj interface{}) bool {
+       if obj == nil {
+               return false
+       }
+       switch v := reflect.ValueOf(obj); v.Kind() {
+       case reflect.Array, reflect.Slice, reflect.Map, reflect.String, reflect.Chan:
+               if v.Len() == 0 {
+                       return false
+               }
+       case reflect.Ptr:
+               return r.IsSatisfied(reflect.Indirect(v).Interface())
+       }
+       return !reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface())
+}
+
+func (r Required) DefaultMessage() string {
+       return fmt.Sprintln("Required")
+}
+
+type Min struct {
+       Min float64
+}
+
+func ValidMin(min int) Min {
+       return ValidMinFloat(float64(min))
+}
+
+func ValidMinFloat(min float64) Min {
+       return Min{min}
+}
+
+func (m Min) IsSatisfied(obj interface{}) bool {
+       var (
+               num float64
+               ok  bool
+       )
+       switch reflect.TypeOf(obj).Kind() {
+       case reflect.Float64:
+               num, ok = obj.(float64)
+       case reflect.Float32:
+               ok = true
+               num = float64(obj.(float32))
+       case reflect.Int:
+               ok = true
+               num = float64(obj.(int))
+       }
+
+       if ok {
+               return num >= m.Min
+       }
+       return false
+}
+
+func (m Min) DefaultMessage() string {
+       return fmt.Sprintln("Minimum is", m.Min)
+}
+
+type Max struct {
+       Max float64
+}
+
+func ValidMax(max int) Max {
+       return ValidMaxFloat(float64(max))
+}
+
+func ValidMaxFloat(max float64) Max {
+       return Max{max}
+}
+
+func (m Max) IsSatisfied(obj interface{}) bool {
+       var (
+               num float64
+               ok  bool
+       )
+       switch reflect.TypeOf(obj).Kind() {
+       case reflect.Float64:
+               num, ok = obj.(float64)
+       case reflect.Float32:
+               ok = true
+               num = float64(obj.(float32))
+       case reflect.Int:
+               ok = true
+               num = float64(obj.(int))
+       }
+
+       if ok {
+               return num <= m.Max
+       }
+       return false
+}
+
+func (m Max) DefaultMessage() string {
+       return fmt.Sprintln("Maximum is", m.Max)
+}
+
+// Range requires an integer to be within Min, Max inclusive.
+type Range struct {
+       Min
+       Max
+}
+
+func ValidRange(min, max int) Range {
+       return ValidRangeFloat(float64(min), float64(max))
+}
+
+func ValidRangeFloat(min, max float64) Range {
+       return Range{Min{min}, Max{max}}
+}
+
+func (r Range) IsSatisfied(obj interface{}) bool {
+       return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
+}
+
+func (r Range) DefaultMessage() string {
+       return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max)
+}
+
+// MinSize requires an array or string to be at least a given length.
+type MinSize struct {
+       Min int
+}
+
+func ValidMinSize(min int) MinSize {
+       return MinSize{min}
+}
+
+func (m MinSize) IsSatisfied(obj interface{}) bool {
+       if str, ok := obj.(string); ok {
+               return utf8.RuneCountInString(str) >= m.Min
+       }
+       v := reflect.ValueOf(obj)
+       if v.Kind() == reflect.Slice {
+               return v.Len() >= m.Min
+       }
+       return false
+}
+
+func (m MinSize) DefaultMessage() string {
+       return fmt.Sprintln("Minimum size is", m.Min)
+}
+
+// MaxSize requires an array or string to be at most a given length.
+type MaxSize struct {
+       Max int
+}
+
+func ValidMaxSize(max int) MaxSize {
+       return MaxSize{max}
+}
+
+func (m MaxSize) IsSatisfied(obj interface{}) bool {
+       if str, ok := obj.(string); ok {
+               return utf8.RuneCountInString(str) <= m.Max
+       }
+       v := reflect.ValueOf(obj)
+       if v.Kind() == reflect.Slice {
+               return v.Len() <= m.Max
+       }
+       return false
+}
+
+func (m MaxSize) DefaultMessage() string {
+       return fmt.Sprintln("Maximum size is", m.Max)
+}
+
+// Length requires an array or string to be exactly a given length.
+type Length struct {
+       N int
+}
+
+func ValidLength(n int) Length {
+       return Length{n}
+}
+
+func (s Length) IsSatisfied(obj interface{}) bool {
+       if str, ok := obj.(string); ok {
+               return utf8.RuneCountInString(str) == s.N
+       }
+       v := reflect.ValueOf(obj)
+       if v.Kind() == reflect.Slice {
+               return v.Len() == s.N
+       }
+       return false
+}
+
+func (s Length) DefaultMessage() string {
+       return fmt.Sprintln("Required length is", s.N)
+}
+
+// Match requires a string to match a given regex.
+type Match struct {
+       Regexp *regexp.Regexp
+}
+
+func ValidMatch(regex *regexp.Regexp) Match {
+       return Match{regex}
+}
+
+func (m Match) IsSatisfied(obj interface{}) bool {
+       str := obj.(string)
+       return m.Regexp.MatchString(str)
+}
+
+func (m Match) DefaultMessage() string {
+       return fmt.Sprintln("Must match", m.Regexp)
+}
+
+var emailPattern = regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$")
+
+type Email struct {
+       Match
+}
+
+func ValidEmail() Email {
+       return Email{Match{emailPattern}}
+}
+
+func (e Email) DefaultMessage() string {
+       return fmt.Sprintln("Must be a valid email address")
+}
+
+const (
+       None               = 0
+       IPAny              = 1
+       IPv4               = 32 // IPv4 (32 chars)
+       IPv6               = 39 // IPv6(39 chars)
+       IPv4MappedIPv6     = 45 // IP4-mapped IPv6 (45 chars) , Ex) ::FFFF:129.144.52.38
+       IPv4CIDR           = IPv4 + 3
+       IPv6CIDR           = IPv6 + 3
+       IPv4MappedIPv6CIDR = IPv4MappedIPv6 + 3
+)
+
+// Requires a string(IP Address) to be within IP Pattern type inclusive.
+type IPAddr struct {
+       Vaildtypes []int
+}
+
+// Requires an IP Address string to be exactly a given  validation type (IPv4, IPv6, IPv4MappedIPv6, IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR OR IPAny)
+func ValidIPAddr(cktypes ...int) IPAddr {
+
+       for _, cktype := range cktypes {
+
+               if cktype != IPAny && cktype != IPv4 && cktype != IPv6 && cktype != IPv4MappedIPv6 && cktype != IPv4CIDR && cktype != IPv6CIDR && cktype != IPv4MappedIPv6CIDR {
+                       return IPAddr{Vaildtypes: []int{None}}
+               }
+       }
+
+       return IPAddr{Vaildtypes: cktypes}
+}
+
+func isWithCIDR(str string, l int) bool {
+
+       if str[l-3] == '/' || str[l-2] == '/' {
+
+               cidr_bit := strings.Split(str, "/")
+               if 2 == len(cidr_bit) {
+                       bit, err := strconv.Atoi(cidr_bit[1])
+                       //IPv4 : 0~32, IPv6 : 0 ~ 128
+                       if err == nil && bit >= 0 && bit <= 128 {
+                               return true
+                       }
+               }
+       }
+
+       return false
+}
+
+func getIPType(str string, l int) int {
+
+       if l < 3 { //least 3 chars (::F)
+               return None
+       }
+
+       has_dot := strings.Index(str[2:], ".")
+       has_colon := strings.Index(str[2:], ":")
+
+       switch {
+       case has_dot > -1 && has_colon == -1 && l >= 7 && l <= IPv4CIDR:
+               if isWithCIDR(str, l) == true {
+                       return IPv4CIDR
+               } else {
+                       return IPv4
+               }
+       case has_dot == -1 && has_colon > -1 && l >= 6 && l <= IPv6CIDR:
+               if isWithCIDR(str, l) == true {
+                       return IPv6CIDR
+               } else {
+                       return IPv6
+               }
+
+       case has_dot > -1 && has_colon > -1 && l >= 14 && l <= IPv4MappedIPv6:
+               if isWithCIDR(str, l) == true {
+                       return IPv4MappedIPv6CIDR
+               } else {
+                       return IPv4MappedIPv6
+               }
+       }
+
+       return None
+}
+
+func (i IPAddr) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+
+               l := len(str)
+               ret := getIPType(str, l)
+
+               for _, ck := range i.Vaildtypes {
+
+                       if ret != None && (ck == ret || ck == IPAny) {
+
+                               switch ret {
+                               case IPv4, IPv6, IPv4MappedIPv6:
+                                       ip := net.ParseIP(str)
+
+                                       if ip != nil {
+                                               return true
+                                       }
+
+                               case IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR:
+                                       _, _, err := net.ParseCIDR(str)
+                                       if err == nil {
+                                               return true
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return false
+}
+
+func (i IPAddr) DefaultMessage() string {
+       return fmt.Sprintln("Must be a vaild IP address")
+}
+
+// Requires a MAC Address string to be exactly
+type MacAddr struct{}
+
+func ValidMacAddr() MacAddr {
+
+       return MacAddr{}
+}
+
+func (m MacAddr) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+               if _, err := net.ParseMAC(str); err == nil {
+                       return true
+               }
+       }
+
+       return false
+}
+
+func (m MacAddr) DefaultMessage() string {
+       return fmt.Sprintln("Must be a vaild MAC address")
+}
+
+var domainPattern = regexp.MustCompile(`^(([a-zA-Z0-9-\p{L}]{1,63}\.)?(xn--)?[a-zA-Z0-9\p{L}]+(-[a-zA-Z0-9\p{L}]+)*\.)+[a-zA-Z\p{L}]{2,63}$`)
+
+// Requires a Domain string to be exactly
+type Domain struct {
+       Regexp *regexp.Regexp
+}
+
+func ValidDomain() Domain {
+       return Domain{domainPattern}
+}
+
+func (d Domain) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+
+               l := len(str)
+               //can't exceed 253 chars.
+               if l > 253 {
+                       return false
+               }
+
+               //first and last char must be alphanumeric
+               if str[l-1] == 46 || str[0] == 46 {
+                       return false
+               }
+
+               return domainPattern.MatchString(str)
+       }
+
+       return false
+}
+
+func (d Domain) DefaultMessage() string {
+       return fmt.Sprintln("Must be a vaild domain address")
+}
+
+var urlPattern = regexp.MustCompile(`^((((https?|ftps?|gopher|telnet|nntp)://)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-()_.!~*';/?:@#&=+$,A-Za-z0-9\p{L}])+)([).!';/?:,][[:blank:]])?$`)
+
+type URL struct {
+       Domain
+}
+
+func ValidURL() URL {
+       return URL{Domain: ValidDomain()}
+}
+
+func (u URL) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+
+               // TODO : Required lot of testing
+               return urlPattern.MatchString(str)
+       }
+
+       return false
+}
+
+func (u URL) DefaultMessage() string {
+       return fmt.Sprintln("Must be a vaild URL address")
+}
+
+/*
+NORMAL BenchmarkRegex-8        2000000000               0.24 ns/op
+STRICT BenchmarkLoop-8         2000000000               0.01 ns/op
+*/
+const (
+       NORMAL = 0
+       STRICT = 4
+)
+
+// Requires a string to be without invisible characters
+type PureText struct {
+       Mode int
+}
+
+func ValidPureText(m int) PureText {
+       if m != NORMAL && m != STRICT { // Q:required fatal error
+               m = STRICT
+       }
+       return PureText{m}
+}
+
+func isPureTextStrict(str string) (bool, error) {
+
+       l := len(str)
+
+       for i := 0; i < l; i++ {
+
+               c := str[i]
+
+               // deny : control char (00-31 without 9(TAB) and Single 10(LF),13(CR)
+               if c >= 0 && c <= 31 && c != 9 && c != 10 && c != 13 {
+                       return false, errors.New("detect control character")
+               }
+
+               // deny : control char (DEL)
+               if c == 127 {
+                       return false, errors.New("detect control character (DEL)")
+               }
+
+               //deny : html tag (< ~ >)
+               if c == 60 {
+
+                       ds := 0
+                       for n := i; n < l; n++ {
+
+                               // 60 (<) , 47(/) | 33(!) | 63(?)
+                               if str[n] == 60 && n+1 <= l && (str[n+1] == 47 || str[n+1] == 33 || str[n+1] == 63) {
+                                       ds = 1
+                                       n += 3 //jump to next char
+                               }
+
+                               // 62 (>)
+                               if ds == 1 && str[n] == 62 {
+                                       return false, errors.New("detect tag (<[!|?]~>)")
+                               }
+                       }
+               }
+
+               //deby : html encoded tag (&xxx;)
+               if c == 38 && i+1 <= l && str[i+1] != 35 {
+
+                       max := i + 64
+                       if max > l {
+                               max = l
+                       }
+                       for n := i; n < max; n++ {
+                               if str[n] == 59 {
+                                       return false, errors.New("detect html encoded ta (&XXX;)")
+                               }
+                       }
+               }
+       }
+
+       return true, nil
+}
+
+// Requires a string to match a given html tag elements regex pattern
+// referrer : http://www.w3schools.com/Tags/
+var elementPattern = regexp.MustCompile(`(?im)<(?P<tag>(/*\s*|\?*|\!*)(figcaption|expression|blockquote|plaintext|textarea|progress|optgroup|noscript|noframes|menuitem|frameset|fieldset|!DOCTYPE|datalist|colgroup|behavior|basefont|summary|section|isindex|details|caption|bgsound|article|address|acronym|strong|strike|source|select|script|output|option|object|legend|keygen|ilayer|iframe|header|footer|figure|dialog|center|canvas|button|applet|video|track|title|thead|tfoot|tbody|table|style|small|param|meter|layer|label|input|frame|embed|blink|audio|aside|alert|time|span|samp|ruby|meta|menu|mark|main|link|html|head|form|font|code|cite|body|base|area|abbr|xss|xml|wbr|var|svg|sup|sub|pre|nav|map|kbd|ins|img|div|dir|dfn|del|col|big|bdo|bdi|!--|ul|tt|tr|th|td|rt|rp|ol|li|hr|em|dt|dl|dd|br|u|s|q|p|i|b|a|(h[0-9]+)))([^><]*)([><]*)`)
+
+// Requires a string to match a given urlencoded regex pattern
+var urlencodedPattern = regexp.MustCompile(`(?im)(\%[0-9a-fA-F]{1,})`)
+
+// Requires a string to match a given control characters regex pattern (ASCII : 00-08, 11, 12, 14, 15-31)
+var controlcharPattern = regexp.MustCompile(`(?im)([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)`)
+
+func isPureTextNormal(str string) (bool, error) {
+
+       decoded_str := html.UnescapeString(str)
+
+       matched_urlencoded := urlencodedPattern.MatchString(decoded_str)
+       if matched_urlencoded == true {
+               temp_buf, err := url.QueryUnescape(decoded_str)
+               if err == nil {
+                       decoded_str = temp_buf
+               }
+       }
+
+       matched_element := elementPattern.MatchString(decoded_str)
+       if matched_element == true {
+               return false, errors.New("detect html element")
+       }
+
+       matched_cc := controlcharPattern.MatchString(decoded_str)
+       if matched_cc == true {
+               return false, errors.New("detect control character")
+       }
+
+       return true, nil
+}
+
+func (p PureText) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+
+               var ret bool
+               switch p.Mode {
+               case STRICT:
+                       ret, _ = isPureTextStrict(str)
+               case NORMAL:
+                       ret, _ = isPureTextStrict(str)
+               }
+               return ret
+       }
+
+       return false
+}
+
+func (p PureText) DefaultMessage() string {
+       return fmt.Sprintln("Must be a vaild Text")
+}
+
+const (
+       ONLY_FILENAME       = 0
+       ALLOW_RELATIVE_PATH = 1
+)
+
+const regexDenyFileNameCharList = `[\x00-\x1f|\x21-\x2c|\x3b-\x40|\x5b-\x5e|\x60|\x7b-\x7f]+`
+const regexDenyFileName = `|\x2e\x2e\x2f+`
+
+var checkAllowRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + `)`)
+var checkDenyRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + regexDenyFileName + `)`)
+
+// Requires an string to be sanitary file path
+type FilePath struct {
+       Mode int
+}
+
+func ValidFilePath(m int) FilePath {
+
+       if m != ONLY_FILENAME && m != ALLOW_RELATIVE_PATH {
+               m = ONLY_FILENAME
+       }
+       return FilePath{m}
+}
+
+func (f FilePath) IsSatisfied(obj interface{}) bool {
+
+       if str, ok := obj.(string); ok {
+
+               var ret bool
+               switch f.Mode {
+
+               case ALLOW_RELATIVE_PATH:
+                       ret = checkAllowRelativePath.MatchString(str)
+                       if ret == false {
+                               return true
+                       }
+               default: //ONLY_FILENAME
+                       ret = checkDenyRelativePath.MatchString(str)
+                       if ret == false {
+                               return true
+                       }
+               }
+       }
+
+       return false
+}
+
+func (f FilePath) DefaultMessage() string {
+       return fmt.Sprintln("Must be a unsanitary string")
+}
diff --git a/src/foundation/api/revel/validators_test.go b/src/foundation/api/revel/validators_test.go
new file mode 100644 (file)
index 0000000..9baf177
--- /dev/null
@@ -0,0 +1,640 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel_test
+
+import (
+       "fmt"
+       "github.com/revel/revel"
+       "net"
+       "reflect"
+       "regexp"
+       "strings"
+       "testing"
+       "time"
+)
+
+const (
+       errorsMessage   = "validation for %s should not be satisfied with %s\n"
+       noErrorsMessage = "validation for %s should be satisfied with %s\n"
+)
+
+type Expect struct {
+       input          interface{}
+       expectedResult bool
+       errorMessage   string
+}
+
+func performTests(validator revel.Validator, tests []Expect, t *testing.T) {
+       for _, test := range tests {
+               if validator.IsSatisfied(test.input) != test.expectedResult {
+                       if test.expectedResult {
+                               t.Errorf(noErrorsMessage, reflect.TypeOf(validator), test.errorMessage)
+                       } else {
+                               t.Errorf(errorsMessage, reflect.TypeOf(validator), test.errorMessage)
+                       }
+               }
+       }
+}
+
+func TestRequired(t *testing.T) {
+       tests := []Expect{
+               {nil, false, "nil data"},
+               {"Testing", true, "non-empty string"},
+               {"", false, "empty string"},
+               {true, true, "true boolean"},
+               {false, false, "false boolean"},
+               {1, true, "positive integer"},
+               {-1, true, "negative integer"},
+               {0, false, "0 integer"},
+               {time.Now(), true, "current time"},
+               {time.Time{}, false, "a zero time"},
+               {func() {}, true, "other non-nil data types"},
+               {net.IP(""), false, "empty IP address"},
+       }
+
+       // testing both the struct and the helper method
+       for _, required := range []revel.Required{{}, revel.ValidRequired()} {
+               performTests(required, tests, t)
+       }
+}
+
+func TestMin(t *testing.T) {
+       tests := []Expect{
+               {11, true, "val > min"},
+               {10, true, "val == min"},
+               {9, false, "val < min"},
+               {true, false, "TypeOf(val) != int"},
+       }
+       for _, min := range []revel.Min{{10}, revel.ValidMin(10)} {
+               performTests(min, tests, t)
+       }
+}
+
+func TestMax(t *testing.T) {
+       tests := []Expect{
+               {9, true, "val < max"},
+               {10, true, "val == max"},
+               {11, false, "val > max"},
+               {true, false, "TypeOf(val) != int"},
+       }
+       for _, max := range []revel.Max{{10}, revel.ValidMax(10)} {
+               performTests(max, tests, t)
+       }
+}
+
+func TestRange(t *testing.T) {
+       tests := []Expect{
+               {50, true, "min <= val <= max"},
+               {10, true, "val == min"},
+               {100, true, "val == max"},
+               {9, false, "val < min"},
+               {101, false, "val > max"},
+       }
+
+       goodValidators := []revel.Range{
+               {revel.Min{10}, revel.Max{100}},
+               revel.ValidRange(10, 100),
+       }
+       for _, rangeValidator := range goodValidators {
+               performTests(rangeValidator, tests, t)
+       }
+
+       testsFloat := []Expect{
+               {50, true, "min <= val <= max"},
+               {10.25, true, "val == min"},
+               {100, true, "val == max"},
+               {9, false, "val < min"},
+               {101, false, "val > max"},
+       }
+       goodValidatorsFloat := []revel.Range{
+               {revel.Min{10.25}, revel.Max{100.5}},
+               revel.ValidRangeFloat(10.25, 100.5),
+       }
+       for _, rangeValidator := range goodValidatorsFloat {
+               performTests(rangeValidator, testsFloat, t)
+       }
+
+       tests = []Expect{
+               {10, true, "min == val == max"},
+               {9, false, "val < min && val < max && min == max"},
+               {11, false, "val > min && val > max && min == max"},
+       }
+
+       goodValidators = []revel.Range{
+               {revel.Min{10}, revel.Max{10}},
+               revel.ValidRange(10, 10),
+       }
+       for _, rangeValidator := range goodValidators {
+               performTests(rangeValidator, tests, t)
+       }
+
+       tests = make([]Expect, 7)
+       for i, num := range []int{50, 100, 10, 9, 101, 0, -1} {
+               tests[i] = Expect{
+                       num,
+                       false,
+                       "min > val < max",
+               }
+       }
+       // these are min/max with values swapped, so the min is the high
+       // and max is the low. rangeValidator.IsSatisfied() should ALWAYS
+       // result in false since val can never be greater than min and less
+       // than max when min > max
+       badValidators := []revel.Range{
+               {revel.Min{100}, revel.Max{10}},
+               revel.ValidRange(100, 10),
+       }
+       for _, rangeValidator := range badValidators {
+               performTests(rangeValidator, tests, t)
+       }
+
+       badValidatorsFloat := []revel.Range{
+               {revel.Min{100}, revel.Max{10}},
+               revel.ValidRangeFloat(100, 10),
+       }
+       for _, rangeValidator := range badValidatorsFloat {
+               performTests(rangeValidator, tests, t)
+       }
+}
+
+func TestMinSize(t *testing.T) {
+       greaterThanMessage := "len(val) >= min"
+       tests := []Expect{
+               {"12", true, greaterThanMessage},
+               {"123", true, greaterThanMessage},
+               {[]int{1, 2}, true, greaterThanMessage},
+               {[]int{1, 2, 3}, true, greaterThanMessage},
+               {"", false, "len(val) <= min"},
+               {"手", false, "len(val) <= min"},
+               {[]int{}, false, "len(val) <= min"},
+               {nil, false, "TypeOf(val) != string && TypeOf(val) != slice"},
+       }
+
+       for _, minSize := range []revel.MinSize{{2}, revel.ValidMinSize(2)} {
+               performTests(minSize, tests, t)
+       }
+}
+
+func TestMaxSize(t *testing.T) {
+       lessThanMessage := "len(val) <= max"
+       tests := []Expect{
+               {"", true, lessThanMessage},
+               {"12", true, lessThanMessage},
+               {"ルビー", true, lessThanMessage},
+               {[]int{}, true, lessThanMessage},
+               {[]int{1, 2}, true, lessThanMessage},
+               {[]int{1, 2, 3}, true, lessThanMessage},
+               {"1234", false, "len(val) >= max"},
+               {[]int{1, 2, 3, 4}, false, "len(val) >= max"},
+       }
+       for _, maxSize := range []revel.MaxSize{{3}, revel.ValidMaxSize(3)} {
+               performTests(maxSize, tests, t)
+       }
+}
+
+func TestLength(t *testing.T) {
+       tests := []Expect{
+               {"12", true, "len(val) == length"},
+               {"火箭", true, "len(val) == length"},
+               {[]int{1, 2}, true, "len(val) == length"},
+               {"123", false, "len(val) > length"},
+               {[]int{1, 2, 3}, false, "len(val) > length"},
+               {"1", false, "len(val) < length"},
+               {[]int{1}, false, "len(val) < length"},
+               {nil, false, "TypeOf(val) != string && TypeOf(val) != slice"},
+       }
+       for _, length := range []revel.Length{{2}, revel.ValidLength(2)} {
+               performTests(length, tests, t)
+       }
+}
+
+func TestMatch(t *testing.T) {
+       tests := []Expect{
+               {"bca123", true, `"[abc]{3}\d*" matches "bca123"`},
+               {"bc123", false, `"[abc]{3}\d*" does not match "bc123"`},
+               {"", false, `"[abc]{3}\d*" does not match ""`},
+       }
+       regex := regexp.MustCompile(`[abc]{3}\d*`)
+       for _, match := range []revel.Match{{regex}, revel.ValidMatch(regex)} {
+               performTests(match, tests, t)
+       }
+}
+
+func TestEmail(t *testing.T) {
+       // unicode char included
+       validStartingCharacters := strings.Split("!#$%^&*_+1234567890abcdefghijklmnopqrstuvwxyzñ", "")
+       invalidCharacters := strings.Split(" ()", "")
+
+       definiteInvalidDomains := []string{
+               "",                  // any empty string (x@)
+               ".com",              // only the TLD (x@.com)
+               ".",                 // only the . (x@.)
+               ".*",                // TLD containing symbol (x@.*)
+               "asdf",              // no TLD
+               "a!@#$%^&*()+_.com", // characters which are not ASCII/0-9/dash(-) in a domain
+               "-a.com",            // host starting with any symbol
+               "a-.com",            // host ending with any symbol
+               "aå.com",            // domain containing unicode (however, unicode domains do exist in the state of xn--<POINT>.com e.g. å.com = xn--5ca.com)
+       }
+
+       // Email pattern is not exposed
+       emailPattern := regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$")
+       for _, email := range []revel.Email{{revel.Match{emailPattern}}, revel.ValidEmail()} {
+               var currentEmail string
+
+               // test invalid starting chars
+               for _, startingChar := range validStartingCharacters {
+                       currentEmail = fmt.Sprintf("%sñbc+123@do-main.com", startingChar)
+                       if email.IsSatisfied(currentEmail) {
+                               t.Errorf(noErrorsMessage, "starting characters", fmt.Sprintf("email = %s", currentEmail))
+                       }
+
+                       // validation should fail because of multiple @ symbols
+                       currentEmail = fmt.Sprintf("%s@ñbc+123@do-main.com", startingChar)
+                       if email.IsSatisfied(currentEmail) {
+                               t.Errorf(errorsMessage, "starting characters with multiple @ symbols", fmt.Sprintf("email = %s", currentEmail))
+                       }
+
+                       // should fail simply because of the invalid char
+                       for _, invalidChar := range invalidCharacters {
+                               currentEmail = fmt.Sprintf("%sñbc%s+123@do-main.com", startingChar, invalidChar)
+                               if email.IsSatisfied(currentEmail) {
+                                       t.Errorf(errorsMessage, "invalid starting characters", fmt.Sprintf("email = %s", currentEmail))
+                               }
+                       }
+               }
+
+               // test invalid domains
+               for _, invalidDomain := range definiteInvalidDomains {
+                       currentEmail = fmt.Sprintf("a@%s", invalidDomain)
+                       if email.IsSatisfied(currentEmail) {
+                               t.Errorf(errorsMessage, "invalid domain", fmt.Sprintf("email = %s", currentEmail))
+                       }
+               }
+
+               // should always be satisfied
+               if !email.IsSatisfied("t0.est+email123@1abc0-def.com") {
+                       t.Errorf(noErrorsMessage, "guaranteed valid email", fmt.Sprintf("email = %s", "t0.est+email123@1abc0-def.com"))
+               }
+
+               // should never be satisfied (this is redundant given the loops above)
+               if email.IsSatisfied("a@xcom") {
+                       t.Errorf(noErrorsMessage, "guaranteed invalid email", fmt.Sprintf("email = %s", "a@xcom"))
+               }
+               if email.IsSatisfied("a@@x.com") {
+                       t.Errorf(noErrorsMessage, "guaranteed invalid email", fmt.Sprintf("email = %s", "a@@x.com"))
+               }
+       }
+}
+
+func runIPAddrTestfunc(t *testing.T, test_type int, ipaddr_list map[string]bool, msg_fmt string) {
+
+       // generate dataset for test
+       test_ipaddr_list := []Expect{}
+       for ipaddr, expected := range ipaddr_list {
+               test_ipaddr_list = append(test_ipaddr_list, Expect{input: ipaddr, expectedResult: expected, errorMessage: fmt.Sprintf(msg_fmt, ipaddr)})
+       }
+
+       for _, ip_test_list := range []revel.IPAddr{{[]int{test_type}}, revel.ValidIPAddr(test_type)} {
+               performTests(ip_test_list, test_ipaddr_list, t)
+       }
+}
+
+func TestIPAddr(t *testing.T) {
+
+       //IPv4
+       test_ipv4_ipaddrs := map[string]bool{
+               "192.168.1.1":     true,
+               "127.0.0.1":       true,
+               "10.10.90.12":     true,
+               "8.8.8.8":         true,
+               "4.4.4.4":         true,
+               "912.456.123.123": false,
+               "999.999.999.999": false,
+               "192.192.19.999":  false,
+       }
+
+       //IPv4 with CIDR
+       test_ipv4_with_cidr_ipaddrs := map[string]bool{
+               "192.168.1.1/24": true,
+               "127.0.0.1/32":   true,
+               "10.10.90.12/8":  true,
+               "8.8.8.8/1":      true,
+               "4.4.4.4/7":      true,
+               "192.168.1.1/99": false,
+               "127.0.0.1/9999": false,
+               "10.10.90.12/33": false,
+               "8.8.8.8/128":    false,
+               "4.4.4.4/256":    false,
+       }
+
+       //IPv6
+       test_ipv6_ipaddrs := map[string]bool{
+               "2607:f0d0:1002:51::4":                    true,
+               "2607:f0d0:1002:0051:0000:0000:0000:0004": true,
+               "ff05::1:3":                               true,
+               "FE80:0000:0000:0000:0202:B3FF:FE1E:8329": true,
+               "FE80::0202:B3FF:FE1E:8329":               true,
+               "fe80::202:b3ff:fe1e:8329":                true,
+               "fe80:0000:0000:0000:0202:b3ff:fe1e:8329": true,
+               "2001:470:1f09:495::3":                    true,
+               "2001:470:1f1d:275::1":                    true,
+               "2600:9000:5304:200::1":                   true,
+               "2600:9000:5306:d500::1":                  true,
+               "2600:9000:5301:b600::1":                  true,
+               "2600:9000:5303:900::1":                   true,
+               "127:12:12:12:12:12:!2:!2":                false,
+               "127.0.0.1":                               false,
+               "234:23:23:23:23:23:23":                   false,
+       }
+
+       //IPv6 with CIDR
+       test_ipv6_with_cidr_ipaddrs := map[string]bool{
+               "2000::/5":      true,
+               "2000::/15":     true,
+               "2001:db8::/33": true,
+               "2001:db8::/48": true,
+               "fc00::/7":      true,
+       }
+
+       //IPv4-Mapped Embedded IPv6 Address
+       test_ipv4_mapped_ipv6_ipaddrs := map[string]bool{
+               "2001:470:1f09:495::3:217.126.185.215":         true,
+               "2001:470:1f1d:275::1:213.0.69.132":            true,
+               "2600:9000:5304:200::1:205.251.196.2":          true,
+               "2600:9000:5306:d500::1:205.251.198.213":       true,
+               "2600:9000:5301:b600::1:205.251.193.182":       true,
+               "2600:9000:5303:900::1:205.251.195.9":          true,
+               "0:0:0:0:0:FFFF:222.1.41.90":                   true,
+               "::FFFF:222.1.41.90":                           true,
+               "0000:0000:0000:0000:0000:FFFF:12.155.166.101": true,
+               "12.155.166.101":                               false,
+               "12.12/12":                                     false,
+       }
+
+       runIPAddrTestfunc(t, revel.IPv4, test_ipv4_ipaddrs, "invalid (%s) IPv4 address")
+       runIPAddrTestfunc(t, revel.IPv4CIDR, test_ipv4_with_cidr_ipaddrs, "invalid (%s) IPv4 with CIDR address")
+
+       runIPAddrTestfunc(t, revel.IPv6, test_ipv6_ipaddrs, "invalid (%s) IPv6 address")
+       runIPAddrTestfunc(t, revel.IPv6CIDR, test_ipv6_with_cidr_ipaddrs, "invalid (%s) IPv6 with CIDR address")
+       runIPAddrTestfunc(t, revel.IPv4MappedIPv6, test_ipv4_mapped_ipv6_ipaddrs, "invalid (%s) IPv4-Mapped Embedded IPv6 address")
+}
+
+func TestMacAddr(t *testing.T) {
+
+       macaddr_list := map[string]bool{
+               "02:f3:71:eb:9e:4b": true,
+               "02-f3-71-eb-9e-4b": true,
+               "02f3.71eb.9e4b":    true,
+               "87:78:6e:3e:90:40": true,
+               "87-78-6e-3e-90-40": true,
+               "8778.6e3e.9040":    true,
+               "e7:28:b9:57:ab:36": true,
+               "e7-28-b9-57-ab-36": true,
+               "e728.b957.ab36":    true,
+               "eb:f8:2b:d7:e9:62": true,
+               "eb-f8-2b-d7-e9-62": true,
+               "ebf8.2bd7.e962":    true,
+       }
+
+       test_macaddr_list := []Expect{}
+       for macaddr, expected := range macaddr_list {
+               test_macaddr_list = append(test_macaddr_list, Expect{input: macaddr, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) MAC address", macaddr)})
+       }
+
+       for _, mac_test_list := range []revel.MacAddr{{}, revel.ValidMacAddr()} {
+               performTests(mac_test_list, test_macaddr_list, t)
+       }
+}
+
+func TestDomain(t *testing.T) {
+
+       test_domains := map[string]bool{
+               "대한민국.xn-korea.co.kr":           true,
+               "google.com":                    true,
+               "masełkowski.pl":                true,
+               "maselkowski.pl":                true,
+               "m.maselkowski.pl":              true,
+               "www.masełkowski.pl.com":        true,
+               "xn--masekowski-d0b.pl":         true,
+               "中国互联网络信息中心.中国":                 true,
+               "masełkowski.pl.":               false,
+               "中国互联网络信息中心.xn--masekowski-d0b": false,
+               "a.jp":                     true,
+               "a.co":                     true,
+               "a.co.jp":                  true,
+               "a.co.or":                  true,
+               "a.or.kr":                  true,
+               "qwd-qwdqwd.com":           true,
+               "qwd-qwdqwd.co_m":          false,
+               "qwd-qwdqwd.c":             false,
+               "qwd-qwdqwd.-12":           false,
+               "qwd-qwdqwd.1212":          false,
+               "qwd-qwdqwd.org":           true,
+               "qwd-qwdqwd.ac.kr":         true,
+               "qwd-qwdqwd.gov":           true,
+               "chicken.beer":             true,
+               "aa.xyz":                   true,
+               "google.asn.au":            true,
+               "google.com.au":            true,
+               "google.net.au":            true,
+               "google.priv.at":           true,
+               "google.ac.at":             true,
+               "google.gv.at":             true,
+               "google.avocat.fr":         true,
+               "google.geek.nz":           true,
+               "google.gen.nz":            true,
+               "google.kiwi.nz":           true,
+               "google.org.il":            true,
+               "google.net.il":            true,
+               "www.google.edu.au":        true,
+               "www.google.gov.au":        true,
+               "www.google.csiro.au":      true,
+               "www.google.act.au":        true,
+               "www.google.avocat.fr":     true,
+               "www.google.aeroport.fr":   true,
+               "www.google.co.nz":         true,
+               "www.google.geek.nz":       true,
+               "www.google.gen.nz":        true,
+               "www.google.kiwi.nz":       true,
+               "www.google.parliament.nz": true,
+               "www.google.muni.il":       true,
+               "www.google.idf.il":        true,
+       }
+
+       tests := []Expect{}
+
+       for domain, expected := range test_domains {
+               tests = append(tests, Expect{input: domain, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) domain", domain)})
+       }
+
+       for _, domain := range []revel.Domain{{}, revel.ValidDomain()} {
+               performTests(domain, tests, t)
+       }
+}
+
+func TestURL(t *testing.T) {
+
+       test_urls := map[string]bool{
+               "https://www.google.co.kr/url?sa=t&rct=j&q=&esrc=s&source=web":                                      true,
+               "http://stackoverflow.com/questions/27812164/can-i-import-3rd-party-package-into-golang-playground": true,
+               "https://tour.golang.org/welcome/4":                                                                 true,
+               "https://revel.github.io/":                                                                          true,
+               "https://github.com/revel/revel/commit/bd1d083ee4345e919b3bca1e4c42ca682525e395#diff-972a2b2141d27e9d7a8a4149a7e28eef": true,
+               "https://github.com/ndevilla/iniparser/pull/82#issuecomment-261817064":                                                 true,
+               "http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=golang":                                            true,
+               "http://www.baidu.com/link?url=DrWkM_beo2M5kB5sLYnItKSQ0Ib3oDhKcPprdtLzAWNfFt_VN5oyD3KwnAKT6Xsk":                       true,
+       }
+
+       tests := []Expect{}
+
+       for url, expected := range test_urls {
+               tests = append(tests, Expect{input: url, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) url", url)})
+       }
+
+       for _, url := range []revel.URL{{}, revel.ValidURL()} {
+               performTests(url, tests, t)
+       }
+
+}
+
+func TestPureTextNormal(t *testing.T) {
+
+       test_txts := map[string]bool{
+               `<script ?>qwdpijqwd</script>qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`:       false,
+               `a\r\nb<script ?>qwdpijqwd</script>qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false,
+               `Foo<script type="text/javascript">alert(1337)</script>Bar`:                                                 false,
+               `Foo<12>Bar`:              true,
+               `Foo<>Bar`:                true,
+               `Foo</br>Bar`:             false,
+               `Foo <!-- Bar --> Baz`:    false,
+               `I <3 Ponies!`:            true,
+               `I &#32; like Golang\t\n`: true,
+               `I &amp; like Golang\t\n`: false,
+               `<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" /> </layout> </appender> <root> <level value="DEBUG" /> <appender-ref ref="console" /> </root> </log4j:configuration>`: false,
+               `I like Golang\r\n`:       true,
+               `I like Golang\r\na`:      true,
+               "I &#32; like Golang\t\n": true,
+               "I &amp; like Golang\t\n": false,
+               `ハイレゾ対応ウォークマン®、ヘッドホン、スピーカー「Winter Gift Collection ~Presented by JUJU~」をソニーストアにて販売開始`:                                                                      true,
+               `VAIOパーソナルコンピューター type T TZシリーズ 無償点検・修理のお知らせとお詫び(2009年10月15日更新)`:                                                                                          true,
+               `把百度设为主页关于百度About  Baidu百度推广`:                                                                                                                             true,
+               `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About++Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`:     true,
+               `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About%20%20Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true,
+               `abcd/>qwdqwdoijhwer/>qwdojiqwdqwd</>qwdoijqwdoiqjd`:                                                                                                      true,
+               `abcd/>qwdqwdoijhwer/>qwdojiqwdqwd</a>qwdoijqwdoiqjd`:                                                                                                     false,
+       }
+
+       tests := []Expect{}
+
+       for txt, expected := range test_txts {
+               tests = append(tests, Expect{input: txt, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%#v) text", txt)})
+       }
+
+       // normal
+       for _, txt := range []revel.PureText{{revel.NORMAL}, revel.ValidPureText(revel.NORMAL)} {
+               performTests(txt, tests, t)
+       }
+}
+
+func TestPureTextStrict(t *testing.T) {
+
+       test_txts := map[string]bool{
+               `<script ?>qwdpijqwd</script>qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`:       false,
+               `a\r\nb<script ?>qwdpijqwd</script>qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false,
+               `Foo<script type="text/javascript">alert(1337)</script>Bar`:                                                 false,
+               `Foo<12>Bar`:              true,
+               `Foo<>Bar`:                true,
+               `Foo</br>Bar`:             false,
+               `Foo <!-- Bar --> Baz`:    false,
+               `I <3 Ponies!`:            true,
+               `I &#32; like Golang\t\n`: true,
+               `I &amp; like Golang\t\n`: false,
+               `<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/'> <ender name="console" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p 1}:%L - %m%n" /> </layout> </appender> <root> <level value="DEBUG" /> <appender-ref ref="console" /> </root> </log4j:configuration>`: false,
+               `I like Golang\r\n`:       true,
+               `I like Golang\r\na`:      true,
+               "I &#32; like Golang\t\n": true,
+               "I &amp; like Golang\t\n": false,
+               `ハイレゾ対応ウォークマン®、ヘッドホン、スピーカー「Winter Gift Collection ~Presented by JUJU~」をソニーストアにて販売開始`:                                                                      true,
+               `VAIOパーソナルコンピューター type T TZシリーズ 無償点検・修理のお知らせとお詫び(2009年10月15日更新)`:                                                                                          true,
+               `把百度设为主页关于百度About  Baidu百度推广`:                                                                                                                             true,
+               `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About++Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`:     true,
+               `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About%20%20Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true,
+               `abcd/>qwdqwdoijhwer/>qwdojiqwdqwd</>qwdoijqwdoiqjd`:                                                                                                      true,
+               `abcd/>qwdqwdoijhwer/>qwdojiqwdqwd</a>qwdoijqwdoiqjd`:                                                                                                     false,
+       }
+
+       tests := []Expect{}
+
+       for txt, expected := range test_txts {
+               tests = append(tests, Expect{input: txt, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%#v) text", txt)})
+       }
+
+       // strict
+       for _, txt := range []revel.PureText{{revel.STRICT}, revel.ValidPureText(revel.STRICT)} {
+               performTests(txt, tests, t)
+       }
+}
+
+func TestFilePathOnlyFilePath(t *testing.T) {
+
+       test_filepaths := map[string]bool{
+               "../../qwdqwdqwd/../qwdqwdqwd.txt": false,
+               `../../qwdqwdqwd/..
+                                       /qwdqwdqwd.txt`: false,
+               "\t../../qwdqwdqwd/../qwdqwdqwd.txt": false,
+               `\16../../qwdqwdqwd/../qwdqwdqwd.txt`: false,
+               `../../qwdqwdqwd/..\ 2/qwdqwdqwd.txt`: false,
+               "../../etc/passwd":                 false,
+               "a.txt;rm -rf /":                   false,
+               "sudo rm -rf ../":                  false,
+               "a-1-s-d-v-we-wd_+qwd-qwd-qwd.txt": false,
+               "a-qwdqwd_qwdqwdqwd-123.txt":       true,
+               "a.txt": true,
+               "a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt": true,
+       }
+
+       tests := []Expect{}
+
+       for filepath, expected := range test_filepaths {
+               tests = append(tests, Expect{input: filepath, expectedResult: expected, errorMessage: fmt.Sprintf("unsanitary (%#v) string", filepath)})
+       }
+
+       // filename without relative path
+       for _, filepath := range []revel.FilePath{{revel.ONLY_FILENAME}, revel.ValidFilePath(revel.ONLY_FILENAME)} {
+               performTests(filepath, tests, t)
+       }
+}
+
+func TestFilePathAllowRelativePath(t *testing.T) {
+
+       test_filepaths := map[string]bool{
+               "../../qwdqwdqwd/../qwdqwdqwd.txt": true,
+               `../../qwdqwdqwd/..
+                                       /qwdqwdqwd.txt`: false,
+               "\t../../qwdqwdqwd/../qwdqwdqwd.txt": false,
+               `\16../../qwdqwdqwd/../qwdqwdqwd.txt`: false,
+               `../../qwdqwdqwd/..\ 2/qwdqwdqwd.txt`: false,
+               "../../etc/passwd":                 true,
+               "a.txt;rm -rf /":                   false,
+               "sudo rm -rf ../":                  true,
+               "a-1-s-d-v-we-wd_+qwd-qwd-qwd.txt": false,
+               "a-qwdqwd_qwdqwdqwd-123.txt":       true,
+               "a.txt": true,
+               "a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt":                                       true,
+               "/asdasd/asdasdasd/qwdqwd_qwdqwd/12-12/a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt": true,
+       }
+
+       tests := []Expect{}
+
+       for filepath, expected := range test_filepaths {
+               tests = append(tests, Expect{input: filepath, expectedResult: expected, errorMessage: fmt.Sprintf("unsanitary (%#v) string", filepath)})
+       }
+
+       // filename with relative path
+       for _, filepath := range []revel.FilePath{{revel.ALLOW_RELATIVE_PATH}, revel.ValidFilePath(revel.ALLOW_RELATIVE_PATH)} {
+               performTests(filepath, tests, t)
+       }
+}
diff --git a/src/foundation/api/revel/version.go b/src/foundation/api/revel/version.go
new file mode 100644 (file)
index 0000000..6d666ca
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+const (
+       // Version current Revel version
+       Version = "0.21.0"
+
+       // BuildDate latest commit/release date
+       BuildDate = "2018-10-30"
+
+       // MinimumGoVersion minimum required Go version for Revel
+       MinimumGoVersion = ">= go1.8"
+)
diff --git a/src/foundation/api/revel/watcher.go b/src/foundation/api/revel/watcher.go
new file mode 100644 (file)
index 0000000..0dfc18c
--- /dev/null
@@ -0,0 +1,299 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "os"
+       "path/filepath"
+       "strings"
+       "sync"
+
+       "gopkg.in/fsnotify/fsnotify.v1"
+       "time"
+)
+
+// Listener is an interface for receivers of filesystem events.
+type Listener interface {
+       // Refresh is invoked by the watcher on relevant filesystem events.
+       // If the listener returns an error, it is served to the user on the current request.
+       Refresh() *Error
+}
+
+// DiscerningListener allows the receiver to selectively watch files.
+type DiscerningListener interface {
+       Listener
+       WatchDir(info os.FileInfo) bool
+       WatchFile(basename string) bool
+}
+
+// Watcher allows listeners to register to be notified of changes under a given
+// directory.
+type Watcher struct {
+       serial              bool                // true to process events in serial
+       watchers            []*fsnotify.Watcher // Parallel arrays of watcher/listener pairs.
+       listeners           []Listener          // List of listeners for watcher
+       forceRefresh        bool                // True to force the refresh
+       lastError           int                 // The last error found
+       notifyMutex         sync.Mutex          // The mutext to serialize watches
+       refreshTimer        *time.Timer         // The timer to countdown the next refresh
+       timerMutex          *sync.Mutex         // A mutex to prevent concurrent updates
+       refreshChannel      chan *Error         // The error channel to listen to when waiting for a refresh
+       refreshChannelCount int                 // The number of clients listening on the channel
+       refreshTimerMS      time.Duration       // The number of milliseconds between refreshing builds
+}
+
+func NewWatcher() *Watcher {
+       return &Watcher{
+               forceRefresh:        true,
+               lastError:           -1,
+               refreshTimerMS:      time.Duration(Config.IntDefault("watch.rebuild.delay", 10)),
+               timerMutex:          &sync.Mutex{},
+               refreshChannel:      make(chan *Error, 10),
+               refreshChannelCount: 0,
+       }
+}
+
+// Listen registers for events within the given root directories (recursively).
+func (w *Watcher) Listen(listener Listener, roots ...string) {
+       watcher, err := fsnotify.NewWatcher()
+       if err != nil {
+               utilLog.Fatal("Watcher: Failed to create watcher", "error", err)
+       }
+
+       // Replace the unbuffered Event channel with a buffered one.
+       // Otherwise multiple change events only come out one at a time, across
+       // multiple page views.  (There appears no way to "pump" the events out of
+       // the watcher)
+       // This causes a notification when you do a check in go, since you are modifying a buffer in use
+       watcher.Events = make(chan fsnotify.Event, 100)
+       watcher.Errors = make(chan error, 10)
+
+       // Walk through all files / directories under the root, adding each to watcher.
+       for _, p := range roots {
+               // is the directory / file a symlink?
+               f, err := os.Lstat(p)
+               if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
+                       var realPath string
+                       realPath, err = filepath.EvalSymlinks(p)
+                       if err != nil {
+                               panic(err)
+                       }
+                       p = realPath
+               }
+
+               fi, err := os.Stat(p)
+               if err != nil {
+                       utilLog.Error("Watcher: Failed to stat watched path, code will continue but auto updates will not work", "path", p, "error", err)
+                       continue
+               }
+
+               // If it is a file, watch that specific file.
+               if !fi.IsDir() {
+                       err = watcher.Add(p)
+                       if err != nil {
+                               utilLog.Error("Watcher: Failed to watch, code will continue but auto updates will not work", "path", p, "error", err)
+                       }
+                       continue
+               }
+
+               var watcherWalker func(path string, info os.FileInfo, err error) error
+
+               watcherWalker = func(path string, info os.FileInfo, err error) error {
+                       if err != nil {
+                               utilLog.Error("Watcher: Error walking path:", "error", err)
+                               return nil
+                       }
+
+                       if info.IsDir() {
+                               if dl, ok := listener.(DiscerningListener); ok {
+                                       if !dl.WatchDir(info) {
+                                               return filepath.SkipDir
+                                       }
+                               }
+
+                               err := watcher.Add(path)
+                               if err != nil {
+                                       utilLog.Error("Watcher: Failed to watch this path, code will continue but auto updates will not work", "path", path, "error", err)
+                               }
+                       }
+                       return nil
+               }
+
+               // Else, walk the directory tree.
+               err = Walk(p, watcherWalker)
+               if err != nil {
+                       utilLog.Error("Watcher: Failed to walk directory, code will continue but auto updates will not work", "path", p, "error", err)
+               }
+       }
+
+       if w.eagerRebuildEnabled() {
+               // Create goroutine to notify file changes in real time
+               go w.NotifyWhenUpdated(listener, watcher)
+       }
+
+       w.watchers = append(w.watchers, watcher)
+       w.listeners = append(w.listeners, listener)
+}
+
+// NotifyWhenUpdated notifies the watcher when a file event is received.
+func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
+
+       for {
+               select {
+               case ev := <-watcher.Events:
+                       if w.rebuildRequired(ev, listener) {
+                               // Serialize listener.Refresh() calls.
+                               if w.serial {
+                                       // Serialize listener.Refresh() calls.
+                                       w.notifyMutex.Lock()
+
+                                       if err := listener.Refresh(); err != nil {
+                                               utilLog.Error("Watcher: Listener refresh reported error:", "error", err)
+                                       }
+                                       w.notifyMutex.Unlock()
+                               } else {
+                                       // Run refresh in parallel
+                                       go func() {
+                                               w.notifyInProcess(listener)
+                                       }()
+                               }
+                       }
+               case <-watcher.Errors:
+                       continue
+               }
+       }
+}
+
+// Notify causes the watcher to forward any change events to listeners.
+// It returns the first (if any) error returned.
+func (w *Watcher) Notify() *Error {
+       // Serialize Notify() calls.
+       w.notifyMutex.Lock()
+       defer w.notifyMutex.Unlock()
+
+       for i, watcher := range w.watchers {
+               listener := w.listeners[i]
+
+               // Pull all pending events / errors from the watcher.
+               refresh := false
+               for {
+                       select {
+                       case ev := <-watcher.Events:
+                               if w.rebuildRequired(ev, listener) {
+                                       refresh = true
+                               }
+                               continue
+                       case <-watcher.Errors:
+                               continue
+                       default:
+                               // No events left to pull
+                       }
+                       break
+               }
+
+               if w.forceRefresh || refresh || w.lastError == i {
+                       var err *Error
+                       if w.serial {
+                               err = listener.Refresh()
+                       } else {
+                               err = w.notifyInProcess(listener)
+                       }
+
+                       if err != nil {
+                               w.lastError = i
+                               return err
+                       }
+               }
+       }
+
+       w.forceRefresh = false
+       w.lastError = -1
+       return nil
+}
+
+// Build a queue for refresh notifications
+// this will not return until one of the queue completes
+func (w *Watcher) notifyInProcess(listener Listener) (err *Error) {
+       shouldReturn := false
+       // This code block ensures that either a timer is created
+       // or that a process would be added the the h.refreshChannel
+       func() {
+               w.timerMutex.Lock()
+               defer w.timerMutex.Unlock()
+               // If we are in the process of a rebuild, forceRefresh will always be true
+               w.forceRefresh = true
+               if w.refreshTimer != nil {
+                       utilLog.Info("Found existing timer running, resetting")
+                       w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS)
+                       shouldReturn = true
+                       w.refreshChannelCount++
+               } else {
+                       w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS)
+               }
+       }()
+
+       // If another process is already waiting for the timer this one
+       // only needs to return the output from the channel
+       if shouldReturn {
+               return <-w.refreshChannel
+       }
+       utilLog.Info("Waiting for refresh timer to expire")
+       <-w.refreshTimer.C
+       w.timerMutex.Lock()
+
+       // Ensure the queue is properly dispatched even if a panic occurs
+       defer func() {
+               for x := 0; x < w.refreshChannelCount; x++ {
+                       w.refreshChannel <- err
+               }
+               w.refreshChannelCount = 0
+               w.refreshTimer = nil
+               w.timerMutex.Unlock()
+       }()
+
+       err = listener.Refresh()
+       if err != nil {
+               utilLog.Info("Watcher: Recording error last build, setting rebuild on", "error", err)
+       } else {
+               w.lastError = -1
+               w.forceRefresh = false
+       }
+       utilLog.Info("Rebuilt, result", "error", err)
+       return
+}
+
+// If watch.mode is set to eager, the application is rebuilt immediately
+// when a source file is changed.
+// This feature is available only in dev mode.
+func (w *Watcher) eagerRebuildEnabled() bool {
+       return Config.BoolDefault("mode.dev", true) &&
+               Config.BoolDefault("watch", true) &&
+               Config.StringDefault("watch.mode", "normal") == "eager"
+}
+
+func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool {
+       // Ignore changes to dotfiles.
+       if strings.HasPrefix(filepath.Base(ev.Name), ".") {
+               return false
+       }
+
+       if dl, ok := listener.(DiscerningListener); ok {
+               if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod {
+                       return false
+               }
+       }
+       return true
+}
+
+var WatchFilter = func(c *Controller, fc []Filter) {
+       if MainWatcher != nil {
+               err := MainWatcher.Notify()
+               if err != nil {
+                       c.Result = c.RenderError(err)
+                       return
+               }
+       }
+       fc[0](c, fc[1:])
+}
diff --git a/src/foundation/scripts/cni/cilium/cilium_install.sh b/src/foundation/scripts/cni/cilium/cilium_install.sh
new file mode 100755 (executable)
index 0000000..b030785
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -o xtrace
+set -e
+
+umount /sys/fs/bpf || true
+mount bpffs /sys/fs/bpf -t bpf
diff --git a/src/foundation/scripts/cni/cilium/quick-install.yaml b/src/foundation/scripts/cni/cilium/quick-install.yaml
new file mode 100644 (file)
index 0000000..1abeda3
--- /dev/null
@@ -0,0 +1,669 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length rule:comments rule:comments-indentation
+---
+# Source: cilium/charts/config/templates/configmap.yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cilium-config
+  namespace: kube-system
+data:
+
+  # Identity allocation mode selects how identities are shared between cilium
+  # nodes by setting how they are stored. The options are "crd" or "kvstore".
+  # - "crd" stores identities in kubernetes as CRDs (custom resource definition).
+  #   These can be queried with:
+  #     kubectl get ciliumid
+  # - "kvstore" stores identities in a kvstore, etcd or consul, that is
+  #   configured below. Cilium versions before 1.6 supported only the kvstore
+  #   backend. Upgrades from these older cilium versions should continue using
+  #   the kvstore by commenting out the identity-allocation-mode below, or
+  #   setting it to "kvstore".
+  identity-allocation-mode: crd
+
+  # If you want to run cilium in debug mode change this value to true
+  debug: "true"
+
+  # Enable IPv4 addressing. If enabled, all endpoints are allocated an IPv4
+  # address.
+  enable-ipv4: "true"
+
+  # Enable IPv6 addressing. If enabled, all endpoints are allocated an IPv6
+  # address.
+  enable-ipv6: "false"
+
+  # If you want cilium monitor to aggregate tracing for packets, set this level
+  # to "low", "medium", or "maximum". The higher the level, the less packets
+  # that will be seen in monitor output.
+  monitor-aggregation: medium
+
+  # ct-global-max-entries-* specifies the maximum number of connections
+  # supported across all endpoints, split by protocol: tcp or other. One pair
+  # of maps uses these values for IPv4 connections, and another pair of maps
+  # use these values for IPv6 connections.
+  #
+  # If these values are modified, then during the next Cilium startup the
+  # tracking of ongoing connections may be disrupted. This may lead to brief
+  # policy drops or a change in loadbalancing decisions for a connection.
+  #
+  # For users upgrading from Cilium 1.2 or earlier, to minimize disruption
+  # during the upgrade process, comment out these options.
+  bpf-ct-global-tcp-max: "524288"
+  bpf-ct-global-any-max: "262144"
+
+  # Pre-allocation of map entries allows per-packet latency to be reduced, at
+  # the expense of up-front memory allocation for the entries in the maps. The
+  # default value below will minimize memory usage in the default installation;
+  # users who are sensitive to latency may consider setting this to "true".
+  #
+  # This option was introduced in Cilium 1.4. Cilium 1.3 and earlier ignore
+  # this option and behave as though it is set to "true".
+  #
+  # If this value is modified, then during the next Cilium startup the restore
+  # of existing endpoints and tracking of ongoing connections may be disrupted.
+  # This may lead to policy drops or a change in loadbalancing decisions for a
+  # connection for some time. Endpoints may need to be recreated to restore
+  # connectivity.
+  #
+  # If this option is set to "false" during an upgrade from 1.3 or earlier to
+  # 1.4 or later, then it may cause one-time disruptions during the upgrade.
+  preallocate-bpf-maps: "false"
+
+  # Regular expression matching compatible Istio sidecar istio-proxy
+  # container image names
+  sidecar-istio-proxy-image: "cilium/istio_proxy"
+
+  # Encapsulation mode for communication between nodes
+  # Possible values:
+  #   - disabled
+  #   - vxlan (default)
+  #   - geneve
+  tunnel: vxlan
+
+  # Name of the cluster. Only relevant when building a mesh of clusters.
+  cluster-name: default
+
+  # DNS Polling periodically issues a DNS lookup for each `matchName` from
+  # cilium-agent. The result is used to regenerate endpoint policy.
+  # DNS lookups are repeated with an interval of 5 seconds, and are made for
+  # A(IPv4) and AAAA(IPv6) addresses. Should a lookup fail, the most recent IP
+  # data is used instead. An IP change will trigger a regeneration of the Cilium
+  # policy for each endpoint and increment the per cilium-agent policy
+  # repository revision.
+  #
+  # This option is disabled by default starting from version 1.4.x in favor
+  # of a more powerful DNS proxy-based implementation, see [0] for details.
+  # Enable this option if you want to use FQDN policies but do not want to use
+  # the DNS proxy.
+  #
+  # To ease upgrade, users may opt to set this option to "true".
+  # Otherwise please refer to the Upgrade Guide [1] which explains how to
+  # prepare policy rules for upgrade.
+  #
+  # [0] http://docs.cilium.io/en/stable/policy/language/#dns-based
+  # [1] http://docs.cilium.io/en/stable/install/upgrade/#changes-that-may-require-action
+  tofqdns-enable-poller: "false"
+
+  # wait-bpf-mount makes init container wait until bpf filesystem is mounted
+  wait-bpf-mount: "false"
+
+  # Enable fetching of container-runtime specific metadata
+  #
+  # By default, the Kubernetes pod and namespace labels are retrieved and
+  # associated with endpoints for identification purposes. By integrating
+  # with the container runtime, container runtime specific labels can be
+  # retrieved, such labels will be prefixed with container:
+  #
+  # CAUTION: The container runtime labels can include information such as pod
+  # annotations which may result in each pod being associated a unique set of
+  # labels which can result in excessive security identities being allocated.
+  # Please review the labels filter when enabling container runtime labels.
+  #
+  # Supported values:
+  # - containerd
+  # - crio
+  # - docker
+  # - none
+  # - auto (automatically detect the container runtime)
+  #
+  container-runtime: none
+
+  masquerade: "true"
+
+  install-iptables-rules: "true"
+  auto-direct-node-routes: "false"
+  enable-node-port: "false"
+
+---
+# Source: cilium/charts/agent/templates/serviceaccount.yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: cilium
+  namespace: kube-system
+
+---
+# Source: cilium/charts/operator/templates/serviceaccount.yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: cilium-operator
+  namespace: kube-system
+
+---
+# Source: cilium/charts/agent/templates/clusterrole.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: cilium
+rules:
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - networkpolicies
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - namespaces
+  - services
+  - nodes
+  - endpoints
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - nodes
+  verbs:
+  - get
+  - list
+  - watch
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - nodes
+  - nodes/status
+  verbs:
+  - patch
+- apiGroups:
+  - extensions
+  resources:
+  - ingresses
+  verbs:
+  - create
+  - get
+  - list
+  - watch
+- apiGroups:
+  - apiextensions.k8s.io
+  resources:
+  - customresourcedefinitions
+  verbs:
+  - create
+  - get
+  - list
+  - watch
+  - update
+- apiGroups:
+  - cilium.io
+  resources:
+  - ciliumnetworkpolicies
+  - ciliumnetworkpolicies/status
+  - ciliumclusterwidenetworkpolicies
+  - ciliumclusterwidenetworkpolicies/status
+  - ciliumendpoints
+  - ciliumendpoints/status
+  - ciliumnodes
+  - ciliumnodes/status
+  - ciliumidentities
+  - ciliumidentities/status
+  verbs:
+  - '*'
+
+---
+# Source: cilium/charts/operator/templates/clusterrole.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: cilium-operator
+rules:
+- apiGroups:
+  - ""
+  resources:
+  # to automatically delete [core|kube]dns pods so that are starting to being
+  # managed by Cilium
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
+  - delete
+- apiGroups:
+  - ""
+  resources:
+  # to automatically read from k8s and import the node's pod CIDR to cilium's
+  # etcd so all nodes know how to reach another pod running in in a different
+  # node.
+  - nodes
+  # to perform the translation of a CNP that contains `ToGroup` to its endpoints
+  - services
+  - endpoints
+  # to check apiserver connectivity
+  - namespaces
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - cilium.io
+  resources:
+  - ciliumnetworkpolicies
+  - ciliumnetworkpolicies/status
+  - ciliumclusterwidenetworkpolicies
+  - ciliumclusterwidenetworkpolicies/status
+  - ciliumendpoints
+  - ciliumendpoints/status
+  - ciliumnodes
+  - ciliumnodes/status
+  - ciliumidentities
+  - ciliumidentities/status
+  verbs:
+  - '*'
+
+---
+# Source: cilium/charts/agent/templates/clusterrolebinding.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: cilium
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cilium
+subjects:
+- kind: ServiceAccount
+  name: cilium
+  namespace: kube-system
+
+---
+# Source: cilium/charts/operator/templates/clusterrolebinding.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: cilium-operator
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cilium-operator
+subjects:
+- kind: ServiceAccount
+  name: cilium-operator
+  namespace: kube-system
+
+---
+# Source: cilium/charts/agent/templates/daemonset.yaml
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  labels:
+    k8s-app: cilium
+    kubernetes.io/cluster-service: "true"
+  name: cilium
+  namespace: kube-system
+spec:
+  selector:
+    matchLabels:
+      k8s-app: cilium
+      kubernetes.io/cluster-service: "true"
+  template:
+    metadata:
+      annotations:
+        # This annotation plus the CriticalAddonsOnly toleration makes
+        # cilium to be a critical pod in the cluster, which ensures cilium
+        # gets priority scheduling.
+        # https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/
+        scheduler.alpha.kubernetes.io/critical-pod: ""
+        scheduler.alpha.kubernetes.io/tolerations: '[{"key":"dedicated","operator":"Equal","value":"master","effect":"NoSchedule"}]'
+      labels:
+        k8s-app: cilium
+        kubernetes.io/cluster-service: "true"
+    spec:
+      containers:
+      - args:
+        - --config-dir=/tmp/cilium/config-map
+        command:
+        - cilium-agent
+        env:
+        - name: K8S_NODE_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: spec.nodeName
+        - name: CILIUM_K8S_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: CILIUM_FLANNEL_MASTER_DEVICE
+          valueFrom:
+            configMapKeyRef:
+              key: flannel-master-device
+              name: cilium-config
+              optional: true
+        - name: CILIUM_FLANNEL_UNINSTALL_ON_EXIT
+          valueFrom:
+            configMapKeyRef:
+              key: flannel-uninstall-on-exit
+              name: cilium-config
+              optional: true
+        - name: CILIUM_CLUSTERMESH_CONFIG
+          value: /var/lib/cilium/clustermesh/
+        - name: CILIUM_CNI_CHAINING_MODE
+          valueFrom:
+            configMapKeyRef:
+              key: cni-chaining-mode
+              name: cilium-config
+              optional: true
+        - name: CILIUM_CUSTOM_CNI_CONF
+          valueFrom:
+            configMapKeyRef:
+              key: custom-cni-conf
+              name: cilium-config
+              optional: true
+        image: "iecedge/cilium:latest"
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          postStart:
+            exec:
+              command:
+              - /cni-install.sh
+          preStop:
+            exec:
+              command:
+              - /cni-uninstall.sh
+        livenessProbe:
+          exec:
+            command:
+            - cilium
+            - status
+            - --brief
+          failureThreshold: 10
+          # The initial delay for the liveness probe is intentionally large to
+          # avoid an endless kill & restart cycle if in the event that the initial
+          # bootstrapping takes longer than expected.
+          initialDelaySeconds: 120
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: cilium-agent
+        readinessProbe:
+          exec:
+            command:
+            - cilium
+            - status
+            - --brief
+          failureThreshold: 3
+          initialDelaySeconds: 5
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        securityContext:
+          capabilities:
+            add:
+            - NET_ADMIN
+            - SYS_MODULE
+          privileged: true
+        volumeMounts:
+        - mountPath: /sys/fs/bpf
+          name: bpf-maps
+        - mountPath: /var/run/cilium
+          name: cilium-run
+        - mountPath: /host/opt/cni/bin
+          name: cni-path
+        - mountPath: /host/etc/cni/net.d
+          name: etc-cni-netd
+        - mountPath: /var/lib/cilium/clustermesh
+          name: clustermesh-secrets
+          readOnly: true
+        - mountPath: /tmp/cilium/config-map
+          name: cilium-config-path
+          readOnly: true
+          # Needed to be able to load kernel modules
+        - mountPath: /lib/modules
+          name: lib-modules
+          readOnly: true
+        - mountPath: /run/xtables.lock
+          name: xtables-lock
+      hostNetwork: true
+      initContainers:
+      - command:
+        - /init-container.sh
+        env:
+        - name: CILIUM_ALL_STATE
+          valueFrom:
+            configMapKeyRef:
+              key: clean-cilium-state
+              name: cilium-config
+              optional: true
+        - name: CILIUM_BPF_STATE
+          valueFrom:
+            configMapKeyRef:
+              key: clean-cilium-bpf-state
+              name: cilium-config
+              optional: true
+        - name: CILIUM_WAIT_BPF_MOUNT
+          valueFrom:
+            configMapKeyRef:
+              key: wait-bpf-mount
+              name: cilium-config
+              optional: true
+        image: "iecedge/cilium:latest"
+        imagePullPolicy: IfNotPresent
+        name: clean-cilium-state
+        securityContext:
+          capabilities:
+            add:
+            - NET_ADMIN
+          privileged: true
+        volumeMounts:
+        - mountPath: /sys/fs/bpf
+          name: bpf-maps
+        - mountPath: /var/run/cilium
+          name: cilium-run
+      restartPolicy: Always
+      serviceAccount: cilium
+      serviceAccountName: cilium
+      terminationGracePeriodSeconds: 1
+      tolerations:
+      - operator: Exists
+      volumes:
+        # To keep state between restarts / upgrades
+      - hostPath:
+          path: /var/run/cilium
+          type: DirectoryOrCreate
+        name: cilium-run
+        # To keep state between restarts / upgrades for bpf maps
+      - hostPath:
+          path: /sys/fs/bpf
+          type: DirectoryOrCreate
+        name: bpf-maps
+      # To install cilium cni plugin in the host
+      - hostPath:
+          path: /opt/cni/bin
+          type: DirectoryOrCreate
+        name: cni-path
+        # To install cilium cni configuration in the host
+      - hostPath:
+          path: /etc/cni/net.d
+          type: DirectoryOrCreate
+        name: etc-cni-netd
+        # To be able to load kernel modules
+      - hostPath:
+          path: /lib/modules
+        name: lib-modules
+        # To access iptables concurrently with other processes (e.g. kube-proxy)
+      - hostPath:
+          path: /run/xtables.lock
+          type: FileOrCreate
+        name: xtables-lock
+        # To read the clustermesh configuration
+      - name: clustermesh-secrets
+        secret:
+          defaultMode: 420
+          optional: true
+          secretName: cilium-clustermesh
+        # To read the configuration from the config map
+      - configMap:
+          name: cilium-config
+        name: cilium-config-path
+  updateStrategy:
+    rollingUpdate:
+      maxUnavailable: 2
+    type: RollingUpdate
+
+---
+# Source: cilium/charts/operator/templates/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    io.cilium/app: operator
+    name: cilium-operator
+  name: cilium-operator
+  namespace: kube-system
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      io.cilium/app: operator
+      name: cilium-operator
+  strategy:
+    rollingUpdate:
+      maxSurge: 1
+      maxUnavailable: 1
+    type: RollingUpdate
+  template:
+    metadata:
+      annotations:
+      labels:
+        io.cilium/app: operator
+        name: cilium-operator
+    spec:
+      containers:
+      - args:
+        - --debug=$(CILIUM_DEBUG)
+        - --identity-allocation-mode=$(CILIUM_IDENTITY_ALLOCATION_MODE)
+        command:
+        - cilium-operator
+        env:
+        - name: CILIUM_K8S_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: K8S_NODE_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: spec.nodeName
+        - name: CILIUM_DEBUG
+          valueFrom:
+            configMapKeyRef:
+              key: debug
+              name: cilium-config
+              optional: true
+        - name: CILIUM_CLUSTER_NAME
+          valueFrom:
+            configMapKeyRef:
+              key: cluster-name
+              name: cilium-config
+              optional: true
+        - name: CILIUM_CLUSTER_ID
+          valueFrom:
+            configMapKeyRef:
+              key: cluster-id
+              name: cilium-config
+              optional: true
+        - name: CILIUM_IPAM
+          valueFrom:
+            configMapKeyRef:
+              key: ipam
+              name: cilium-config
+              optional: true
+        - name: CILIUM_DISABLE_ENDPOINT_CRD
+          valueFrom:
+            configMapKeyRef:
+              key: disable-endpoint-crd
+              name: cilium-config
+              optional: true
+        - name: CILIUM_KVSTORE
+          valueFrom:
+            configMapKeyRef:
+              key: kvstore
+              name: cilium-config
+              optional: true
+        - name: CILIUM_KVSTORE_OPT
+          valueFrom:
+            configMapKeyRef:
+              key: kvstore-opt
+              name: cilium-config
+              optional: true
+        - name: AWS_ACCESS_KEY_ID
+          valueFrom:
+            secretKeyRef:
+              key: AWS_ACCESS_KEY_ID
+              name: cilium-aws
+              optional: true
+        - name: AWS_SECRET_ACCESS_KEY
+          valueFrom:
+            secretKeyRef:
+              key: AWS_SECRET_ACCESS_KEY
+              name: cilium-aws
+              optional: true
+        - name: AWS_DEFAULT_REGION
+          valueFrom:
+            secretKeyRef:
+              key: AWS_DEFAULT_REGION
+              name: cilium-aws
+              optional: true
+        - name: CILIUM_IDENTITY_ALLOCATION_MODE
+          valueFrom:
+            configMapKeyRef:
+              key: identity-allocation-mode
+              name: cilium-config
+              optional: true
+        image: "iecedge/operator:latest"
+        imagePullPolicy: IfNotPresent
+        name: cilium-operator
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 9234
+            scheme: HTTP
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          timeoutSeconds: 3
+
+      hostNetwork: true
+      restartPolicy: Always
+      serviceAccount: cilium-operator
+      serviceAccountName: cilium-operator
+
+---
+# Source: cilium/charts/agent/templates/servicemonitor.yaml
+
+---
+# Source: cilium/charts/agent/templates/svc.yaml
+
+---
+# Source: cilium/charts/operator/templates/servicemonitor.yaml
+
+---
+# Source: cilium/charts/operator/templates/svc.yaml
diff --git a/src/foundation/scripts/cni/multus/README.md b/src/foundation/scripts/cni/multus/README.md
new file mode 100644 (file)
index 0000000..214b4e8
--- /dev/null
@@ -0,0 +1,81 @@
+# Multus/SRIOV CNI&Device Plugin Support for IEC
+
+
+## Introduction
+This commit provides Kubernetes networking support for Multus with SRIOV CNI support both on arm64 and amd64. A special configuration file for Broadcom smartNIC Stingray PS225 is provided as an example. So it can not only be used as a sample container networking support for Stingray PS225 with its sriov interfaces, but also a generic SRIOV support for various ethernet NICs.
+Here we would like to provide 2 types legacy CNIs besides the SRIOV-CNI by Multus, which are [Flannel](https://coreos.com/flannel/docs/latest/kubernetes.html), and [Calico](https://docs.projectcalico.org/v3.11/introduction/).
+They would be provided as the default CNIs for any pods without explicit annotations.
+
+The work here is based on the following open source projects:
+1. [Multus](https://github.com/intel/multus-cni)
+1. [SRIOV Device Plugin](https://github.com/intel/sriov-network-device-plugin)
+1. [SRIOV CNI](https://github.com/intel/sriov-cni)
+
+For Broadcom Stingray PS225, please refer its [documents](https://github.com/CCX-Stingray/Documentation).
+
+
+## Initial setup
+
+The SRIOV interfaces should be created before using the SRIOV CNI, for example, if the PF name of one of the Stingray PS225 ethernet NICs in your system is enp8s0f0np0, then you can lookup the maximum supported number of VFs under the PF and create the VFs with the command:
+
+```
+#ip link set enp8s0f0np0 up
+#cat /sys/class/net/enp8s0f0np0/device/sriov_totalvfs
+16
+#echo 16 > /sys/class/net/enp8s0f0np0/device/sriov_numvfs
+```
+For SRIOV CNI with DPDK type drivers, such as vfio-pci, uio_pci_generic, please bind the driver by dpdk-devbind besides creating the VFs, for example:
+```dpdk-devbind.py -b vfio-pci enp12s2```
+The `enp12s2` is the name of one of VFs of a ethernet NIC.
+
+For more information, please refer the above links in the [Introduction](#Introduction).
+
+##Installation
+
+To install the Multus-SRIOV-Calico or Multus-SRIOV-Flannel, the CNI_TYPE field should be set to 'multus-sriov-calico'
+or 'multus-sriov-flannel' correspondingly in the IEC's installation configuration file named as 'config', then do the
+CNI installation by setup-cni.sh.
+
+For SRIOV CNI with Flannel by Multus CNI, there are 4 yaml files give:
+1. configMap.yaml:
+The resource list configuration file for SRIOV device plugin
+1. multus-sriov-flannel-daemonsets.yaml
+The Multus, SRIOV device plugin&CNI configuration file
+1. flannel-daemonset.yml
+The Flannel CNI installation file
+1. sriov-crd.yaml
+The SRIOV CNI configuration file for the attached SRIOV interface resource.
+
+For SRIOV CNI with Calico by Multus CNI, there are 4 yaml files give:
+1. configMap.yaml:
+The resource list configuration file for SRIOV device plugin
+1. multus-sriov-calico-daemonsets.yaml
+The Multus, SRIOV device plugin&CNI configuration file
+1. calico-daemonset.yaml
+The Flannel CNI installation file
+1. sriov-crd.yaml
+The SRIOV CNI configuration file for the attached SRIOV interface resource.
+
+Usually users should modify the `configMap.yaml` and `sriov-crd.yaml` with their own corresponding networking configuration before doing the installation.
+
+A quick installation script is given as `install.sh`, and the uninstallation could be done by call the `uninstall.sh`.Before you call the install.sh manually to do the install, you should set your desired POD_NETWORK or other parameters in the installation yaml files as we do in the setup-cni.sh.
+
+For Kubernets version >=1.16, there are some changes for Kubernetes API. There is a sample installation script for multus-sriov-calico named as install-k8s-v1.16.sh, which could be used as a sample when your K8s version >=1.16.
+
+**The `install.sh` should be called after the Kubernetes cluster had been installed but before installing the CNIs.**
+
+2 sample user pods yaml files using the SRIOV CNI are given also, please see `pod1.yaml` and `pod2.yaml` in the directory, and 2 iperfv2 based yaml files are also provided to facilitate the performance test. We choose IPerfv2 instead of the IPerfv3 because it seems can support multi-threaded sending/receiving. The 4 yaml files here are provided just for arm64 platform and they could easily be adapted to amd64 platform with corresponding images with a docker building based on the provided Dockerfile.iperf2 file.
+
+
+## Notes
+Current installation files are suitable for K8s version less than 1.6, for that equal to or greater than 1.6, some modifications to the installation files should be done, which would adapt to the K8s api changes for newer versions.
+We would try to give a sample installation file for k8s 1.6 or above in the future, but it had not tested now.
+
+The command `kubectl get node $(hostname) -o json | jq '.status.allocatable'` can be used to check whether the allocated resources are available to use or not.
+
+## Other References
+[Advanced Networking Features in
+Kubernetes and Container Bare Metal](https://builders.intel.com/docs/networkbuilders/adv-network-features-in-kubernetes-app-note.pdf)
+
+[Kubernetes: Multus + SRIOV quickstart](https://zshisite.wordpress.com/2018/11/15/kubernetes-multus-sriov-quickstart/)
+
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset-k8s-v1.16.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset-k8s-v1.16.yaml
new file mode 100644 (file)
index 0000000..1cbf14c
--- /dev/null
@@ -0,0 +1,641 @@
+# yamllint disable
+# This is a modified Calico daemonset.
+# it is based on: https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: calico-config
+  namespace: kube-system
+data:
+  typha_service_name: "none"
+  calico_backend: "bird"
+  veth_mtu: "1440"
+  cni_network_config: |-
+    {
+      "name": "k8s-pod-network",
+      "cniVersion": "0.3.0",
+      "plugins": [
+        {
+          "type": "calico",
+          "log_level": "info",
+          "datastore_type": "kubernetes",
+          "nodename": "__KUBERNETES_NODE_NAME__",
+          "mtu": __CNI_MTU__,
+          "ipam": {
+            "type": "calico-ipam"
+          },
+          "policy": {
+              "type": "k8s"
+          },
+          "kubernetes": {
+              "kubeconfig": "__KUBECONFIG_FILEPATH__"
+          }
+        },
+        {
+          "type": "portmap",
+          "snat": true,
+          "capabilities": {"portMappings": true}
+        }
+      ]
+    }
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+   name: felixconfigurations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: FelixConfiguration
+    plural: felixconfigurations
+    singular: felixconfiguration
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamblocks.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMBlock
+    plural: ipamblocks
+    singular: ipamblock
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: blockaffinities.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BlockAffinity
+    plural: blockaffinities
+    singular: blockaffinity
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamhandles.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMHandle
+    plural: ipamhandles
+    singular: ipamhandle
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamconfigs.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMConfig
+    plural: ipamconfigs
+    singular: ipamconfig
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: bgppeers.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BGPPeer
+    plural: bgppeers
+    singular: bgppeer
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: bgpconfigurations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BGPConfiguration
+    plural: bgpconfigurations
+    singular: bgpconfiguration
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: ippools.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPPool
+    plural: ippools
+    singular: ippool
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: hostendpoints.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: HostEndpoint
+    plural: hostendpoints
+    singular: hostendpoint
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: clusterinformations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: ClusterInformation
+    plural: clusterinformations
+    singular: clusterinformation
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: globalnetworkpolicies.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: GlobalNetworkPolicy
+    plural: globalnetworkpolicies
+    singular: globalnetworkpolicy
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: globalnetworksets.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: GlobalNetworkSet
+    plural: globalnetworksets
+    singular: globalnetworkset
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: networkpolicies.crd.projectcalico.org
+spec:
+  scope: Namespaced
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: NetworkPolicy
+    plural: networkpolicies
+    singular: networkpolicy
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-kube-controllers
+rules:
+  - apiGroups: [""]
+    resources:
+      - nodes
+    verbs:
+      - watch
+      - list
+      - get
+  - apiGroups: [""]
+    resources:
+      - pods
+    verbs:
+      - get
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ippools
+    verbs:
+      - list
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+      - ipamblocks
+      - ipamhandles
+    verbs:
+      - get
+      - list
+      - create
+      - update
+      - delete
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - clusterinformations
+    verbs:
+      - get
+      - create
+      - update
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-kube-controllers
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: calico-kube-controllers
+subjects:
+- kind: ServiceAccount
+  name: calico-kube-controllers
+  namespace: kube-system
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-node
+rules:
+  - apiGroups: [""]
+    resources:
+      - pods
+      - nodes
+      - namespaces
+    verbs:
+      - get
+  - apiGroups: [""]
+    resources:
+      - endpoints
+      - services
+    verbs:
+      - watch
+      - list
+      - get
+  - apiGroups: [""]
+    resources:
+      - nodes/status
+    verbs:
+      - patch
+      - update
+  - apiGroups: ["networking.k8s.io"]
+    resources:
+      - networkpolicies
+    verbs:
+      - watch
+      - list
+  - apiGroups: [""]
+    resources:
+      - pods
+      - namespaces
+      - serviceaccounts
+    verbs:
+      - list
+      - watch
+  - apiGroups: [""]
+    resources:
+      - pods/status
+    verbs:
+      - patch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - globalfelixconfigs
+      - felixconfigurations
+      - bgppeers
+      - globalbgpconfigs
+      - bgpconfigurations
+      - ippools
+      - ipamblocks
+      - globalnetworkpolicies
+      - globalnetworksets
+      - networkpolicies
+      - clusterinformations
+      - hostendpoints
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ippools
+      - felixconfigurations
+      - clusterinformations
+    verbs:
+      - create
+      - update
+  - apiGroups: [""]
+    resources:
+      - nodes
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - bgpconfigurations
+      - bgppeers
+    verbs:
+      - create
+      - update
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+      - ipamblocks
+      - ipamhandles
+    verbs:
+      - get
+      - list
+      - create
+      - update
+      - delete
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ipamconfigs
+    verbs:
+      - get
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+    verbs:
+      - watch
+  - apiGroups: ["apps"]
+    resources:
+      - daemonsets
+    verbs:
+      - get
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRoleBinding
+metadata:
+  name: calico-node
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: calico-node
+subjects:
+- kind: ServiceAccount
+  name: calico-node
+  namespace: kube-system
+---
+kind: DaemonSet
+apiVersion: apps/v1
+metadata:
+  name: calico-node
+  namespace: kube-system
+  labels:
+    k8s-app: calico-node
+spec:
+  selector:
+    matchLabels:
+      k8s-app: calico-node
+  updateStrategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxUnavailable: 1
+  template:
+    metadata:
+      labels:
+        k8s-app: calico-node
+      annotations:
+        scheduler.alpha.kubernetes.io/critical-pod: ''
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      hostNetwork: true
+      tolerations:
+        - effect: NoSchedule
+          operator: Exists
+        - key: CriticalAddonsOnly
+          operator: Exists
+        - effect: NoExecute
+          operator: Exists
+      serviceAccountName: calico-node
+      terminationGracePeriodSeconds: 0
+      initContainers:
+        - name: upgrade-ipam
+          image: calico/cni:v3.6.1
+          command: ["/opt/cni/bin/calico-ipam", "-upgrade"]
+          env:
+            - name: KUBERNETES_NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: CALICO_NETWORKING_BACKEND
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: calico_backend
+          volumeMounts:
+            - mountPath: /var/lib/cni/networks
+              name: host-local-net-dir
+            - mountPath: /host/opt/cni/bin
+              name: cni-bin-dir
+        - name: install-cni
+          image: calico/cni:v3.6.1
+          command: ["/install-cni.sh"]
+          env:
+            - name: CNI_CONF_NAME
+              value: "10-calico.conflist"
+            - name: CNI_NETWORK_CONFIG
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: cni_network_config
+            - name: KUBERNETES_NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: CNI_MTU
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: veth_mtu
+            - name: SLEEP
+              value: "false"
+          volumeMounts:
+            - mountPath: /host/opt/cni/bin
+              name: cni-bin-dir
+            - mountPath: /host/etc/cni/net.d
+              name: cni-net-dir
+      containers:
+        - name: calico-node
+          image: calico/node:v3.6.1
+          env:
+            # Use Kubernetes API as the backing datastore.
+            - name: DATASTORE_TYPE
+              value: "kubernetes"
+            # Wait for the datastore.
+            - name: WAIT_FOR_DATASTORE
+              value: "true"
+            # Set based on the k8s node name.
+            - name: NODENAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            # Choose the backend to use.
+            - name: CALICO_NETWORKING_BACKEND
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: calico_backend
+            # Cluster type to identify the deployment type
+            - name: CLUSTER_TYPE
+              value: "k8s,bgp"
+            # Auto-detect the BGP IP address.
+            - name: IP
+              value: "autodetect"
+            - name: IP_AUTODETECTION_METHOD
+              value: "can-reach=www.google.com"
+            # Enable IPIP
+            - name: CALICO_IPV4POOL_IPIP
+              value: "Always"
+            # Set MTU for tunnel device used if ipip is enabled
+            - name: FELIX_IPINIPMTU
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: veth_mtu
+            # The default IPv4 pool to create on startup if none exists. Pod IPs will be
+            # chosen from this range. Changing this value after installation will have
+            # no effect. This should fall within `--cluster-cidr`.
+            - name: CALICO_IPV4POOL_CIDR
+              value: "10.244.0.0/16"
+            # Disable file logging so `kubectl logs` works.
+            - name: CALICO_DISABLE_FILE_LOGGING
+              value: "true"
+            # Set Felix endpoint to host default action to ACCEPT.
+            - name: FELIX_DEFAULTENDPOINTTOHOSTACTION
+              value: "ACCEPT"
+            # Disable IPv6 on Kubernetes.
+            - name: FELIX_IPV6SUPPORT
+              value: "false"
+            # Set Felix logging to "info"
+            - name: FELIX_LOGSEVERITYSCREEN
+              value: "info"
+            - name: FELIX_HEALTHENABLED
+              value: "true"
+          securityContext:
+            privileged: true
+          resources:
+            requests:
+              cpu: 250m
+          livenessProbe:
+            httpGet:
+              path: /liveness
+              port: 9099
+              host: localhost
+            periodSeconds: 10
+            initialDelaySeconds: 10
+            failureThreshold: 6
+          readinessProbe:
+            exec:
+              command:
+              - /bin/calico-node
+              - -bird-ready
+              - -felix-ready
+            periodSeconds: 10
+          volumeMounts:
+            - mountPath: /lib/modules
+              name: lib-modules
+              readOnly: true
+            - mountPath: /run/xtables.lock
+              name: xtables-lock
+              readOnly: false
+            - mountPath: /var/run/calico
+              name: var-run-calico
+              readOnly: false
+            - mountPath: /var/lib/calico
+              name: var-lib-calico
+              readOnly: false
+      volumes:
+        - name: lib-modules
+          hostPath:
+            path: /lib/modules
+        - name: var-run-calico
+          hostPath:
+            path: /var/run/calico
+        - name: var-lib-calico
+          hostPath:
+            path: /var/lib/calico
+        - name: xtables-lock
+          hostPath:
+            path: /run/xtables.lock
+            type: FileOrCreate
+        - name: cni-bin-dir
+          hostPath:
+            path: /opt/cni/bin
+        - name: cni-net-dir
+          hostPath:
+            # NOTE: moved to tmp so we can see what it attempts to write
+            path: /etc/cni/multus/calico/net.d
+        - name: host-local-net-dir
+          hostPath:
+            path: /var/lib/cni/networks
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: calico-node
+  namespace: kube-system
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: calico-kube-controllers
+  namespace: kube-system
+  labels:
+    k8s-app: calico-kube-controllers
+  annotations:
+    scheduler.alpha.kubernetes.io/critical-pod: ''
+spec:
+  selector:
+    matchLabels:
+      k8s-app: calico-kube-controllers
+  replicas: 1
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      name: calico-kube-controllers
+      namespace: kube-system
+      labels:
+        k8s-app: calico-kube-controllers
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      tolerations:
+        - key: CriticalAddonsOnly
+          operator: Exists
+        - key: node-role.kubernetes.io/master
+          effect: NoSchedule
+      serviceAccountName: calico-kube-controllers
+      containers:
+        - name: calico-kube-controllers
+          image: calico/kube-controllers:v3.6.1
+          env:
+            - name: ENABLED_CONTROLLERS
+              value: node
+            - name: DATASTORE_TYPE
+              value: kubernetes
+          readinessProbe:
+            exec:
+              command:
+              - /usr/bin/check-status
+              - -r
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: calico-kube-controllers
+  namespace: kube-system
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/calico-daemonset.yaml
new file mode 100644 (file)
index 0000000..dedb813
--- /dev/null
@@ -0,0 +1,638 @@
+# yamllint disable
+# This is a modified Calico daemonset.
+# it is based on: https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: calico-config
+  namespace: kube-system
+data:
+  typha_service_name: "none"
+  calico_backend: "bird"
+  veth_mtu: "1440"
+  cni_network_config: |-
+    {
+      "name": "k8s-pod-network",
+      "cniVersion": "0.3.0",
+      "plugins": [
+        {
+          "type": "calico",
+          "log_level": "info",
+          "datastore_type": "kubernetes",
+          "nodename": "__KUBERNETES_NODE_NAME__",
+          "mtu": __CNI_MTU__,
+          "ipam": {
+            "type": "calico-ipam"
+          },
+          "policy": {
+              "type": "k8s"
+          },
+          "kubernetes": {
+              "kubeconfig": "__KUBECONFIG_FILEPATH__"
+          }
+        },
+        {
+          "type": "portmap",
+          "snat": true,
+          "capabilities": {"portMappings": true}
+        }
+      ]
+    }
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+   name: felixconfigurations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: FelixConfiguration
+    plural: felixconfigurations
+    singular: felixconfiguration
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamblocks.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMBlock
+    plural: ipamblocks
+    singular: ipamblock
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: blockaffinities.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BlockAffinity
+    plural: blockaffinities
+    singular: blockaffinity
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamhandles.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMHandle
+    plural: ipamhandles
+    singular: ipamhandle
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: ipamconfigs.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPAMConfig
+    plural: ipamconfigs
+    singular: ipamconfig
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: bgppeers.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BGPPeer
+    plural: bgppeers
+    singular: bgppeer
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: bgpconfigurations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: BGPConfiguration
+    plural: bgpconfigurations
+    singular: bgpconfiguration
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: ippools.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: IPPool
+    plural: ippools
+    singular: ippool
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: hostendpoints.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: HostEndpoint
+    plural: hostendpoints
+    singular: hostendpoint
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: clusterinformations.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: ClusterInformation
+    plural: clusterinformations
+    singular: clusterinformation
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: globalnetworkpolicies.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: GlobalNetworkPolicy
+    plural: globalnetworkpolicies
+    singular: globalnetworkpolicy
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: globalnetworksets.crd.projectcalico.org
+spec:
+  scope: Cluster
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: GlobalNetworkSet
+    plural: globalnetworksets
+    singular: globalnetworkset
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: networkpolicies.crd.projectcalico.org
+spec:
+  scope: Namespaced
+  group: crd.projectcalico.org
+  version: v1
+  names:
+    kind: NetworkPolicy
+    plural: networkpolicies
+    singular: networkpolicy
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-kube-controllers
+rules:
+  - apiGroups: [""]
+    resources:
+      - nodes
+    verbs:
+      - watch
+      - list
+      - get
+  - apiGroups: [""]
+    resources:
+      - pods
+    verbs:
+      - get
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ippools
+    verbs:
+      - list
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+      - ipamblocks
+      - ipamhandles
+    verbs:
+      - get
+      - list
+      - create
+      - update
+      - delete
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - clusterinformations
+    verbs:
+      - get
+      - create
+      - update
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-kube-controllers
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: calico-kube-controllers
+subjects:
+- kind: ServiceAccount
+  name: calico-kube-controllers
+  namespace: kube-system
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: calico-node
+rules:
+  - apiGroups: [""]
+    resources:
+      - pods
+      - nodes
+      - namespaces
+    verbs:
+      - get
+  - apiGroups: [""]
+    resources:
+      - endpoints
+      - services
+    verbs:
+      - watch
+      - list
+      - get
+  - apiGroups: [""]
+    resources:
+      - nodes/status
+    verbs:
+      - patch
+      - update
+  - apiGroups: ["networking.k8s.io"]
+    resources:
+      - networkpolicies
+    verbs:
+      - watch
+      - list
+  - apiGroups: [""]
+    resources:
+      - pods
+      - namespaces
+      - serviceaccounts
+    verbs:
+      - list
+      - watch
+  - apiGroups: [""]
+    resources:
+      - pods/status
+    verbs:
+      - patch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - globalfelixconfigs
+      - felixconfigurations
+      - bgppeers
+      - globalbgpconfigs
+      - bgpconfigurations
+      - ippools
+      - ipamblocks
+      - globalnetworkpolicies
+      - globalnetworksets
+      - networkpolicies
+      - clusterinformations
+      - hostendpoints
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ippools
+      - felixconfigurations
+      - clusterinformations
+    verbs:
+      - create
+      - update
+  - apiGroups: [""]
+    resources:
+      - nodes
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - bgpconfigurations
+      - bgppeers
+    verbs:
+      - create
+      - update
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+      - ipamblocks
+      - ipamhandles
+    verbs:
+      - get
+      - list
+      - create
+      - update
+      - delete
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - ipamconfigs
+    verbs:
+      - get
+  - apiGroups: ["crd.projectcalico.org"]
+    resources:
+      - blockaffinities
+    verbs:
+      - watch
+  - apiGroups: ["apps"]
+    resources:
+      - daemonsets
+    verbs:
+      - get
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: ClusterRoleBinding
+metadata:
+  name: calico-node
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: calico-node
+subjects:
+- kind: ServiceAccount
+  name: calico-node
+  namespace: kube-system
+---
+kind: DaemonSet
+apiVersion: extensions/v1beta1
+metadata:
+  name: calico-node
+  namespace: kube-system
+  labels:
+    k8s-app: calico-node
+spec:
+  selector:
+    matchLabels:
+      k8s-app: calico-node
+  updateStrategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxUnavailable: 1
+  template:
+    metadata:
+      labels:
+        k8s-app: calico-node
+      annotations:
+        scheduler.alpha.kubernetes.io/critical-pod: ''
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      hostNetwork: true
+      tolerations:
+        - effect: NoSchedule
+          operator: Exists
+        - key: CriticalAddonsOnly
+          operator: Exists
+        - effect: NoExecute
+          operator: Exists
+      serviceAccountName: calico-node
+      terminationGracePeriodSeconds: 0
+      initContainers:
+        - name: upgrade-ipam
+          image: calico/cni:v3.6.1
+          command: ["/opt/cni/bin/calico-ipam", "-upgrade"]
+          env:
+            - name: KUBERNETES_NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: CALICO_NETWORKING_BACKEND
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: calico_backend
+          volumeMounts:
+            - mountPath: /var/lib/cni/networks
+              name: host-local-net-dir
+            - mountPath: /host/opt/cni/bin
+              name: cni-bin-dir
+        - name: install-cni
+          image: calico/cni:v3.6.1
+          command: ["/install-cni.sh"]
+          env:
+            - name: CNI_CONF_NAME
+              value: "10-calico.conflist"
+            - name: CNI_NETWORK_CONFIG
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: cni_network_config
+            - name: KUBERNETES_NODE_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            - name: CNI_MTU
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: veth_mtu
+            - name: SLEEP
+              value: "false"
+          volumeMounts:
+            - mountPath: /host/opt/cni/bin
+              name: cni-bin-dir
+            - mountPath: /host/etc/cni/net.d
+              name: cni-net-dir
+      containers:
+        - name: calico-node
+          image: calico/node:v3.6.1
+          env:
+            # Use Kubernetes API as the backing datastore.
+            - name: DATASTORE_TYPE
+              value: "kubernetes"
+            # Wait for the datastore.
+            - name: WAIT_FOR_DATASTORE
+              value: "true"
+            # Set based on the k8s node name.
+            - name: NODENAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: spec.nodeName
+            # Choose the backend to use.
+            - name: CALICO_NETWORKING_BACKEND
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: calico_backend
+            # Cluster type to identify the deployment type
+            - name: CLUSTER_TYPE
+              value: "k8s,bgp"
+            # Auto-detect the BGP IP address.
+            - name: IP
+              value: "autodetect"
+            - name: IP_AUTODETECTION_METHOD
+              value: "can-reach=www.google.com"
+            # Enable IPIP
+            - name: CALICO_IPV4POOL_IPIP
+              value: "Always"
+            # Set MTU for tunnel device used if ipip is enabled
+            - name: FELIX_IPINIPMTU
+              valueFrom:
+                configMapKeyRef:
+                  name: calico-config
+                  key: veth_mtu
+            # The default IPv4 pool to create on startup if none exists. Pod IPs will be
+            # chosen from this range. Changing this value after installation will have
+            # no effect. This should fall within `--cluster-cidr`.
+            - name: CALICO_IPV4POOL_CIDR
+              value: "10.244.0.0/16"
+            # Disable file logging so `kubectl logs` works.
+            - name: CALICO_DISABLE_FILE_LOGGING
+              value: "true"
+            # Set Felix endpoint to host default action to ACCEPT.
+            - name: FELIX_DEFAULTENDPOINTTOHOSTACTION
+              value: "ACCEPT"
+            # Disable IPv6 on Kubernetes.
+            - name: FELIX_IPV6SUPPORT
+              value: "false"
+            # Set Felix logging to "info"
+            - name: FELIX_LOGSEVERITYSCREEN
+              value: "info"
+            - name: FELIX_HEALTHENABLED
+              value: "true"
+          securityContext:
+            privileged: true
+          resources:
+            requests:
+              cpu: 250m
+          livenessProbe:
+            httpGet:
+              path: /liveness
+              port: 9099
+              host: localhost
+            periodSeconds: 10
+            initialDelaySeconds: 10
+            failureThreshold: 6
+          readinessProbe:
+            exec:
+              command:
+              - /bin/calico-node
+              - -bird-ready
+              - -felix-ready
+            periodSeconds: 10
+          volumeMounts:
+            - mountPath: /lib/modules
+              name: lib-modules
+              readOnly: true
+            - mountPath: /run/xtables.lock
+              name: xtables-lock
+              readOnly: false
+            - mountPath: /var/run/calico
+              name: var-run-calico
+              readOnly: false
+            - mountPath: /var/lib/calico
+              name: var-lib-calico
+              readOnly: false
+      volumes:
+        - name: lib-modules
+          hostPath:
+            path: /lib/modules
+        - name: var-run-calico
+          hostPath:
+            path: /var/run/calico
+        - name: var-lib-calico
+          hostPath:
+            path: /var/lib/calico
+        - name: xtables-lock
+          hostPath:
+            path: /run/xtables.lock
+            type: FileOrCreate
+        - name: cni-bin-dir
+          hostPath:
+            path: /opt/cni/bin
+        - name: cni-net-dir
+          hostPath:
+            # NOTE: moved to tmp so we can see what it attempts to write
+            path: /etc/cni/multus/calico/net.d
+        - name: host-local-net-dir
+          hostPath:
+            path: /var/lib/cni/networks
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: calico-node
+  namespace: kube-system
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: calico-kube-controllers
+  namespace: kube-system
+  labels:
+    k8s-app: calico-kube-controllers
+  annotations:
+    scheduler.alpha.kubernetes.io/critical-pod: ''
+spec:
+  replicas: 1
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      name: calico-kube-controllers
+      namespace: kube-system
+      labels:
+        k8s-app: calico-kube-controllers
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      tolerations:
+        - key: CriticalAddonsOnly
+          operator: Exists
+        - key: node-role.kubernetes.io/master
+          effect: NoSchedule
+      serviceAccountName: calico-kube-controllers
+      containers:
+        - name: calico-kube-controllers
+          image: calico/kube-controllers:v3.6.1
+          env:
+            - name: ENABLED_CONTROLLERS
+              value: node
+            - name: DATASTORE_TYPE
+              value: kubernetes
+          readinessProbe:
+            exec:
+              command:
+              - /usr/bin/check-status
+              - -r
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: calico-kube-controllers
+  namespace: kube-system
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/configMap.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/configMap.yaml
new file mode 100644 (file)
index 0000000..a2309ce
--- /dev/null
@@ -0,0 +1,29 @@
+# yamllint disable
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: sriovdp-config
+  namespace: kube-system
+data:
+  config.json: |
+    {
+        "resourceList": [{
+                "resourceName": "ps225_sriov_netdevice",
+                "selectors": {
+                    "vendors": ["14e4"],
+                    "devices": ["d800"],
+                    "drivers": ["bnxt_en"],
+                    "pfNames": ["enp8s0f0np0"]
+                }
+            },
+            {
+                "resourceName": "intel_sriov_netdevice",
+                "selectors": {
+                    "vendors": ["8086"],
+                    "devices": ["154c"],
+                    "drivers": ["i40evf"],
+                    "pfNames": ["enp12s0f0"]
+                }
+            }
+        ]
+    }
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/install-k8s-v1.16.sh b/src/foundation/scripts/cni/multus/multus-sriov-calico/install-k8s-v1.16.sh
new file mode 100755 (executable)
index 0000000..2f0af5e
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/bash -ex
+# shellcheck disable=SC2016,SC2046
+
+function wait_for {
+  # Execute in a subshell to prevent local variable override during recursion
+  (
+    local total_attempts=$1; shift
+    local cmdstr=$*
+    local sleep_time=2
+    echo -e "\n[wait_for] Waiting for cmd to return success: ${cmdstr}"
+    # shellcheck disable=SC2034
+    for attempt in $(seq "${total_attempts}"); do
+      echo "[wait_for] Attempt ${attempt}/${total_attempts%.*} for: ${cmdstr}"
+      # shellcheck disable=SC2015
+      eval "${cmdstr}" && echo "[wait_for] OK: ${cmdstr}" && return 0 || true
+      sleep "${sleep_time}"
+    done
+    echo "[wait_for] ERROR: Failed after max attempts: ${cmdstr}"
+    return 1
+  )
+}
+
+kubectl create -f configMap.yaml
+wait_for 5 'test $(kubectl get configmap -n kube-system | grep sriovdp-config -c ) -eq 1'
+
+kubectl create -f multus-sriov-calico-daemonsets-k8s-v1.16.yaml
+wait_for 100 'test $(kubectl get pods -n kube-system | grep -e "kube-multus-ds" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-cni" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-device-plugin" | grep "Running" -c) -ge 1'
+
+kubectl create -f calico-daemonset-k8s-v1.16.yaml
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "calico-kube-controllers" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "calico-node" | grep "Running" -c) -ge 1'
+
+kubectl create -f sriov-crd.yaml
+wait_for 5 'test $(kubectl get crd | grep -e "network-attachment-definitions" -c) -ge 1'
+
+sleep 2
+kubectl get node $(hostname) -o json | jq '.status.allocatable' || true
+kubectl get pods --all-namespaces
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/install.sh b/src/foundation/scripts/cni/multus/multus-sriov-calico/install.sh
new file mode 100755 (executable)
index 0000000..7fc90ff
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/bash -ex
+# shellcheck disable=SC2016,SC2046
+
+function wait_for {
+  # Execute in a subshell to prevent local variable override during recursion
+  (
+    local total_attempts=$1; shift
+    local cmdstr=$*
+    local sleep_time=2
+    echo -e "\n[wait_for] Waiting for cmd to return success: ${cmdstr}"
+    # shellcheck disable=SC2034
+    for attempt in $(seq "${total_attempts}"); do
+      echo "[wait_for] Attempt ${attempt}/${total_attempts%.*} for: ${cmdstr}"
+      # shellcheck disable=SC2015
+      eval "${cmdstr}" && echo "[wait_for] OK: ${cmdstr}" && return 0 || true
+      sleep "${sleep_time}"
+    done
+    echo "[wait_for] ERROR: Failed after max attempts: ${cmdstr}"
+    return 1
+  )
+}
+
+
+kubectl create -f configMap.yaml
+wait_for 5 'test $(kubectl get configmap -n kube-system | grep sriovdp-config -c ) -eq 1'
+
+kubectl create -f multus-sriov-calico-daemonsets.yaml
+wait_for 100 'test $(kubectl get pods -n kube-system | grep -e "kube-multus-ds" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-cni" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-device-plugin" | grep "Running" -c) -ge 1'
+#kubectl create -f multus-sriov-calico-daemonsets-k8s-v1.16.yaml
+
+kubectl create -f calico-daemonset.yaml
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "calico-kube-controllers" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "calico-node" | grep "Running" -c) -ge 1'
+#kubectl create -f calico-daemonset-k8s-v1.16.yml
+
+kubectl create -f sriov-crd.yaml
+wait_for 5 'test $(kubectl get crd | grep -e "network-attachment-definitions" -c) -ge 1'
+
+sleep 2
+kubectl get node $(hostname) -o json | jq '.status.allocatable' || true
+kubectl get pods --all-namespaces
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets-k8s-v1.16.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets-k8s-v1.16.yaml
new file mode 100644 (file)
index 0000000..0238e6d
--- /dev/null
@@ -0,0 +1,614 @@
+# yamllint disable
+# This yaml file contains necessary configuration to setup
+# a demo environment for Multus + SR-IOV, the config includes
+# the following pieces:
+# 1. Multus ConfigMap
+# 2. Network Plumbing Working Group Spec Version 1 CustomerResourceDefinition
+# 3. Multus ClusterRole & ClusterRoleBinding
+# 4. Multus & SR-IOV Device Plugin ServiceAccounts
+# 5. Multus & SR-IOV Device Plugin & SR-IOV CNI DaemonSets
+
+# Note: This yaml file will not create customer SR-IOV CRD
+# which will be specified in Pod spec annotation. Below is
+# an example of SR-IOV CRD:
+#
+# apiVersion: "k8s.cni.cncf.io/v1"
+# kind: NetworkAttachmentDefinition
+# metadata:
+#   name: sriov-net1
+#   annotations:
+#     k8s.v1.cni.cncf.io/resourceName: intel.com/sriov
+# spec:
+#   config: '{
+#       "type": "sriov",
+#        "name": "sriov-network",
+#       "ipam": {
+#               "type": "host-local",
+#               "subnet": "10.56.217.0/24",
+#               "routes": [{
+#                       "dst": "0.0.0.0/0"
+#               }],
+#               "gateway": "10.56.217.1"
+#       }
+#   }'
+
+# An example of Pod spec using above SR-IOV CRD:
+#
+# apiVersion: v1
+# kind: Pod
+# metadata:
+#   name: testpod1
+#   labels:
+#     env: test
+#   annotations:
+#     k8s.v1.cni.cncf.io/networks: sriov-net1
+# spec:
+#   containers:
+#   - name: appcntr1
+#     image: centos/tools
+#     imagePullPolicy: IfNotPresent
+#     command: [ "/bin/bash", "-c", "--" ]
+#     args: [ "while true; do sleep 300000; done;" ]
+#     resources:
+#       requests:
+#         intel.com/sriov: '1'
+#       limits:
+#        intel.com/sriov: '1'
+
+
+# --------------------------------------------------------------------
+
+# 1. Multus ConfigMap
+#
+# This configMap assumes that:
+# - Kubeconfig file is located at "/etc/kubernetes/admin.conf" on host
+# - Default master plugin for Multus is set to flannel
+#
+# Note: If either of above is not True in your environment
+# make sure they are properly set to the corrent values.
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: multus-cni-config
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+data:
+  cni-conf.json: |
+    {
+      "name": "multus-cni-network",
+      "type": "multus",
+      "capabilities": {
+        "portMappings": true
+      },
+      "delegates": [
+        {
+          "cniVersion": "0.3.1",
+          "name": "default-cni-network",
+          "plugins": [
+            {
+              "name": "k8s-pod-network",
+              "cniVersion": "0.3.0",
+              "type": "calico",
+              "log_level": "info",
+              "datastore_type": "kubernetes",
+              "nodename": "__KUBERNETES_NODE_NAME__",
+              "mtu": 1440,
+              "ipam": {
+                "type": "calico-ipam"
+              },
+              "policy": {
+                "type": "k8s"
+              },
+              "kubernetes": {
+                "kubeconfig": "/etc/kubernetes/admin.conf"
+              }
+            },
+            {
+              "type": "portmap",
+              "snat": true,
+              "capabilities": {"portMappings": true}
+            }
+          ]
+        }
+      ],
+      "kubeconfig": "/etc/kubernetes/admin.conf"
+    }
+    #"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
+# 2. NPWG spec v1 Network Attachment Definition
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: network-attachment-definitions.k8s.cni.cncf.io
+spec:
+  group: k8s.cni.cncf.io
+  scope: Namespaced
+  names:
+    plural: network-attachment-definitions
+    singular: network-attachment-definition
+    kind: NetworkAttachmentDefinition
+    shortNames:
+    - net-attach-def
+  versions:
+    - name: v1
+      served: true
+      storage: true
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                config:
+                  type: string
+# 3.1 Multus Cluster Role
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+rules:
+  - apiGroups: ["k8s.cni.cncf.io"]
+    resources:
+      - '*'
+    verbs:
+      - '*'
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - pods/status
+    verbs:
+      - get
+      - update
+
+# 3.2 Multus Cluster Role Binding
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: multus
+subjects:
+- kind: ServiceAccount
+  name: multus
+  namespace: kube-system
+
+# 4.1 SR-IOV Device Plugin ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sriov-device-plugin
+  namespace: kube-system
+
+# 4.2 Multus ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: multus
+  namespace: kube-system
+
+# 5.1 SR-IOV Device Plugin DaemonSet
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  selector:
+    matchLabels:
+      name: sriov-device-plugin
+  template:
+    metadata:
+      labels:
+        name: sriov-device-plugin
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        #image: nfvpe/sriov-device-plugin
+        image: iecedge/sriov-device-plugin-amd64
+        imagePullPolicy: IfNotPresent
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  selector:
+    matchLabels:
+      name: sriov-device-plugin
+  template:
+    metadata:
+      labels:
+        name: sriov-device-plugin
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        #image: nfvpe/sriov-device-plugin
+        image: iecedge/sriov-device-plugin-arm64
+        imagePullPolicy: IfNotPresent
+        #imagePullPolicy: Never
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+
+# 5.2 SR-IOV CNI DaemonSet
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  selector:
+    matchLabels:
+      name: sriov-cni
+  template:
+    metadata:
+      labels:
+        name: sriov-cni
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - key: node-role.kubernetes.io/master
+        operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        #image: nfvpe/sriov-cni:latest
+        image: iecedge/sriov-cni-amd64:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  selector:
+    matchLabels:
+      name: sriov-cni        
+  template:
+    metadata:
+      labels:
+        name: sriov-cni
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        #image: nfvpe/sriov-cni-arm64:latest
+        image: iecedge/sriov-cni-arm64:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+
+# 5.3 Multus DaemonSet
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        #image: nfvpe/multus:v3.4
+        image: iecedge/multus-amd64:v3.4
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: KUBERNETES_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        command:
+        - /bin/bash
+        - -cex
+        - |
+          #!/bin/bash
+          sed "s|__KUBERNETES_NODE_NAME__|${KUBERNETES_NODE_NAME}|g" /tmp/multus-conf/70-multus.conf.template > /tmp/multus-conf/70-multus.conf
+          /entrypoint.sh \
+            --multus-conf-file=/tmp/multus-conf/70-multus.conf
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+          #- name: multus-cfg
+          #mountPath: /tmp/multus-conf
+          #readOnly: false
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf/70-multus.conf.template
+          subPath: "cni-conf.json"
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+            #- name: multus-cfg
+            #configMap:
+            #name: multus-cni-config
+            #items:
+            #- key: cni-conf.json
+            #  path: 70-multus.conf.template
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #image: iecedge/multus-arm64:latest
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        image: iecedge/multus-arm64:v3.4
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: KUBERNETES_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        command:
+        - /bin/bash
+        - -cex
+        - |
+          #!/bin/bash
+          sed "s|__KUBERNETES_NODE_NAME__|${KUBERNETES_NODE_NAME}|g" /tmp/multus-conf/70-multus.conf.template > /tmp/multus-conf/70-multus.conf
+          /entrypoint.sh \
+            --multus-conf-file=/tmp/multus-conf/70-multus.conf
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+          #- name: multus-cfg
+          #mountPath: /tmp/multus-conf
+          #readOnly: false
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf/70-multus.conf.template
+          subPath: "cni-conf.json"
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+            #- name: multus-cfg
+            #configMap:
+            #name: multus-cni-config
+            #items:
+            #- key: cni-conf.json
+            #  path: 70-multus.conf.template
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/multus-sriov-calico-daemonsets.yaml
new file mode 100644 (file)
index 0000000..bb84657
--- /dev/null
@@ -0,0 +1,592 @@
+# yamllint disable
+# This yaml file contains necessary configuration to setup
+# a demo environment for Multus + SR-IOV, the config includes
+# the following pieces:
+# 1. Multus ConfigMap
+# 2. Network Plumbing Working Group Spec Version 1 CustomerResourceDefinition
+# 3. Multus ClusterRole & ClusterRoleBinding
+# 4. Multus & SR-IOV Device Plugin ServiceAccounts
+# 5. Multus & SR-IOV Device Plugin & SR-IOV CNI DaemonSets
+
+# Note: This yaml file will not create customer SR-IOV CRD
+# which will be specified in Pod spec annotation. Below is
+# an example of SR-IOV CRD:
+#
+# apiVersion: "k8s.cni.cncf.io/v1"
+# kind: NetworkAttachmentDefinition
+# metadata:
+#   name: sriov-net1
+#   annotations:
+#     k8s.v1.cni.cncf.io/resourceName: intel.com/sriov
+# spec:
+#   config: '{
+#       "type": "sriov",
+#        "name": "sriov-network",
+#       "ipam": {
+#               "type": "host-local",
+#               "subnet": "10.56.217.0/24",
+#               "routes": [{
+#                       "dst": "0.0.0.0/0"
+#               }],
+#               "gateway": "10.56.217.1"
+#       }
+#   }'
+
+# An example of Pod spec using above SR-IOV CRD:
+#
+# apiVersion: v1
+# kind: Pod
+# metadata:
+#   name: testpod1
+#   labels:
+#     env: test
+#   annotations:
+#     k8s.v1.cni.cncf.io/networks: sriov-net1
+# spec:
+#   containers:
+#   - name: appcntr1
+#     image: centos/tools
+#     imagePullPolicy: IfNotPresent
+#     command: [ "/bin/bash", "-c", "--" ]
+#     args: [ "while true; do sleep 300000; done;" ]
+#     resources:
+#       requests:
+#         intel.com/sriov: '1'
+#       limits:
+#        intel.com/sriov: '1'
+
+
+# --------------------------------------------------------------------
+
+# 1. Multus ConfigMap
+#
+# This configMap assumes that:
+# - Kubeconfig file is located at "/etc/kubernetes/admin.conf" on host
+# - Default master plugin for Multus is set to flannel
+#
+# Note: If either of above is not True in your environment
+# make sure they are properly set to the corrent values.
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: multus-cni-config
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+data:
+  cni-conf.json: |
+    {
+      "name": "multus-cni-network",
+      "type": "multus",
+      "capabilities": {
+        "portMappings": true
+      },
+      "delegates": [
+        {
+          "cniVersion": "0.3.1",
+          "name": "default-cni-network",
+          "plugins": [
+            {
+              "name": "k8s-pod-network",
+              "cniVersion": "0.3.0",
+              "type": "calico",
+              "log_level": "info",
+              "datastore_type": "kubernetes",
+              "nodename": "__KUBERNETES_NODE_NAME__",
+              "mtu": 1440,
+              "ipam": {
+                "type": "calico-ipam"
+              },
+              "policy": {
+                "type": "k8s"
+              },
+              "kubernetes": {
+                "kubeconfig": "/etc/kubernetes/admin.conf"
+              }
+            },
+            {
+              "type": "portmap",
+              "snat": true,
+              "capabilities": {"portMappings": true}
+            }
+          ]
+        }
+      ],
+      "kubeconfig": "/etc/kubernetes/admin.conf"
+    }
+    #"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
+# 2. NPWG spec v1 Network Attachment Definition
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: network-attachment-definitions.k8s.cni.cncf.io
+spec:
+  group: k8s.cni.cncf.io
+  version: v1
+  scope: Namespaced
+  names:
+    plural: network-attachment-definitions
+    singular: network-attachment-definition
+    kind: NetworkAttachmentDefinition
+    shortNames:
+    - net-attach-def
+  validation:
+    openAPIV3Schema:
+      properties:
+        spec:
+          properties:
+            config:
+                 type: string
+
+
+# 3.1 Multus Cluster Role
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+rules:
+  - apiGroups: ["k8s.cni.cncf.io"]
+    resources:
+      - '*'
+    verbs:
+      - '*'
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - pods/status
+    verbs:
+      - get
+      - update
+
+# 3.2 Multus Cluster Role Binding
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: multus
+subjects:
+- kind: ServiceAccount
+  name: multus
+  namespace: kube-system
+
+# 4.1 SR-IOV Device Plugin ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sriov-device-plugin
+  namespace: kube-system
+
+# 4.2 Multus ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: multus
+  namespace: kube-system
+
+# 5.1 SR-IOV Device Plugin DaemonSet
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        image: nfvpe/sriov-device-plugin
+        imagePullPolicy: IfNotPresent
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        #image: nfvpe/sriov-device-plugin
+        image: iecedge/sriov-device-plugin-arm64
+        imagePullPolicy: IfNotPresent
+        #imagePullPolicy: Never
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+
+# 5.2 SR-IOV CNI DaemonSet
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - key: node-role.kubernetes.io/master
+        operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        image: nfvpe/sriov-cni:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        #image: nfvpe/sriov-cni-arm64:latest
+        image: iecedge/sriov-cni-arm64:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+
+# 5.3 Multus DaemonSet
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        image: nfvpe/multus:v3.4
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: KUBERNETES_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        command:
+        - /bin/bash
+        - -cex
+        - |
+          #!/bin/bash
+          sed "s|__KUBERNETES_NODE_NAME__|${KUBERNETES_NODE_NAME}|g" /tmp/multus-conf/70-multus.conf.template > /tmp/multus-conf/70-multus.conf
+          /entrypoint.sh \
+            --multus-conf-file=/tmp/multus-conf/70-multus.conf
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+          #- name: multus-cfg
+          #mountPath: /tmp/multus-conf
+          #readOnly: false
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf/70-multus.conf.template
+          subPath: "cni-conf.json"
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+            #- name: multus-cfg
+            #configMap:
+            #name: multus-cni-config
+            #items:
+            #- key: cni-conf.json
+            #  path: 70-multus.conf.template
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #image: iecedge/multus-arm64:latest
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        image: iecedge/multus-arm64:v3.4
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: KUBERNETES_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        command:
+        - /bin/bash
+        - -cex
+        - |
+          #!/bin/bash
+          sed "s|__KUBERNETES_NODE_NAME__|${KUBERNETES_NODE_NAME}|g" /tmp/multus-conf/70-multus.conf.template > /tmp/multus-conf/70-multus.conf
+          /entrypoint.sh \
+            --multus-conf-file=/tmp/multus-conf/70-multus.conf
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+          #- name: multus-cfg
+          #mountPath: /tmp/multus-conf
+          #readOnly: false
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf/70-multus.conf.template
+          subPath: "cni-conf.json"
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+            #- name: multus-cfg
+            #configMap:
+            #name: multus-cni-config
+            #items:
+            #- key: cni-conf.json
+            #  path: 70-multus.conf.template
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/sriov-crd.yaml b/src/foundation/scripts/cni/multus/multus-sriov-calico/sriov-crd.yaml
new file mode 100644 (file)
index 0000000..3502975
--- /dev/null
@@ -0,0 +1,24 @@
+# yamllint disable
+apiVersion: "k8s.cni.cncf.io/v1"
+kind: NetworkAttachmentDefinition
+metadata:
+  name: sriov-net1
+  annotations:
+    k8s.v1.cni.cncf.io/resourceName: arm.com/ps225_sriov_netdevice
+    #  "vlan": 1000,
+spec:
+  config: '{
+  "type": "sriov",
+  "cniVersion": "0.3.1",
+  "name": "sriov-network",
+  "ipam": {
+    "type": "host-local",
+    "subnet": "10.56.217.0/24",
+    "rangeStart": "10.56.217.11",
+    "rangeEnd": "10.56.217.181",
+    "routes": [{
+      "dst": "0.0.0.0/0"
+    }],
+    "gateway": "10.56.217.1"
+  }
+}'
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall-k8s-v1.16.sh b/src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall-k8s-v1.16.sh
new file mode 100755 (executable)
index 0000000..fbaba0e
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+# shellcheck disable=SC1073,SC1072,SC1039,SC2059,SC2046
+set -x
+
+kubectl delete -f sriov-crd.yaml
+sleep 2
+kubectl delete -f calico-daemonset-k8s-v1.16.yaml
+sleep 5
+kubectl delete -f multus-sriov-calico-daemonsets-k8s-v1.16.yaml
+sleep 5
+kubectl delete -f configMap.yaml
+sleep 2
+
+kubectl get node $(hostname) -o json | jq '.status.allocatable' || true
+kubectl get pods --all-namespaces
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall.sh b/src/foundation/scripts/cni/multus/multus-sriov-calico/uninstall.sh
new file mode 100755 (executable)
index 0000000..a0bcc79
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+# shellcheck disable=SC1073,SC1072,SC1039,SC2059,SC2046
+set -x
+
+kubectl delete -f sriov-crd.yaml
+sleep 2
+kubectl delete -f calico-daemonset.yaml
+#kubectl delete -f calico-daemonset-k8s-v1.16.yaml
+sleep 5
+#kubectl delete -f multus-sriov-calico-daemonsets.yaml
+kubectl delete -f multus-sriov-calico-daemonsets-k8s-v1.16.yaml
+sleep 5
+kubectl delete -f configMap.yaml
+sleep 2
+
+kubectl get node $(hostname) -o json | jq '.status.allocatable' || true
+kubectl get pods --all-namespaces
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/configMap.yaml b/src/foundation/scripts/cni/multus/multus-sriov-flannel/configMap.yaml
new file mode 100644 (file)
index 0000000..5022ea4
--- /dev/null
@@ -0,0 +1,37 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: sriovdp-config
+  namespace: kube-system
+data:
+  config.json: |
+    {
+        "resourceList": [{
+                "resourceName": "ps225_sriov_netdevice",
+                "selectors": {
+                    "vendors": ["14e4"],
+                    "devices": ["d800"],
+                    "drivers": ["bnxt_en"],
+                    "pfNames": ["enp8s0f0np0"]
+                }
+            },
+            {
+                "resourceName": "intel_sriov_netdevice",
+                "selectors": {
+                    "vendors": ["8086"],
+                    "devices": ["154c"],
+                    "drivers": ["i40evf"],
+                    "pfNames": ["enp12s0f0"]
+                }
+            },
+            {
+                "resourceName": "intel_sriov_dpdk",
+                "selectors": {
+                    "vendors": ["8086"],
+                    "devices": ["154c"],
+                    "drivers": ["vfio-pci"],
+                    "pfNames": ["enp12s0f1"]
+                }
+            }
+        ]
+    }
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/flannel-daemonset.yml b/src/foundation/scripts/cni/multus/multus-sriov-flannel/flannel-daemonset.yml
new file mode 100644 (file)
index 0000000..f8ef216
--- /dev/null
@@ -0,0 +1,479 @@
+# yamllint disable
+# This is a modified Flannel daemonset.
+# it is based on: https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
+# Notably, it removes the creation of an configuration file in/etc/cni/net.d/
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: flannel
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+    verbs:
+      - get
+  - apiGroups:
+      - ""
+    resources:
+      - nodes
+    verbs:
+      - list
+      - watch
+  - apiGroups:
+      - ""
+    resources:
+      - nodes/status
+    verbs:
+      - patch
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: flannel
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: flannel
+subjects:
+  - kind: ServiceAccount
+    name: flannel
+    namespace: kube-system
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: flannel
+  namespace: kube-system
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: kube-flannel-cfg
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+data:
+  # ------------------------------- Intentionally removed, Multus daemonset configures /etc/cni/net.d
+  #cni-conf.json: |
+  #  {
+  #    "name": "cbr0",
+  #    "plugins": [
+  #      {
+  #        "type": "flannel",
+  #        "delegate": {
+  #          "hairpinMode": true,
+  #          "isDefaultGateway": true
+  #        }
+  #      },
+  #      {
+  #        "type": "portmap",
+  #        "capabilities": {
+  #          "portMappings": true
+  #        }
+  #      }
+  #    ]
+  #  }
+  net-conf.json: |
+    {
+      "Network": "10.244.0.0/16",
+      "Backend": {
+        "Type": "vxlan",
+        "Port": 18989
+      }
+    }
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: flannel
+      # ------------------------------- Intentionally removed, Multus daemonset configures /etc/cni/net.d
+      # initContainers:
+      # - name: install-cni
+      #   image: quay.io/coreos/flannel:v0.10.0-amd64
+      #   command:
+      #   - cp
+      #   args:
+      #   - -f
+      #   - /etc/kube-flannel/cni-conf.json
+      #   - /etc/cni/net.d/10-flannel.conflist
+      #   volumeMounts:
+      #   - name: cni
+      #     mountPath: /etc/cni/net.d
+      #   - name: flannel-cfg
+      #     mountPath: /etc/kube-flannel/
+      containers:
+      - name: kube-flannel
+        image: quay.io/coreos/flannel:v0.10.0-amd64
+        imagePullPolicy: IfNotPresent
+        command:
+        - /opt/bin/flanneld
+        args:
+        - --ip-masq
+        - --kube-subnet-mgr
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: flannel
+      # ------------------------------- Intentionally removed, Multus daemonset configures /etc/cni/net.d
+      # initContainers:
+      # - name: install-cni
+      #   image: quay.io/coreos/flannel:v0.10.0-arm64
+      #   command:
+      #   - cp
+      #   args:
+      #   - -f
+      #   - /etc/kube-flannel/cni-conf.json
+      #   - /etc/cni/net.d/10-flannel.conflist
+      #   volumeMounts:
+      #   - name: cni
+      #     mountPath: /etc/cni/net.d
+      #   - name: flannel-cfg
+      #     mountPath: /etc/kube-flannel/
+      containers:
+      - name: kube-flannel
+        image: quay.io/coreos/flannel:v0.10.0-arm64
+        command:
+        - /opt/bin/flanneld
+        args:
+        - --ip-masq
+        - --kube-subnet-mgr
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds-arm
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: flannel
+      initContainers:
+      - name: install-cni
+        image: quay.io/coreos/flannel:v0.10.0-arm
+        command:
+        - cp
+        args:
+        - -f
+        - /etc/kube-flannel/cni-conf.json
+        - /etc/cni/net.d/10-flannel.conflist
+        volumeMounts:
+        - name: cni
+          mountPath: /etc/cni/net.d
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      containers:
+      - name: kube-flannel
+        image: quay.io/coreos/flannel:v0.10.0-arm
+        command:
+        - /opt/bin/flanneld
+        args:
+        - --ip-masq
+        - --kube-subnet-mgr
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds-ppc64le
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: ppc64le
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: flannel
+      initContainers:
+      - name: install-cni
+        image: quay.io/coreos/flannel:v0.10.0-ppc64le
+        command:
+        - cp
+        args:
+        - -f
+        - /etc/kube-flannel/cni-conf.json
+        - /etc/cni/net.d/10-flannel.conflist
+        volumeMounts:
+        - name: cni
+          mountPath: /etc/cni/net.d
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      containers:
+      - name: kube-flannel
+        image: quay.io/coreos/flannel:v0.10.0-ppc64le
+        command:
+        - /opt/bin/flanneld
+        args:
+        - --ip-masq
+        - --kube-subnet-mgr
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-flannel-ds-s390x
+  namespace: kube-system
+  labels:
+    tier: node
+    app: flannel
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: flannel
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: s390x
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: flannel
+      initContainers:
+      - name: install-cni
+        image: quay.io/coreos/flannel:v0.10.0-s390x
+        command:
+        - cp
+        args:
+        - -f
+        - /etc/kube-flannel/cni-conf.json
+        - /etc/cni/net.d/10-flannel.conflist
+        volumeMounts:
+        - name: cni
+          mountPath: /etc/cni/net.d
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      containers:
+      - name: kube-flannel
+        image: quay.io/coreos/flannel:v0.10.0-s390x
+        command:
+        - /opt/bin/flanneld
+        args:
+        - --ip-masq
+        - --kube-subnet-mgr
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        volumeMounts:
+        - name: run
+          mountPath: /run
+        - name: flannel-cfg
+          mountPath: /etc/kube-flannel/
+      volumes:
+        - name: run
+          hostPath:
+            path: /run
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: flannel-cfg
+          configMap:
+            name: kube-flannel-cfg
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/install.sh b/src/foundation/scripts/cni/multus/multus-sriov-flannel/install.sh
new file mode 100755 (executable)
index 0000000..8fb0208
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/bash -ex
+# shellcheck disable=SC2016,SC2046
+
+#SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")
+
+function wait_for {
+  # Execute in a subshell to prevent local variable override during recursion
+  (
+    local total_attempts=$1; shift
+    local cmdstr=$*
+    local sleep_time=2
+    echo -e "\n[wait_for] Waiting for cmd to return success: ${cmdstr}"
+    # shellcheck disable=SC2034
+    for attempt in $(seq "${total_attempts}"); do
+      echo "[wait_for] Attempt ${attempt}/${total_attempts%.*} for: ${cmdstr}"
+      # shellcheck disable=SC2015
+      eval "${cmdstr}" && echo "[wait_for] OK: ${cmdstr}" && return 0 || true
+      sleep "${sleep_time}"
+    done
+    echo "[wait_for] ERROR: Failed after max attempts: ${cmdstr}"
+    return 1
+  )
+}
+
+
+kubectl create -f configMap.yaml
+wait_for 5 'test $(kubectl get configmap -n kube-system | grep sriovdp-config -c ) -eq 1'
+
+kubectl create -f multus-sriov-flannel-daemonsets.yaml
+wait_for 100 'test $(kubectl get pods -n kube-system | grep -e "kube-multus-ds" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-cni" | grep "Running" -c) -ge 1'
+wait_for 20 'test $(kubectl get pods -n kube-system | grep -e "kube-sriov-device-plugin" | grep "Running" -c) -ge 1'
+
+kubectl create -f flannel-daemonset.yml
+wait_for 100 'test $(kubectl get pods -n kube-system | grep -e "kube-flannel-ds" | grep "Running" -c) -ge 1'
+
+kubectl create -f sriov-crd.yaml
+wait_for 5 'test $(kubectl get crd | grep -e "network-attachment-definitions" -c) -ge 1'
+
+
+kubectl get node $(hostname) -o json | jq '.status.allocatable'
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/multus-sriov-flannel-daemonsets.yaml b/src/foundation/scripts/cni/multus/multus-sriov-flannel/multus-sriov-flannel-daemonsets.yaml
new file mode 100644 (file)
index 0000000..091ccbc
--- /dev/null
@@ -0,0 +1,551 @@
+# yamllint disable
+# This yaml file contains necessary configuration to setup
+# a demo environment for Multus + SR-IOV, the config includes
+# the following pieces:
+# 1. Multus ConfigMap
+# 2. Network Plumbing Working Group Spec Version 1 CustomerResourceDefinition
+# 3. Multus ClusterRole & ClusterRoleBinding
+# 4. Multus & SR-IOV Device Plugin ServiceAccounts
+# 5. Multus & SR-IOV Device Plugin & SR-IOV CNI DaemonSets
+
+# Note: This yaml file will not create customer SR-IOV CRD
+# which will be specified in Pod spec annotation. Below is
+# an example of SR-IOV CRD:
+#
+# apiVersion: "k8s.cni.cncf.io/v1"
+# kind: NetworkAttachmentDefinition
+# metadata:
+#   name: sriov-net1
+#   annotations:
+#     k8s.v1.cni.cncf.io/resourceName: intel.com/sriov
+# spec:
+#   config: '{
+#       "type": "sriov",
+#        "name": "sriov-network",
+#       "ipam": {
+#               "type": "host-local",
+#               "subnet": "10.56.217.0/24",
+#               "routes": [{
+#                       "dst": "0.0.0.0/0"
+#               }],
+#               "gateway": "10.56.217.1"
+#       }
+#   }'
+
+# An example of Pod spec using above SR-IOV CRD:
+#
+# apiVersion: v1
+# kind: Pod
+# metadata:
+#   name: testpod1
+#   labels:
+#     env: test
+#   annotations:
+#     k8s.v1.cni.cncf.io/networks: sriov-net1
+# spec:
+#   containers:
+#   - name: appcntr1
+#     image: centos/tools
+#     imagePullPolicy: IfNotPresent
+#     command: [ "/bin/bash", "-c", "--" ]
+#     args: [ "while true; do sleep 300000; done;" ]
+#     resources:
+#       requests:
+#         intel.com/sriov: '1'
+#       limits:
+#        intel.com/sriov: '1'
+
+
+# --------------------------------------------------------------------
+
+# 1. Multus ConfigMap
+#
+# This configMap assumes that:
+# - Kubeconfig file is located at "/etc/kubernetes/admin.conf" on host
+# - Default master plugin for Multus is set to flannel
+#
+# Note: If either of above is not True in your environment
+# make sure they are properly set to the corrent values.
+---
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: multus-cni-config
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+data:
+  cni-conf.json: |
+    {
+      "name": "multus-cni-network",
+      "type": "multus",
+      "capabilities": {
+        "portMappings": true
+      },
+      "delegates": [
+        {
+          "cniVersion": "0.3.1",
+          "name": "default-cni-network",
+          "plugins": [
+            {
+              "type": "flannel",
+              "name": "flannel.1",
+                "delegate": {
+                  "isDefaultGateway": true,
+                  "hairpinMode": true
+                }
+            },
+            {
+              "type": "portmap",
+              "capabilities": {
+                "portMappings": true
+              }
+            }
+          ]
+        }
+      ],
+      "kubeconfig": "/etc/kubernetes/admin.conf"
+    }
+
+
+# 2. NPWG spec v1 Network Attachment Definition
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: network-attachment-definitions.k8s.cni.cncf.io
+spec:
+  group: k8s.cni.cncf.io
+  version: v1
+  scope: Namespaced
+  names:
+    plural: network-attachment-definitions
+    singular: network-attachment-definition
+    kind: NetworkAttachmentDefinition
+    shortNames:
+    - net-attach-def
+  validation:
+    openAPIV3Schema:
+      properties:
+        spec:
+          properties:
+            config:
+                 type: string
+
+
+# 3.1 Multus Cluster Role
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+rules:
+  - apiGroups: ["k8s.cni.cncf.io"]
+    resources:
+      - '*'
+    verbs:
+      - '*'
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - pods/status
+    verbs:
+      - get
+      - update
+
+# 3.2 Multus Cluster Role Binding
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+  name: multus
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: multus
+subjects:
+- kind: ServiceAccount
+  name: multus
+  namespace: kube-system
+
+# 4.1 SR-IOV Device Plugin ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sriov-device-plugin
+  namespace: kube-system
+
+# 4.2 Multus ServiceAccount
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: multus
+  namespace: kube-system
+
+# 5.1 SR-IOV Device Plugin DaemonSet
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        image: nfvpe/sriov-device-plugin
+        imagePullPolicy: IfNotPresent
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-device-plugin-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriovdp
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriovdp
+    spec:
+      hostNetwork: true
+      hostPID: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: sriov-device-plugin
+      containers:
+      - name: kube-sriovdp
+        #image: nfvpe/sriov-device-plugin
+        image: iecedge/sriov-device-plugin-arm64
+        imagePullPolicy: IfNotPresent
+        #imagePullPolicy: Never
+        args:
+        - --log-dir=sriovdp
+        - --log-level=10
+        - --resource-prefix=arm.com
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: devicesock
+          mountPath: /var/lib/kubelet/
+          readOnly: false
+        - name: log
+          mountPath: /var/log
+        - name: config-volume
+          mountPath: /etc/pcidp
+      volumes:
+        - name: devicesock
+          hostPath:
+            path: /var/lib/kubelet/
+        - name: log
+          hostPath:
+            path: /var/log
+        - name: config-volume
+          configMap:
+            name: sriovdp-config
+            items:
+            - key: config.json
+              path: config.json
+
+# 5.2 SR-IOV CNI DaemonSet
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - key: node-role.kubernetes.io/master
+        operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        image: nfvpe/sriov-cni:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+---
+apiVersion: extensions/v1beta1
+kind: DaemonSet
+metadata:
+  name: kube-sriov-cni-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: sriov-cni
+spec:
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: sriov-cni
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+              #- key: node-role.kubernetes.io/master
+              #        operator: Exists
+              #        effect: NoSchedule
+      - operator: Exists
+        effect: NoSchedule
+      containers:
+      - name: kube-sriov-cni
+        #image: nfvpe/sriov-cni-arm64:latest
+        image: iecedge/sriov-cni-arm64:latest
+        imagePullPolicy: IfNotPresent
+        securityContext:
+          privileged: true
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        volumeMounts:
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+      volumes:
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+
+# 5.3 Multus DaemonSet
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-amd64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: amd64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        #image: nfvpe/multus:v3.4
+        image: iecedge/multus-amd64:v3.4
+        imagePullPolicy: IfNotPresent
+        command: ["/entrypoint.sh"]
+        args:
+        - "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+            items:
+            - key: cni-conf.json
+              path: 70-multus.conf
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: kube-multus-ds-arm64
+  namespace: kube-system
+  labels:
+    tier: node
+    app: multus
+    name: multus
+spec:
+  selector:
+    matchLabels:
+      name: multus
+  updateStrategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        tier: node
+        app: multus
+        name: multus
+    spec:
+      hostNetwork: true
+      nodeSelector:
+        beta.kubernetes.io/arch: arm64
+      tolerations:
+      - operator: Exists
+        effect: NoSchedule
+      serviceAccountName: multus
+      containers:
+      - name: kube-multus
+        #image: nfvpe/multus:v3.3
+        #image: iecedge/multus-arm64:latest
+        #- "--multus-conf-file=auto"
+        #- "--cni-version=0.3.1"
+        image: iecedge/multus-arm64:v3.4
+        imagePullPolicy: IfNotPresent
+        command: ["/entrypoint.sh"]
+        args:
+        - "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
+        resources:
+          requests:
+            cpu: "100m"
+            memory: "50Mi"
+          limits:
+            cpu: "100m"
+            memory: "50Mi"
+        securityContext:
+          privileged: true
+        volumeMounts:
+        - name: cni
+          mountPath: /host/etc/cni/net.d
+        - name: cnibin
+          mountPath: /host/opt/cni/bin
+        - name: multus-cfg
+          mountPath: /tmp/multus-conf
+        - name: kubernetes-cfg-dir
+          mountPath: /etc/kubernetes
+      volumes:
+        - name: cni
+          hostPath:
+            path: /etc/cni/net.d
+        - name: cnibin
+          hostPath:
+            path: /opt/cni/bin
+        - name: multus-cfg
+          configMap:
+            name: multus-cni-config
+            items:
+            - key: cni-conf.json
+              path: 70-multus.conf
+        - name: kubernetes-cfg-dir
+          hostPath:
+            path: /etc/kubernetes
+
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/sriov-crd.yaml b/src/foundation/scripts/cni/multus/multus-sriov-flannel/sriov-crd.yaml
new file mode 100644 (file)
index 0000000..e5519b9
--- /dev/null
@@ -0,0 +1,23 @@
+apiVersion: "k8s.cni.cncf.io/v1"
+kind: NetworkAttachmentDefinition
+metadata:
+  name: sriov-net1
+  annotations:
+    k8s.v1.cni.cncf.io/resourceName: arm.com/ps225_sriov_netdevice
+    #  "vlan": 1000,
+spec:
+  config: '{
+  "type": "sriov",
+  "cniVersion": "0.3.1",
+  "name": "sriov-network",
+  "ipam": {
+    "type": "host-local",
+    "subnet": "10.56.217.0/24",
+    "rangeStart": "10.56.217.11",
+    "rangeEnd": "10.56.217.181",
+    "routes": [{
+      "dst": "0.0.0.0/0"
+    }],
+    "gateway": "10.56.217.1"
+  }
+}'
diff --git a/src/foundation/scripts/cni/multus/multus-sriov-flannel/uninstall.sh b/src/foundation/scripts/cni/multus/multus-sriov-flannel/uninstall.sh
new file mode 100755 (executable)
index 0000000..3479990
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash -ex
+# shellcheck disable=SC1073,SC1072,SC1039,SC2059,SC2046
+
+
+kubectl delete -f sriov-crd.yaml
+sleep 2
+kubectl delete -f flannel-daemonset.yml
+sleep 5
+kubectl delete -f multus-sriov-flannel-daemonsets.yaml
+sleep 5
+kubectl delete -f configMap.yaml
+sleep 2
+
+kubectl get node $(hostname) -o json | jq '.status.allocatable'
diff --git a/src/foundation/scripts/cni/multus/use-cases/Dockerfile.iperf2 b/src/foundation/scripts/cni/multus/use-cases/Dockerfile.iperf2
new file mode 100644 (file)
index 0000000..2db0130
--- /dev/null
@@ -0,0 +1,6 @@
+FROM ubuntu:18.04
+MAINTAINER The IPerf Project <trevor.tao@arm.com>
+
+RUN apt-get update && apt-get install -y iperf inetutils-ping iproute2
+
+CMD ["iperf -s &"]
diff --git a/src/foundation/scripts/cni/multus/use-cases/iperfv2-client-sriov.yaml b/src/foundation/scripts/cni/multus/use-cases/iperfv2-client-sriov.yaml
new file mode 100644 (file)
index 0000000..b2cc926
--- /dev/null
@@ -0,0 +1,30 @@
+# yamllint disable
+apiVersion: v1
+kind: Pod
+metadata:
+  name: iperfv2-client-sriov
+  labels:
+    app: iperfv2-client-sriov
+  annotations:
+    k8s.v1.cni.cncf.io/networks: sriov-net1
+    #spec:
+    #  replicas: 1
+    #  template:
+    #metadata:
+    #  labels:
+    #    app: iperf-client
+        # ldpreload-related labels
+spec:
+  containers:
+    - image: iecedge/iperf-arm64:latest
+      imagePullPolicy: IfNotPresent
+      name: iperfv2-client-sriov
+      command: ["bash"]
+      args: ["-c", "while true; do sleep 30; done;"]
+      resources:
+        requests:
+          arm.com/ps225_sriov_netdevice: '1'
+        limits:
+          arm.com/ps225_sriov_netdevice: '1'
+#        securityContext:
+#          privileged: true
diff --git a/src/foundation/scripts/cni/multus/use-cases/iperfv2-server-sriov.yaml b/src/foundation/scripts/cni/multus/use-cases/iperfv2-server-sriov.yaml
new file mode 100644 (file)
index 0000000..73636ea
--- /dev/null
@@ -0,0 +1,42 @@
+# yamllint disable
+#apiVersion: extensions/v1beta1
+apiVersion: apps/v1
+kind: Service
+apiVersion: v1
+metadata:
+  name: iperfv2-server-sriov
+spec:
+  type: NodePort
+  ports:
+    - protocol: TCP
+      port: 5001
+      targetPort: 5001
+      #nodePort: 31050
+  selector:
+    app: iperfv2-server-sriov
+---
+apiVersion: v1
+#kind: Deployment
+kind: Pod
+metadata:
+  name: iperfv2-server-sriov
+  labels:
+    app: iperfv2-server-sriov
+  annotations:
+    k8s.v1.cni.cncf.io/networks: sriov-net1
+spec:
+      containers:
+      - image: iecedge/iperf-arm64
+        imagePullPolicy: IfNotPresent
+        name: iperfv2-server-sriov
+        ports:
+        - containerPort: 5001
+        command: ["iperf"]
+        args: ["-s"]
+        resources:
+          requests:
+            arm.com/ps225_sriov_netdevice: '1'
+          limits:
+            arm.com/ps225_sriov_netdevice: '1'
+#        securityContext:
+#          privileged: true
diff --git a/src/foundation/scripts/cni/multus/use-cases/pod1.yaml b/src/foundation/scripts/cni/multus/use-cases/pod1.yaml
new file mode 100644 (file)
index 0000000..8e5a292
--- /dev/null
@@ -0,0 +1,23 @@
+# yamllint disable
+# An example of Pod spec using above SR-IOV CRD:
+apiVersion: v1
+kind: Pod
+metadata:
+  name: testpod1
+  labels:
+    env: test
+  annotations:
+    k8s.v1.cni.cncf.io/networks: sriov-net1
+spec:
+  containers:
+  - name: appcntr1
+    image: iecedge/centos-tools-arm64
+    imagePullPolicy: IfNotPresent
+    command: [ "/bin/bash", "-c", "--" ]
+    args: [ "while true; do sleep 300000; done;" ]
+    resources:
+      requests:
+       arm.com/ps225_sriov_netdevice: '1'
+      limits:
+       arm.com/ps225_sriov_netdevice: '1'
+
diff --git a/src/foundation/scripts/cni/multus/use-cases/pod2.yaml b/src/foundation/scripts/cni/multus/use-cases/pod2.yaml
new file mode 100644 (file)
index 0000000..0a87e45
--- /dev/null
@@ -0,0 +1,23 @@
+# yamllint disable
+# An example of Pod spec using above SR-IOV CRD:
+apiVersion: v1
+kind: Pod
+metadata:
+  name: testpod2
+  labels:
+    env: test
+  annotations:
+    k8s.v1.cni.cncf.io/networks: sriov-net1
+spec:
+  containers:
+  - name: appcntr2
+    image: iecedge/centos-tools-arm64
+    imagePullPolicy: IfNotPresent
+    command: [ "/bin/bash", "-c", "--" ]
+    args: [ "while true; do sleep 300000; done;" ]
+    resources:
+      requests:
+       arm.com/ps225_sriov_netdevice: '1'
+      limits:
+       arm.com/ps225_sriov_netdevice: '1'
+
index 8c4837c..ebfcf6f 100755 (executable)
@@ -30,7 +30,7 @@ K8S_WORKER_GROUP=(
 CLUSTER_IP=172.16.1.136 # Align with the value in our K8s setup script
 POD_NETWORK_CIDR=192.168.0.0/16
 SVC_CIDR=172.16.1.0/24
-#IEC support three kinds network solution for Kubernetes: calico,flannel,contivpp
+#IEC support multiple network solution for Kubernetes: calico,flannel,contivpp,cilium,etc
 CNI_TYPE=calico
 #kubernetes-cni version 0.7.5/ 0.6.0
 CNI_VERSION=0.6.0
index 74b15e5..f0689de 100755 (executable)
@@ -1,4 +1,5 @@
 #!/bin/bash
+# shellcheck disable=SC1073,SC1072,SC1039,SC2059,SC2046
 set -o xtrace
 set -e
 
@@ -10,9 +11,9 @@ fi
 
 CNI_TYPE=${1:-calico}
 POD_NETWORK_CIDR=${2:-192.168.0.0/16}
-CLUSTER_IP=${3:-172.16.1.136} # Align with the value in our K8s setup script
-K8S_MASTER_IP=${4:-10.169.41.173}
-SERVICE_CIDR=${5:-172.16.1.0/24}
+K8S_MASTER_IP=${3:-10.169.41.173}
+SERVICE_CIDR=${4:-172.16.1.0/24}
+CLUSTER_IP=${5:-172.16.1.136} # Align with the value in our K8s setup script
 DEV_NAME=${6:-}
 
 SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")
@@ -75,6 +76,25 @@ install_ovn_kubernetes(){
 
 }
 
+install_multus_sriov_flannel(){
+
+  sed -i "s@10.244.0.0/16@${POD_NETWORK_CIDR}@" "${SCRIPTS_DIR}/cni/multus/multus-sriov-flannel/flannel-daemonset.yml"
+  # Install Multus Flannel+SRIOV by yaml files
+  # shellcheck source=/dev/null
+  source ${SCRIPTS_DIR}/cni/multus/multus-sriov-flannel/install.sh
+
+}
+
+install_multus_sriov_calico(){
+
+  sed -i "s@10.244.0.0/16@${POD_NETWORK_CIDR}@" \
+    "${SCRIPTS_DIR}/cni/multus/multus-sriov-calico/calico-daemonset.yaml"
+  # Install Multus Calico+SRIOV by yaml files
+  # shellcheck source=/dev/null
+  source ${SCRIPTS_DIR}/cni/multus/multus-sriov-calico/install.sh
+
+}
+
 install_danm(){
   ${SCRIPTS_DIR}/cni/danm/danm_install.sh
 
@@ -88,6 +108,19 @@ install_danm(){
   install_flannel
 }
 
+
+install_cilium(){
+  ${SCRIPTS_DIR}/cni/cilium/cilium_install.sh
+
+  # Deploying cilium CNI
+  kubectl create -f ${SCRIPTS_DIR}/cni/cilium/quick-install.yaml
+}
+
+# Remove the taints on master node
+# Taint master before installing the CNI for the case that there is
+# only one master node
+kubectl taint nodes --all node-role.kubernetes.io/master- || true
+
 case ${CNI_TYPE} in
  'calico')
         echo "Install calico ..."
@@ -105,15 +138,25 @@ case ${CNI_TYPE} in
         echo "Install Ovn-Kubernetes ..."
         install_ovn_kubernetes
         ;;
+ 'multus-flannel-sriov')
+        echo "Install Flannel with SRIOV CNI by Multus-CNI ..."
+        install_multus_sriov_flannel
+        ;;
+ 'multus-calico-sriov')
+        echo "Install Calico with SRIOV CNI by Multus-CNI ..."
+        install_multus_sriov_calico
+        ;;
  'danm')
         echo "Install danm ..."
         install_danm
         ;;
+ 'cilium')
+        echo "Install cilium ..."
+        install_cilium
+        ;;
  *)
         echo "${CNI_TYPE} is not supported"
         exit 1
         ;;
 esac
 
-# Remove the taints on master node
-kubectl taint nodes --all node-role.kubernetes.io/master- || true
index 40f54d3..1687d9d 100755 (executable)
@@ -1,4 +1,5 @@
 #!/bin/bash
+# shellcheck disable=SC1073,SC1072,SC1039,SC2059,SC2046
 #Install the k8s-master & k8s-worker node from Mgnt node
 #
 set -e
@@ -49,6 +50,10 @@ case ${CNI_TYPE} in
     DANM_CONFIG="cd iec/src/foundation/scripts/cni/danm && sudo ./danm_install.sh"
     sshpass -p ${passwd} ssh -o StrictHostKeyChecking=no ${HOST_USER}@${ip_addr} $DANM_CONFIG
     ;;
+  cilium)
+    CILIUM_CONFIG="cd iec/src/foundation/scripts/cni/cilium && sudo ./cilium_install.sh"
+    sshpass -p ${passwd} ssh -o StrictHostKeyChecking=no ${HOST_USER}@${ip_addr} $CILIUM_CONFIG
+    ;;
   *)
     ;;
 esac
@@ -98,7 +103,7 @@ deploy_k8s () {
 
 
   #Deploy etcd & CNI from master node
-  SETUP_CNI="cd iec/src/foundation/scripts && source setup-cni.sh $CNI_TYPE $POD_NETWORK_CIDR $CLUSTER_IP $K8S_MASTER_IP $SVC_CIDR"
+  SETUP_CNI="cd iec/src/foundation/scripts && source setup-cni.sh $CNI_TYPE $POD_NETWORK_CIDR $K8S_MASTER_IP $SVC_CIDR $CLUSTER_IP"
   sshpass -p ${K8S_MASTERPW} ssh -o StrictHostKeyChecking=no ${HOST_USER}@${K8S_MASTER_IP} ${SETUP_CNI}
   SETUP_HELM="cd iec/src/foundation/scripts && source helm.sh"
   sshpass -p ${K8S_MASTERPW} ssh -o StrictHostKeyChecking=no ${HOST_USER}@${K8S_MASTER_IP} ${SETUP_HELM}
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/src/foundation/service_mesh/Istio/init/crd-10.yaml b/src/foundation/service_mesh/Istio/init/crd-10.yaml
new file mode 100644 (file)
index 0000000..6e9ad88
--- /dev/null
@@ -0,0 +1,574 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: virtualservices.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: VirtualService
+    listKind: VirtualServiceList
+    plural: virtualservices
+    singular: virtualservice
+    shortNames:
+    - vs
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+  additionalPrinterColumns:
+  - JSONPath: .spec.gateways
+    description: The names of gateways and sidecars that should apply these routes
+    name: Gateways
+    type: string
+  - JSONPath: .spec.hosts
+    description: The destination hosts to which traffic is being sent
+    name: Hosts
+    type: string
+  - JSONPath: .metadata.creationTimestamp
+    description: |-
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+    name: Age
+    type: date
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: destinationrules.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: DestinationRule
+    listKind: DestinationRuleList
+    plural: destinationrules
+    singular: destinationrule
+    shortNames:
+    - dr
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+  additionalPrinterColumns:
+  - JSONPath: .spec.host
+    description: The name of a service from the service registry
+    name: Host
+    type: string
+  - JSONPath: .metadata.creationTimestamp
+    description: |-
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+    name: Age
+    type: date
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: serviceentries.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: ServiceEntry
+    listKind: ServiceEntryList
+    plural: serviceentries
+    singular: serviceentry
+    shortNames:
+    - se
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+  additionalPrinterColumns:
+  - JSONPath: .spec.hosts
+    description: The hosts associated with the ServiceEntry
+    name: Hosts
+    type: string
+  - JSONPath: .spec.location
+    description: Whether the service is external to the mesh or part of the mesh (MESH_EXTERNAL or MESH_INTERNAL)
+    name: Location
+    type: string
+  - JSONPath: .spec.resolution
+    description: Service discovery mode for the hosts (NONE, STATIC, or DNS)
+    name: Resolution
+    type: string
+  - JSONPath: .metadata.creationTimestamp
+    description: |-
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+    name: Age
+    type: date
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: gateways.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: Gateway
+    plural: gateways
+    singular: gateway
+    shortNames:
+    - gw
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: envoyfilters.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: EnvoyFilter
+    plural: envoyfilters
+    singular: envoyfilter
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: clusterrbacconfigs.rbac.istio.io
+  labels:
+    app: istio-pilot
+    istio: rbac
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: rbac.istio.io
+  names:
+    kind: ClusterRbacConfig
+    plural: clusterrbacconfigs
+    singular: clusterrbacconfig
+    categories:
+    - istio-io
+    - rbac-istio-io
+  scope: Cluster
+  version: v1alpha1
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: policies.authentication.istio.io
+  labels:
+    app: istio-citadel
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: authentication.istio.io
+  names:
+    kind: Policy
+    plural: policies
+    singular: policy
+    categories:
+    - istio-io
+    - authentication-istio-io
+  scope: Namespaced
+  version: v1alpha1
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: meshpolicies.authentication.istio.io
+  labels:
+    app: istio-citadel
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: authentication.istio.io
+  names:
+    kind: MeshPolicy
+    listKind: MeshPolicyList
+    plural: meshpolicies
+    singular: meshpolicy
+    categories:
+    - istio-io
+    - authentication-istio-io
+  scope: Cluster
+  version: v1alpha1
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: httpapispecbindings.config.istio.io
+  labels:
+    app: istio-mixer
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: HTTPAPISpecBinding
+    plural: httpapispecbindings
+    singular: httpapispecbinding
+    categories:
+    - istio-io
+    - apim-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: httpapispecs.config.istio.io
+  labels:
+    app: istio-mixer
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: HTTPAPISpec
+    plural: httpapispecs
+    singular: httpapispec
+    categories:
+    - istio-io
+    - apim-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: quotaspecbindings.config.istio.io
+  labels:
+    app: istio-mixer
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: QuotaSpecBinding
+    plural: quotaspecbindings
+    singular: quotaspecbinding
+    categories:
+    - istio-io
+    - apim-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: quotaspecs.config.istio.io
+  labels:
+    app: istio-mixer
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: QuotaSpec
+    plural: quotaspecs
+    singular: quotaspec
+    categories:
+    - istio-io
+    - apim-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: rules.config.istio.io
+  labels:
+    app: mixer
+    package: istio.io.mixer
+    istio: core
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: rule
+    plural: rules
+    singular: rule
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: attributemanifests.config.istio.io
+  labels:
+    app: mixer
+    package: istio.io.mixer
+    istio: core
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: attributemanifest
+    plural: attributemanifests
+    singular: attributemanifest
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: rbacconfigs.rbac.istio.io
+  labels:
+    app: mixer
+    package: istio.io.mixer
+    istio: rbac
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: rbac.istio.io
+  names:
+    kind: RbacConfig
+    plural: rbacconfigs
+    singular: rbacconfig
+    categories:
+    - istio-io
+    - rbac-istio-io
+  scope: Namespaced
+  version: v1alpha1
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: serviceroles.rbac.istio.io
+  labels:
+    app: mixer
+    package: istio.io.mixer
+    istio: rbac
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: rbac.istio.io
+  names:
+    kind: ServiceRole
+    plural: serviceroles
+    singular: servicerole
+    categories:
+    - istio-io
+    - rbac-istio-io
+  scope: Namespaced
+  version: v1alpha1
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: servicerolebindings.rbac.istio.io
+  labels:
+    app: mixer
+    package: istio.io.mixer
+    istio: rbac
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: rbac.istio.io
+  names:
+    kind: ServiceRoleBinding
+    plural: servicerolebindings
+    singular: servicerolebinding
+    categories:
+    - istio-io
+    - rbac-istio-io
+  scope: Namespaced
+  version: v1alpha1
+  additionalPrinterColumns:
+  - JSONPath: .spec.roleRef.name
+    description: The name of the ServiceRole object being referenced
+    name: Reference
+    type: string
+  - JSONPath: .metadata.creationTimestamp
+    description: |-
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+    name: Age
+    type: date
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: adapters.config.istio.io
+  labels:
+    app: mixer
+    package: adapter
+    istio: mixer-adapter
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: adapter
+    plural: adapters
+    singular: adapter
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: instances.config.istio.io
+  labels:
+    app: mixer
+    package: instance
+    istio: mixer-instance
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: instance
+    plural: instances
+    singular: instance
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: templates.config.istio.io
+  labels:
+    app: mixer
+    package: template
+    istio: mixer-template
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: template
+    plural: templates
+    singular: template
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: handlers.config.istio.io
+  labels:
+    app: mixer
+    package: handler
+    istio: mixer-handler
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: config.istio.io
+  names:
+    kind: handler
+    plural: handlers
+    singular: handler
+    categories:
+    - istio-io
+    - policy-istio-io
+  scope: Namespaced
+  version: v1alpha2
+---
diff --git a/src/foundation/service_mesh/Istio/init/crd-11.yaml b/src/foundation/service_mesh/Istio/init/crd-11.yaml
new file mode 100644 (file)
index 0000000..36b2171
--- /dev/null
@@ -0,0 +1,24 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: sidecars.networking.istio.io
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: networking.istio.io
+  names:
+    kind: Sidecar
+    plural: sidecars
+    singular: sidecar
+    categories:
+    - istio-io
+    - networking-istio-io
+  scope: Namespaced
+  version: v1alpha3
+---
diff --git a/src/foundation/service_mesh/Istio/init/crd-12.yaml b/src/foundation/service_mesh/Istio/init/crd-12.yaml
new file mode 100644 (file)
index 0000000..36e0c8a
--- /dev/null
@@ -0,0 +1,21 @@
+kind: CustomResourceDefinition
+apiVersion: apiextensions.k8s.io/v1beta1
+metadata:
+  name: authorizationpolicies.rbac.istio.io
+  labels:
+    app: istio-pilot
+    istio: rbac
+    heritage: Tiller
+    release: istio
+spec:
+  group: rbac.istio.io
+  names:
+    kind: AuthorizationPolicy
+    plural: authorizationpolicies
+    singular: authorizationpolicy
+    categories:
+      - istio-io
+      - rbac-istio-io
+  scope: Namespaced
+  version: v1alpha1
+---
diff --git a/src/foundation/service_mesh/Istio/init/crd-certmanager-10.yaml b/src/foundation/service_mesh/Istio/init/crd-certmanager-10.yaml
new file mode 100644 (file)
index 0000000..e500822
--- /dev/null
@@ -0,0 +1,83 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: clusterissuers.certmanager.k8s.io
+  labels:
+    app: certmanager
+    chart: certmanager
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: certmanager.k8s.io
+  version: v1alpha1
+  names:
+    kind: ClusterIssuer
+    plural: clusterissuers
+  scope: Cluster
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: issuers.certmanager.k8s.io
+  labels:
+    app: certmanager
+    chart: certmanager
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  group: certmanager.k8s.io
+  version: v1alpha1
+  names:
+    kind: Issuer
+    plural: issuers
+  scope: Namespaced
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: certificates.certmanager.k8s.io
+  labels:
+    app: certmanager
+    chart: certmanager
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  additionalPrinterColumns:
+    - JSONPath: .status.conditions[?(@.type=="Ready")].status
+      name: Ready
+      type: string
+    - JSONPath: .spec.secretName
+      name: Secret
+      type: string
+    - JSONPath: .spec.issuerRef.name
+      name: Issuer
+      type: string
+      priority: 1
+    - JSONPath: .status.conditions[?(@.type=="Ready")].message
+      name: Status
+      type: string
+      priority: 1
+    - JSONPath: .metadata.creationTimestamp
+      description: |-
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+      name: Age
+      type: date
+  group: certmanager.k8s.io
+  version: v1alpha1
+  scope: Namespaced
+  names:
+    kind: Certificate
+    plural: certificates
+    shortNames:
+      - cert
+      - certs
+---
diff --git a/src/foundation/service_mesh/Istio/init/crd-certmanager-11.yaml b/src/foundation/service_mesh/Istio/init/crd-certmanager-11.yaml
new file mode 100644 (file)
index 0000000..e60e2f6
--- /dev/null
@@ -0,0 +1,75 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: orders.certmanager.k8s.io
+  labels:
+    app: certmanager
+    chart: certmanager
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  additionalPrinterColumns:
+    - JSONPath: .status.state
+      name: State
+      type: string
+    - JSONPath: .spec.issuerRef.name
+      name: Issuer
+      type: string
+      priority: 1
+    - JSONPath: .status.reason
+      name: Reason
+      type: string
+      priority: 1
+    - JSONPath: .metadata.creationTimestamp
+      description: |-
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+      name: Age
+      type: date
+  group: certmanager.k8s.io
+  version: v1alpha1
+  names:
+    kind: Order
+    plural: orders
+  scope: Namespaced
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: challenges.certmanager.k8s.io
+  labels:
+    app: certmanager
+    chart: certmanager
+    heritage: Tiller
+    release: istio
+  annotations:
+    "helm.sh/resource-policy": keep
+spec:
+  additionalPrinterColumns:
+    - JSONPath: .status.state
+      name: State
+      type: string
+    - JSONPath: .spec.dnsName
+      name: Domain
+      type: string
+    - JSONPath: .status.reason
+      name: Reason
+      type: string
+    - JSONPath: .metadata.creationTimestamp
+      description: |-
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.
+
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
+      name: Age
+      type: date
+  group: certmanager.k8s.io
+  version: v1alpha1
+  names:
+    kind: Challenge
+    plural: challenges
+  scope: Namespaced
+---
diff --git a/src/foundation/service_mesh/Istio/istio-demo-arm64.yaml b/src/foundation/service_mesh/Istio/istio-demo-arm64.yaml
new file mode 100644 (file)
index 0000000..bb1690c
--- /dev/null
@@ -0,0 +1,19760 @@
+apiVersion: v1\r
+kind: Namespace\r
+metadata:\r
+  name: istio-system\r
+  labels:\r
+    istio-injection: disabled\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: virtualservices.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: VirtualService\r
+    listKind: VirtualServiceList\r
+    plural: virtualservices\r
+    singular: virtualservice\r
+    shortNames:\r
+    - vs\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+  additionalPrinterColumns:\r
+  - JSONPath: .spec.gateways\r
+    description: The names of gateways and sidecars that should apply these routes\r
+    name: Gateways\r
+    type: string\r
+  - JSONPath: .spec.hosts\r
+    description: The destination hosts to which traffic is being sent\r
+    name: Hosts\r
+    type: string\r
+  - JSONPath: .metadata.creationTimestamp\r
+    description: |-\r
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+    name: Age\r
+    type: date\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: destinationrules.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: DestinationRule\r
+    listKind: DestinationRuleList\r
+    plural: destinationrules\r
+    singular: destinationrule\r
+    shortNames:\r
+    - dr\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+  additionalPrinterColumns:\r
+  - JSONPath: .spec.host\r
+    description: The name of a service from the service registry\r
+    name: Host\r
+    type: string\r
+  - JSONPath: .metadata.creationTimestamp\r
+    description: |-\r
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+    name: Age\r
+    type: date\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: serviceentries.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: ServiceEntry\r
+    listKind: ServiceEntryList\r
+    plural: serviceentries\r
+    singular: serviceentry\r
+    shortNames:\r
+    - se\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+  additionalPrinterColumns:\r
+  - JSONPath: .spec.hosts\r
+    description: The hosts associated with the ServiceEntry\r
+    name: Hosts\r
+    type: string\r
+  - JSONPath: .spec.location\r
+    description: Whether the service is external to the mesh or part of the mesh (MESH_EXTERNAL or MESH_INTERNAL)\r
+    name: Location\r
+    type: string\r
+  - JSONPath: .spec.resolution\r
+    description: Service discovery mode for the hosts (NONE, STATIC, or DNS)\r
+    name: Resolution\r
+    type: string\r
+  - JSONPath: .metadata.creationTimestamp\r
+    description: |-\r
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+    name: Age\r
+    type: date\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: gateways.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: Gateway\r
+    plural: gateways\r
+    singular: gateway\r
+    shortNames:\r
+    - gw\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: envoyfilters.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: EnvoyFilter\r
+    plural: envoyfilters\r
+    singular: envoyfilter\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: clusterrbacconfigs.rbac.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    istio: rbac\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: rbac.istio.io\r
+  names:\r
+    kind: ClusterRbacConfig\r
+    plural: clusterrbacconfigs\r
+    singular: clusterrbacconfig\r
+    categories:\r
+    - istio-io\r
+    - rbac-istio-io\r
+  scope: Cluster\r
+  version: v1alpha1\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: policies.authentication.istio.io\r
+  labels:\r
+    app: istio-citadel\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: authentication.istio.io\r
+  names:\r
+    kind: Policy\r
+    plural: policies\r
+    singular: policy\r
+    categories:\r
+    - istio-io\r
+    - authentication-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha1\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: meshpolicies.authentication.istio.io\r
+  labels:\r
+    app: istio-citadel\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: authentication.istio.io\r
+  names:\r
+    kind: MeshPolicy\r
+    listKind: MeshPolicyList\r
+    plural: meshpolicies\r
+    singular: meshpolicy\r
+    categories:\r
+    - istio-io\r
+    - authentication-istio-io\r
+  scope: Cluster\r
+  version: v1alpha1\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: httpapispecbindings.config.istio.io\r
+  labels:\r
+    app: istio-mixer\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: HTTPAPISpecBinding\r
+    plural: httpapispecbindings\r
+    singular: httpapispecbinding\r
+    categories:\r
+    - istio-io\r
+    - apim-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: httpapispecs.config.istio.io\r
+  labels:\r
+    app: istio-mixer\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: HTTPAPISpec\r
+    plural: httpapispecs\r
+    singular: httpapispec\r
+    categories:\r
+    - istio-io\r
+    - apim-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: quotaspecbindings.config.istio.io\r
+  labels:\r
+    app: istio-mixer\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: QuotaSpecBinding\r
+    plural: quotaspecbindings\r
+    singular: quotaspecbinding\r
+    categories:\r
+    - istio-io\r
+    - apim-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: quotaspecs.config.istio.io\r
+  labels:\r
+    app: istio-mixer\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: QuotaSpec\r
+    plural: quotaspecs\r
+    singular: quotaspec\r
+    categories:\r
+    - istio-io\r
+    - apim-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: rules.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: istio.io.mixer\r
+    istio: core\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: rule\r
+    plural: rules\r
+    singular: rule\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: attributemanifests.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: istio.io.mixer\r
+    istio: core\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: attributemanifest\r
+    plural: attributemanifests\r
+    singular: attributemanifest\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: rbacconfigs.rbac.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: istio.io.mixer\r
+    istio: rbac\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: rbac.istio.io\r
+  names:\r
+    kind: RbacConfig\r
+    plural: rbacconfigs\r
+    singular: rbacconfig\r
+    categories:\r
+    - istio-io\r
+    - rbac-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha1\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: serviceroles.rbac.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: istio.io.mixer\r
+    istio: rbac\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: rbac.istio.io\r
+  names:\r
+    kind: ServiceRole\r
+    plural: serviceroles\r
+    singular: servicerole\r
+    categories:\r
+    - istio-io\r
+    - rbac-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha1\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: servicerolebindings.rbac.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: istio.io.mixer\r
+    istio: rbac\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: rbac.istio.io\r
+  names:\r
+    kind: ServiceRoleBinding\r
+    plural: servicerolebindings\r
+    singular: servicerolebinding\r
+    categories:\r
+    - istio-io\r
+    - rbac-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha1\r
+  additionalPrinterColumns:\r
+  - JSONPath: .spec.roleRef.name\r
+    description: The name of the ServiceRole object being referenced\r
+    name: Reference\r
+    type: string\r
+  - JSONPath: .metadata.creationTimestamp\r
+    description: |-\r
+      CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+      Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+    name: Age\r
+    type: date\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: adapters.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: adapter\r
+    istio: mixer-adapter\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: adapter\r
+    plural: adapters\r
+    singular: adapter\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: instances.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: instance\r
+    istio: mixer-instance\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: instance\r
+    plural: instances\r
+    singular: instance\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: templates.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: template\r
+    istio: mixer-template\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: template\r
+    plural: templates\r
+    singular: template\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: handlers.config.istio.io\r
+  labels:\r
+    app: mixer\r
+    package: handler\r
+    istio: mixer-handler\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: config.istio.io\r
+  names:\r
+    kind: handler\r
+    plural: handlers\r
+    singular: handler\r
+    categories:\r
+    - istio-io\r
+    - policy-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha2\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: sidecars.networking.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: networking.istio.io\r
+  names:\r
+    kind: Sidecar\r
+    plural: sidecars\r
+    singular: sidecar\r
+    categories:\r
+    - istio-io\r
+    - networking-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha3\r
+---\r
+kind: CustomResourceDefinition\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+metadata:\r
+  name: authorizationpolicies.rbac.istio.io\r
+  labels:\r
+    app: istio-pilot\r
+    istio: rbac\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  group: rbac.istio.io\r
+  names:\r
+    kind: AuthorizationPolicy\r
+    plural: authorizationpolicies\r
+    singular: authorizationpolicy\r
+    categories:\r
+      - istio-io\r
+      - rbac-istio-io\r
+  scope: Namespaced\r
+  version: v1alpha1\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: clusterissuers.certmanager.k8s.io\r
+  labels:\r
+    app: certmanager\r
+    chart: certmanager\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: certmanager.k8s.io\r
+  version: v1alpha1\r
+  names:\r
+    kind: ClusterIssuer\r
+    plural: clusterissuers\r
+  scope: Cluster\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: issuers.certmanager.k8s.io\r
+  labels:\r
+    app: certmanager\r
+    chart: certmanager\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  group: certmanager.k8s.io\r
+  version: v1alpha1\r
+  names:\r
+    kind: Issuer\r
+    plural: issuers\r
+  scope: Namespaced\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: certificates.certmanager.k8s.io\r
+  labels:\r
+    app: certmanager\r
+    chart: certmanager\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  additionalPrinterColumns:\r
+    - JSONPath: .status.conditions[?(@.type=="Ready")].status\r
+      name: Ready\r
+      type: string\r
+    - JSONPath: .spec.secretName\r
+      name: Secret\r
+      type: string\r
+    - JSONPath: .spec.issuerRef.name\r
+      name: Issuer\r
+      type: string\r
+      priority: 1\r
+    - JSONPath: .status.conditions[?(@.type=="Ready")].message\r
+      name: Status\r
+      type: string\r
+      priority: 1\r
+    - JSONPath: .metadata.creationTimestamp\r
+      description: |-\r
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+      name: Age\r
+      type: date\r
+  group: certmanager.k8s.io\r
+  version: v1alpha1\r
+  scope: Namespaced\r
+  names:\r
+    kind: Certificate\r
+    plural: certificates\r
+    shortNames:\r
+      - cert\r
+      - certs\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: orders.certmanager.k8s.io\r
+  labels:\r
+    app: certmanager\r
+    chart: certmanager\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  additionalPrinterColumns:\r
+    - JSONPath: .status.state\r
+      name: State\r
+      type: string\r
+    - JSONPath: .spec.issuerRef.name\r
+      name: Issuer\r
+      type: string\r
+      priority: 1\r
+    - JSONPath: .status.reason\r
+      name: Reason\r
+      type: string\r
+      priority: 1\r
+    - JSONPath: .metadata.creationTimestamp\r
+      description: |-\r
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+      name: Age\r
+      type: date\r
+  group: certmanager.k8s.io\r
+  version: v1alpha1\r
+  names:\r
+    kind: Order\r
+    plural: orders\r
+  scope: Namespaced\r
+---\r
+apiVersion: apiextensions.k8s.io/v1beta1\r
+kind: CustomResourceDefinition\r
+metadata:\r
+  name: challenges.certmanager.k8s.io\r
+  labels:\r
+    app: certmanager\r
+    chart: certmanager\r
+    heritage: Tiller\r
+    release: istio\r
+  annotations:\r
+    "helm.sh/resource-policy": keep\r
+spec:\r
+  additionalPrinterColumns:\r
+    - JSONPath: .status.state\r
+      name: State\r
+      type: string\r
+    - JSONPath: .spec.dnsName\r
+      name: Domain\r
+      type: string\r
+    - JSONPath: .status.reason\r
+      name: Reason\r
+      type: string\r
+    - JSONPath: .metadata.creationTimestamp\r
+      description: |-\r
+        CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\r
+\r
+        Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\r
+      name: Age\r
+      type: date\r
+  group: certmanager.k8s.io\r
+  version: v1alpha1\r
+  names:\r
+    kind: Challenge\r
+    plural: challenges\r
+  scope: Namespaced\r
+---\r
+---\r
+# Source: istio/charts/kiali/templates/demosecret.yaml\r
+\r
+apiVersion: v1\r
+kind: Secret\r
+metadata:\r
+  name: kiali\r
+  namespace: istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+type: Opaque\r
+data:\r
+  username: YWRtaW4=   # admin\r
+  passphrase: YWRtaW4= # admin\r
+\r
+---\r
+# Source: istio/charts/galley/templates/configmap.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-galley-configuration\r
+  namespace: istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: galley\r
+data:\r
+  validatingwebhookconfiguration.yaml: |-    \r
+    apiVersion: admissionregistration.k8s.io/v1beta1\r
+    kind: ValidatingWebhookConfiguration\r
+    metadata:\r
+      name: istio-galley\r
+      labels:\r
+        app: galley\r
+        chart: galley\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: galley\r
+    webhooks:\r
+      - name: pilot.validation.istio.io\r
+        clientConfig:\r
+          service:\r
+            name: istio-galley\r
+            namespace: istio-system\r
+            path: "/admitpilot"\r
+          caBundle: ""\r
+        rules:\r
+          - operations:\r
+            - CREATE\r
+            - UPDATE\r
+            apiGroups:\r
+            - config.istio.io\r
+            apiVersions:\r
+            - v1alpha2\r
+            resources:\r
+            - httpapispecs\r
+            - httpapispecbindings\r
+            - quotaspecs\r
+            - quotaspecbindings\r
+          - operations:\r
+            - CREATE\r
+            - UPDATE\r
+            apiGroups:\r
+            - rbac.istio.io\r
+            apiVersions:\r
+            - "*"\r
+            resources:\r
+            - "*"\r
+          - operations:\r
+            - CREATE\r
+            - UPDATE\r
+            apiGroups:\r
+            - authentication.istio.io\r
+            apiVersions:\r
+            - "*"\r
+            resources:\r
+            - "*"\r
+          - operations:\r
+            - CREATE\r
+            - UPDATE\r
+            apiGroups:\r
+            - networking.istio.io\r
+            apiVersions:\r
+            - "*"\r
+            resources:\r
+            - destinationrules\r
+            - envoyfilters\r
+            - gateways\r
+            - serviceentries\r
+            - sidecars\r
+            - virtualservices\r
+        failurePolicy: Fail\r
+        sideEffects: None\r
+      - name: mixer.validation.istio.io\r
+        clientConfig:\r
+          service:\r
+            name: istio-galley\r
+            namespace: istio-system\r
+            path: "/admitmixer"\r
+          caBundle: ""\r
+        rules:\r
+          - operations:\r
+            - CREATE\r
+            - UPDATE\r
+            apiGroups:\r
+            - config.istio.io\r
+            apiVersions:\r
+            - v1alpha2\r
+            resources:\r
+            - rules\r
+            - attributemanifests\r
+            - circonuses\r
+            - deniers\r
+            - fluentds\r
+            - kubernetesenvs\r
+            - listcheckers\r
+            - memquotas\r
+            - noops\r
+            - opas\r
+            - prometheuses\r
+            - rbacs\r
+            - solarwindses\r
+            - stackdrivers\r
+            - cloudwatches\r
+            - dogstatsds\r
+            - statsds\r
+            - stdios\r
+            - apikeys\r
+            - authorizations\r
+            - checknothings\r
+            # - kuberneteses\r
+            - listentries\r
+            - logentries\r
+            - metrics\r
+            - quotas\r
+            - reportnothings\r
+            - tracespans\r
+            - adapters\r
+            - handlers\r
+            - instances\r
+            - templates\r
+            - zipkins\r
+        failurePolicy: Fail\r
+        sideEffects: None\r
+---\r
+# Source: istio/charts/grafana/templates/configmap-custom-resources.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-custom-resources\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  custom-resources.yaml: |-    \r
+    apiVersion: authentication.istio.io/v1alpha1\r
+    kind: Policy\r
+    metadata:\r
+      name: grafana-ports-mtls-disabled\r
+      namespace: istio-system\r
+      labels:\r
+        app: grafana\r
+        chart: grafana\r
+        heritage: Tiller\r
+        release: istio\r
+    spec:\r
+      targets:\r
+      - name: grafana\r
+        ports:\r
+        - number: 3000\r
+  run.sh: |-    \r
+    #!/bin/sh\r
+    \r
+    set -x\r
+    \r
+    if [ "$#" -ne "1" ]; then\r
+        echo "first argument should be path to custom resource yaml"\r
+        exit 1\r
+    fi\r
+    \r
+    pathToResourceYAML=${1}\r
+    \r
+    kubectl get validatingwebhookconfiguration istio-galley 2>/dev/null\r
+    if [ "$?" -eq 0 ]; then\r
+        echo "istio-galley validatingwebhookconfiguration found - waiting for istio-galley deployment to be ready"\r
+        while true; do\r
+            kubectl -n istio-system get deployment istio-galley 2>/dev/null\r
+            if [ "$?" -eq 0 ]; then\r
+                break\r
+            fi\r
+            sleep 1\r
+        done\r
+        kubectl -n istio-system rollout status deployment istio-galley\r
+        if [ "$?" -ne 0 ]; then\r
+            echo "istio-galley deployment rollout status check failed"\r
+            exit 1\r
+        fi\r
+        echo "istio-galley deployment ready for configuration validation"\r
+    fi\r
+    sleep 5\r
+    kubectl apply -f ${pathToResourceYAML}\r
+    \r
+\r
+---\r
+# Source: istio/charts/grafana/templates/configmap-dashboards.yaml\r
+\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-galley-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  galley-dashboard.json: '{\r
+  "__inputs": [\r
+    {\r
+      "name": "DS_PROMETHEUS",\r
+      "label": "Prometheus",\r
+      "description": "",\r
+      "type": "datasource",\r
+      "pluginId": "prometheus",\r
+      "pluginName": "Prometheus"\r
+    }\r
+  ],\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 0,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 5,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "id": 46,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(istio_build{component=\"galley\"}) by (tag)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ tag }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Galley Versions",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 5\r
+      },\r
+      "id": 40,\r
+      "panels": [],\r
+      "title": "Resource Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 6\r
+      },\r
+      "id": 36,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_virtual_memory_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Virtual Memory",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "process_resident_memory_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Resident Memory",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_sys_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap sys",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_alloc_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap alloc",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "go_memstats_alloc_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Alloc",\r
+          "refId": "F"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_inuse_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Heap in-use",\r
+          "refId": "G"\r
+        },\r
+        {\r
+          "expr": "go_memstats_stack_inuse_bytes{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Stack in-use",\r
+          "refId": "H"\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{container_name=~\"galley\", pod_name=~\"istio-galley-.*\"})",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Total (kis)",\r
+          "refId": "E"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 6\r
+      },\r
+      "id": 38,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"galley\", pod_name=~\"istio-galley-.*\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"galley\", pod_name=~\"istio-galley-.*\"}[1m])) by (container_name)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "irate(process_cpu_seconds_total{job=\"galley\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "galley (self-reported)",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "CPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 6\r
+      },\r
+      "id": 42,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_open_fds{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Open FDs (galley)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "container_fs_usage_bytes{container_name=~\"galley\", pod_name=~\"istio-galley-.*\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} ",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 6\r
+      },\r
+      "id": 44,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "go_goroutines{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "goroutines_total",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "galley_mcp_source_clients_total",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "clients_total",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "go_goroutines{job=\"galley\"}/galley_mcp_source_clients_total",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "avg_goroutines_per_client",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Goroutines",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 14\r
+      },\r
+      "id": 10,\r
+      "panels": [],\r
+      "title": "Runtime",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 15\r
+      },\r
+      "id": 2,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(galley_runtime_strategy_on_change_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Strategy Change Events",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(rate(galley_runtime_processor_events_processed_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Processed Events",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(rate(galley_runtime_processor_snapshots_published_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Snapshot Published",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Event Rates",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Events/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 15\r
+      },\r
+      "id": 4,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(galley_runtime_strategy_timer_max_time_reached_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Max Time Reached",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(rate(galley_runtime_strategy_timer_quiesce_reached_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Quiesce Reached",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(rate(galley_runtime_strategy_timer_resets_total[1m])) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Timer Resets",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Timer Rates",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Events/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 15\r
+      },\r
+      "id": 8,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 3,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": true,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum by (le) (galley_runtime_processor_snapshot_events_total_bucket))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P50",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum by (le) (galley_runtime_processor_snapshot_events_total_bucket))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P90",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum by (le) (galley_runtime_processor_snapshot_events_total_bucket))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P95",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum by (le) (galley_runtime_processor_snapshot_events_total_bucket))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P99",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Events Per Snapshot",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 21\r
+      },\r
+      "id": 6,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum by (typeURL) (galley_runtime_state_type_instances_total)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ typeURL }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "State Type Instances",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Count",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 27\r
+      },\r
+      "id": 34,\r
+      "panels": [],\r
+      "title": "Validation",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 28\r
+      },\r
+      "id": 28,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "galley_validation_cert_key_updates{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Key Updates",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "galley_validation_cert_key_update_errors{job=\"galley\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Key Update Errors: {{ error }}",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Validation Webhook Certificate",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 28\r
+      },\r
+      "id": 30,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(galley_validation_passed{job=\"galley\"}) by (group, version, resource)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Passed: {{ group }}/{{ version }}/{{resource}}",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(galley_validation_failed{job=\"galley\"}) by (group, version, resource, reason)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Failed: {{ group }}/{{ version }}/{{resource}} ({{ reason}})",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Resource Validation",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 28\r
+      },\r
+      "id": 32,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(galley_validation_http_error{job=\"galley\"}) by (status)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ status }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Validation HTTP Errors",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 34\r
+      },\r
+      "id": 12,\r
+      "panels": [],\r
+      "title": "Kubernetes Source",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 35\r
+      },\r
+      "id": 14,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "rate(galley_source_kube_event_success_total[1m]) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Success",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "rate(galley_source_kube_event_error_total[1m]) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Error",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Source Event Rate",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Events/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 35\r
+      },\r
+      "id": 16,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "rate(galley_source_kube_dynamic_converter_success_total[1m]) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{apiVersion=\"{{apiVersion}}\",group=\"{{group}}\",kind=\"{{kind}}\"}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Kubernetes Object Conversion Successes",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Conversions/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 35\r
+      },\r
+      "id": 24,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "rate(galley_source_kube_dynamic_converter_failure_total[1m]) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Error",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Kubernetes Object Conversion Failures",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "Failures/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 41\r
+      },\r
+      "id": 18,\r
+      "panels": [],\r
+      "title": "Mesh Configuration Protocol",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 42\r
+      },\r
+      "id": 20,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(galley_mcp_source_clients_total)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Clients",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Connected Clients",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 42\r
+      },\r
+      "id": 22,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum by(collection)(irate(galley_mcp_source_request_acks_total[1m]) * 60)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Request ACKs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "ACKs/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 42\r
+      },\r
+      "id": 26,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "rate(galley_mcp_source_request_nacks_total[1m]) * 60",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Request NACKs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "NACKs/min",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "5s",\r
+  "schemaVersion": 16,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": []\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "",\r
+  "title": "Istio Galley Dashboard",\r
+  "uid": "TSEY6jLmk",\r
+  "version": 1\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-istio-mesh-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  istio-mesh-dashboard.json: '{\r
+  "__inputs": [\r
+    {\r
+      "name": "DS_PROMETHEUS",\r
+      "label": "Prometheus",\r
+      "description": "",\r
+      "type": "datasource",\r
+      "pluginId": "prometheus",\r
+      "pluginName": "Prometheus"\r
+    }\r
+  ],\r
+  "__requires": [\r
+    {\r
+      "type": "grafana",\r
+      "id": "grafana",\r
+      "name": "Grafana",\r
+      "version": "5.2.3"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "graph",\r
+      "name": "Graph",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "datasource",\r
+      "id": "prometheus",\r
+      "name": "Prometheus",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "singlestat",\r
+      "name": "Singlestat",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "table",\r
+      "name": "Table",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "text",\r
+      "name": "Text",\r
+      "version": "5.0.0"\r
+    }\r
+  ],\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 0,\r
+  "id": null,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "content": "<div>\n  <div style=\"position: absolute; bottom: 0\">\n    <a href=\"https://istio.io\" target=\"_blank\" style=\"font-size: 30px; text-decoration: none; color: inherit\"><img src=\"https://istio.io/img/istio-logo.svg\" style=\"height: 50px\"> Istio</a>\n  </div>\n  <div style=\"position: absolute; bottom: 0; right: 0; font-size: 15px\">\n    Istio is an <a href=\"https://github.com/istio/istio\" target=\"_blank\">open platform</a> that provides a uniform way to connect,\n    <a href=\"https://istio.io/docs/concepts/traffic-management/overview.html\" target=\"_blank\">manage</a>, and \n    <a href=\"https://istio.io/docs/concepts/network-and-auth/auth.html\" target=\"_blank\">secure</a> microservices.\n    <br>\n    Need help? Join the <a href=\"https://istio.io/community/\" target=\"_blank\">Istio community</a>.\n  </div>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "height": "50px",\r
+      "id": 13,\r
+      "links": [],\r
+      "mode": "html",\r
+      "style": {\r
+        "font-size": "18pt"\r
+      },\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 3\r
+      },\r
+      "id": 20,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{reporter=\"destination\"}[1m])), 0.001)",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "Global Request Volume",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "percentunit",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 80,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": false\r
+      },\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 3\r
+      },\r
+      "id": 21,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(istio_requests_total{reporter=\"destination\", response_code!~\"5.*\"}[1m])) / sum(rate(istio_requests_total{reporter=\"destination\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "95, 99, 99.5",\r
+      "title": "Global Success Rate (non-5xx responses)",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 3\r
+      },\r
+      "id": 22,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", response_code=~\"4.*\"}[1m])) ",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "4xxs",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 3\r
+      },\r
+      "id": 23,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", response_code=~\"5.*\"}[1m])) ",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "5xxs",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "columns": [],\r
+      "datasource": "Prometheus",\r
+      "fontSize": "100%",\r
+      "gridPos": {\r
+        "h": 21,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 6\r
+      },\r
+      "hideTimeOverride": false,\r
+      "id": 73,\r
+      "links": [],\r
+      "pageSize": null,\r
+      "repeat": null,\r
+      "repeatDirection": "v",\r
+      "scroll": true,\r
+      "showHeader": true,\r
+      "sort": {\r
+        "col": 4,\r
+        "desc": true\r
+      },\r
+      "styles": [\r
+        {\r
+          "alias": "Workload",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": false,\r
+          "linkTargetBlank": false,\r
+          "linkTooltip": "Workload dashboard",\r
+          "linkUrl": "/dashboard/db/istio-workload-dashboard?var-namespace=$__cell_2&var-workload=$__cell_",\r
+          "pattern": "destination_workload",\r
+          "preserveFormat": false,\r
+          "sanitize": false,\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Time",\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "Requests",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #A",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "ops"\r
+        },\r
+        {\r
+          "alias": "P50 Latency",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #B",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "s"\r
+        },\r
+        {\r
+          "alias": "P90 Latency",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #D",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "s"\r
+        },\r
+        {\r
+          "alias": "P99 Latency",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #E",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "s"\r
+        },\r
+        {\r
+          "alias": "Success Rate",\r
+          "colorMode": "cell",\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #F",\r
+          "thresholds": [\r
+            ".95",\r
+            " 1.00"\r
+          ],\r
+          "type": "number",\r
+          "unit": "percentunit"\r
+        },\r
+        {\r
+          "alias": "Workload",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": true,\r
+          "linkTooltip": "$__cell dashboard",\r
+          "linkUrl": "/dashboard/db/istio-workload-dashboard?var-workload=$__cell_2&var-namespace=$__cell_3",\r
+          "pattern": "destination_workload_var",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "Service",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": true,\r
+          "linkTooltip": "$__cell dashboard",\r
+          "linkUrl": "/dashboard/db/istio-service-dashboard?var-service=$__cell",\r
+          "pattern": "destination_service",\r
+          "thresholds": [],\r
+          "type": "string",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "destination_workload_namespace",\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        }\r
+      ],\r
+      "targets": [\r
+        {\r
+          "expr": "label_join(sum(rate(istio_requests_total{reporter=\"destination\", response_code=\"200\"}[1m])) by (destination_workload, destination_workload_namespace, destination_service), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload}}.{{ destination_workload_namespace }}",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "label_join(histogram_quantile(0.50, sum(rate(istio_request_duration_seconds_bucket{reporter=\"destination\"}[1m])) by (le, destination_workload, destination_workload_namespace)), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload}}.{{ destination_workload_namespace }}",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "label_join(histogram_quantile(0.90, sum(rate(istio_request_duration_seconds_bucket{reporter=\"destination\"}[1m])) by (le, destination_workload, destination_workload_namespace)), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "label_join(histogram_quantile(0.99, sum(rate(istio_request_duration_seconds_bucket{reporter=\"destination\"}[1m])) by (le, destination_workload, destination_workload_namespace)), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}",\r
+          "refId": "E"\r
+        },\r
+        {\r
+          "expr": "label_join((sum(rate(istio_requests_total{reporter=\"destination\", response_code!~\"5.*\"}[1m])) by (destination_workload, destination_workload_namespace) / sum(rate(istio_requests_total{reporter=\"destination\"}[1m])) by (destination_workload, destination_workload_namespace)), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "interval": "",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}",\r
+          "refId": "F"\r
+        }\r
+      ],\r
+      "timeFrom": null,\r
+      "title": "HTTP/GRPC Workloads",\r
+      "transform": "table",\r
+      "transparent": false,\r
+      "type": "table"\r
+    },\r
+    {\r
+      "columns": [],\r
+      "datasource": "Prometheus",\r
+      "fontSize": "100%",\r
+      "gridPos": {\r
+        "h": 18,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 27\r
+      },\r
+      "hideTimeOverride": false,\r
+      "id": 109,\r
+      "links": [],\r
+      "pageSize": null,\r
+      "repeatDirection": "v",\r
+      "scroll": true,\r
+      "showHeader": true,\r
+      "sort": {\r
+        "col": 2,\r
+        "desc": true\r
+      },\r
+      "styles": [\r
+        {\r
+          "alias": "Workload",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": false,\r
+          "linkTargetBlank": false,\r
+          "linkTooltip": "$__cell dashboard",\r
+          "linkUrl": "/dashboard/db/istio-tcp-workload-dashboard?var-namespace=$__cell_2&&var-workload=$__cell",\r
+          "pattern": "destination_workload",\r
+          "preserveFormat": false,\r
+          "sanitize": false,\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "Bytes Sent",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #A",\r
+          "thresholds": [\r
+            ""\r
+          ],\r
+          "type": "number",\r
+          "unit": "Bps"\r
+        },\r
+        {\r
+          "alias": "Bytes Received",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Value #C",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "Bps"\r
+        },\r
+        {\r
+          "alias": "",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "Time",\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "Workload",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": true,\r
+          "linkTooltip": "$__cell dashboard",\r
+          "linkUrl": "/dashboard/db/istio-workload-dashboard?var-namespace=$__cell_3&var-workload=$__cell_2",\r
+          "pattern": "destination_workload_var",\r
+          "thresholds": [],\r
+          "type": "string",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "pattern": "destination_workload_namespace",\r
+          "thresholds": [],\r
+          "type": "hidden",\r
+          "unit": "short"\r
+        },\r
+        {\r
+          "alias": "Service",\r
+          "colorMode": null,\r
+          "colors": [\r
+            "rgba(245, 54, 54, 0.9)",\r
+            "rgba(237, 129, 40, 0.89)",\r
+            "rgba(50, 172, 45, 0.97)"\r
+          ],\r
+          "dateFormat": "YYYY-MM-DD HH:mm:ss",\r
+          "decimals": 2,\r
+          "link": true,\r
+          "linkTooltip": "$__cell dashboard",\r
+          "linkUrl": "/dashboard/db/istio-service-dashboard?var-service=$__cell",\r
+          "pattern": "destination_service",\r
+          "thresholds": [],\r
+          "type": "number",\r
+          "unit": "short"\r
+        }\r
+      ],\r
+      "targets": [\r
+        {\r
+          "expr": "label_join(sum(rate(istio_tcp_received_bytes_total{reporter=\"source\"}[1m])) by (destination_workload, destination_workload_namespace, destination_service), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "label_join(sum(rate(istio_tcp_sent_bytes_total{reporter=\"source\"}[1m])) by (destination_workload, destination_workload_namespace, destination_service), \"destination_workload_var\", \".\", \"destination_workload\", \"destination_workload_namespace\")",\r
+          "format": "table",\r
+          "hide": false,\r
+          "instant": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "timeFrom": null,\r
+      "title": "TCP Workloads",\r
+      "transform": "table",\r
+      "transparent": false,\r
+      "type": "table"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 9,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 45\r
+      },\r
+      "id": 111,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(istio_build) by (component, tag)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ component }}: {{ tag }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Istio Components by Version",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "transparent": false,\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "5s",\r
+  "schemaVersion": 16,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": []\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "browser",\r
+  "title": "Istio Mesh Dashboard",\r
+  "version": 4\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-istio-performance-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  istio-performance-dashboard.json: '{\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 0,\r
+  "id": 9,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "collapsed": true,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "id": 21,\r
+      "panels": [\r
+        {\r
+          "content": "The charts on this dashboard are intended to show Istio main components cost in terms resources utilization under steady load.\n\n- **vCPU/1k rps:** shows vCPU utilization by the main Istio components normalized by 1000 requests/second. When idle or low traffic, this chart will be blank. The curve for istio-proxy refers to the services sidecars only.\n- **vCPU:** vCPU utilization by Istio components, not normalized.\n- **Memory:** memory footprint for the components. Telemetry and policy are normalized by 1k rps, and no data is shown  when there is no traffic. For ingress and istio-proxy, the data is per instance.\n- **Bytes transferred/ sec:** shows the number of bytes flowing through each Istio component.\n\n\n",\r
+          "gridPos": {\r
+            "h": 6,\r
+            "w": 24,\r
+            "x": 0,\r
+            "y": 1\r
+          },\r
+          "id": 19,\r
+          "links": [],\r
+          "mode": "markdown",\r
+          "timeFrom": null,\r
+          "timeShift": null,\r
+          "title": "Performance Dashboard README",\r
+          "transparent": true,\r
+          "type": "text"\r
+        }\r
+      ],\r
+      "title": "Performance Dashboard Notes",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 1\r
+      },\r
+      "id": 6,\r
+      "panels": [],\r
+      "title": "vCPU Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 2\r
+      },\r
+      "id": 4,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 2,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "(sum(irate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-telemetry-.*\",container_name=~\"mixer|istio-proxy\"}[1m]))/ (round(sum(irate(istio_requests_total[1m])), 0.001)/1000))/ (sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\"}[1m])) >bool 10)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-telemetry",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "(sum(irate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-ingressgateway-.*\",container_name=\"istio-proxy\"}[1m])) / (round(sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\", reporter=\"source\"}[1m])), 0.001)/1000))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-ingressgateway",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "(sum(irate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",namespace!=\"istio-system\",container_name=\"istio-proxy\"}[1m]))/ (round(sum(irate(istio_requests_total[1m])), 0.001)/1000))/ (sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\"}[1m])) >bool 10)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-proxy",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "(sum(irate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-policy-.*\",container_name=~\"mixer|istio-proxy\"}[1m]))/ (round(sum(irate(istio_requests_total[1m])), 0.001)/1000))/ (sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\"}[1m])) >bool 10)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-policy",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "vCPU / 1k rps",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 2\r
+      },\r
+      "id": 7,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 2,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-telemetry-.*\",container_name=~\"mixer|istio-proxy\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-telemetry",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-ingressgateway-.*\",container_name=\"istio-proxy\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-ingressgateway",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",namespace!=\"istio-system\",container_name=\"istio-proxy\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-proxy",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",pod_name=~\"istio-policy-.*\",container_name=~\"mixer|istio-proxy\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-policy",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "vCPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 10\r
+      },\r
+      "id": 13,\r
+      "panels": [],\r
+      "title": "Memory and Data Rates",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 11\r
+      },\r
+      "id": 902,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 2,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "(sum(container_memory_usage_bytes{pod_name=~\"istio-telemetry-.*\"}) / (sum(irate(istio_requests_total[1m])) / 1000)) / (sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\"}[1m])) >bool 10)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-telemetry / 1k rps",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{pod_name=~\"istio-ingressgateway-.*\"}) / count(container_memory_usage_bytes{pod_name=~\"istio-ingressgateway-.*\",container_name!=\"POD\"})",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "per istio-ingressgateway",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{namespace!=\"istio-system\",container_name=\"istio-proxy\"}) / count(container_memory_usage_bytes{namespace!=\"istio-system\",container_name=\"istio-proxy\"})",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "per istio proxy",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "(sum(container_memory_usage_bytes{pod_name=~\"istio-policy-.*\"}) / (sum(irate(istio_requests_total[1m])) / 1000))/ (sum(irate(istio_requests_total{source_workload=\"istio-ingressgateway\"}[1m])) >bool 10)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-policy / 1k rps",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory Usage",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 11\r
+      },\r
+      "id": 11,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 2,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_response_bytes_sum{destination_workload=\"istio-telemetry\"}[1m])) + sum(irate(istio_request_bytes_sum{destination_workload=\"istio-telemetry\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-telemetry",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_response_bytes_sum{source_workload=\"istio-ingressgateway\", reporter=\"source\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-ingressgateway",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_response_bytes_sum{source_workload_namespace!=\"istio-system\", reporter=\"source\"}[1m])) + sum(irate(istio_response_bytes_sum{destination_workload_namespace!=\"istio-system\", reporter=\"destination\"}[1m])) + sum(irate(istio_request_bytes_sum{source_workload_namespace!=\"istio-system\", reporter=\"source\"}[1m])) + sum(irate(istio_request_bytes_sum{destination_workload_namespace!=\"istio-system\", reporter=\"destination\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio-proxy",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_response_bytes_sum{destination_workload=\"istio-policy\"}[1m])) + sum(irate(istio_request_bytes_sum{destination_workload=\"istio-policy\"}[1m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "istio_policy",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Bytes transferred / sec",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 19\r
+      },\r
+      "id": 17,\r
+      "panels": [],\r
+      "title": "Istio Component Versions",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 20\r
+      },\r
+      "id": 15,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 2,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(istio_build) by (component, tag)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ component }}: {{ tag }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Istio Components by Version",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 31\r
+      },\r
+      "id": 71,\r
+      "panels": [],\r
+      "title": "Proxy Resource Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 32\r
+      },\r
+      "id": 72,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{container_name=\"istio-proxy\"})",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 32\r
+      },\r
+      "id": 73,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=\"istio-proxy\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "vCPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 32\r
+      },\r
+      "id": 702,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(container_fs_usage_bytes{container_name=\"istio-proxy\"})",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "decimals": null,\r
+          "format": "none",\r
+          "label": "",\r
+          "logBase": 1024,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 39\r
+      },\r
+      "id": 69,\r
+      "panels": [],\r
+      "title": "Pilot Resource Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 40\r
+      },\r
+      "id": 5,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_virtual_memory_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Virtual Memory",\r
+          "refId": "I",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "process_resident_memory_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Resident Memory",\r
+          "refId": "H",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_sys_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap sys",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_alloc_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap alloc",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "go_memstats_alloc_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Alloc",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_inuse_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Heap in-use",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_stack_inuse_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Stack in-use",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"})",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "container_memory_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 40\r
+      },\r
+      "id": 602,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}[1m])) by (container_name)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "irate(process_cpu_seconds_total{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "pilot (self-reported)",\r
+          "refId": "C",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "vCPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 40\r
+      },\r
+      "id": 74,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_open_fds{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "instant": false,\r
+          "interval": "",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Open FDs (pilot)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "container_fs_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "decimals": null,\r
+          "format": "none",\r
+          "label": "",\r
+          "logBase": 1024,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 40\r
+      },\r
+      "id": 402,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": false,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "go_goroutines{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Number of Goroutines",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Goroutines",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 47\r
+      },\r
+      "id": 93,\r
+      "panels": [],\r
+      "title": "Mixer Resource Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 48\r
+      },\r
+      "id": 94,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_virtual_memory_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Virtual Memory",\r
+          "refId": "I",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "process_resident_memory_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Resident Memory",\r
+          "refId": "H",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_sys_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap sys",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_alloc_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap alloc",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "go_memstats_alloc_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Alloc",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_inuse_bytes{job=~\"istio-telemetry|istio-policy\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Heap in-use",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_stack_inuse_bytes{job=~\"istio-policy|istio-telemetry\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Stack in-use",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*\"})",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "container_memory_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 48\r
+      },\r
+      "id": 95,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*\"}[1m])) by (container_name)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "irate(process_cpu_seconds_total{job=~\"istio-policy|istio-telemetry\"}[1m])",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "mixer (self-reported)",\r
+          "refId": "C",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "vCPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 48\r
+      },\r
+      "id": 96,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_open_fds{job=~\"istio-policy|istio-telemetry\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "instant": false,\r
+          "interval": "",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Open FDs (pilot)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "container_fs_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "decimals": null,\r
+          "format": "none",\r
+          "label": "",\r
+          "logBase": 1024,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 48\r
+      },\r
+      "id": 97,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": false,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "go_goroutines{job=\"istio-telemetry\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Number of Goroutines",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Goroutines",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "10s",\r
+  "schemaVersion": 18,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": []\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "",\r
+  "title": "Istio Performance Dashboard",\r
+  "uid": "vu8e0VWZk",\r
+  "version": 22\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-istio-service-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  istio-service-dashboard.json: '{\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 0,\r
+  "iteration": 1536442501501,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>SERVICE: $service</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "id": 89,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 3\r
+      },\r
+      "id": 12,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{reporter=\"source\",destination_service=~\"$service\"}[5m])), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "Client Request Volume",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "current"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(50, 172, 45, 0.97)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(245, 54, 54, 0.9)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "decimals": null,\r
+      "format": "percentunit",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 80,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": false\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 3\r
+      },\r
+      "id": 14,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"source\",destination_service=~\"$service\",response_code!~\"5.*\"}[5m])) / sum(irate(istio_requests_total{reporter=\"source\",destination_service=~\"$service\"}[5m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": "95, 99, 99.5",\r
+      "title": "Client Success Rate (non-5xx responses)",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 3\r
+      },\r
+      "id": 87,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": false,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": true,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "interval": "",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P50",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P90",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P99",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Client Request Duration",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "#299c46",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "#d44a3a"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "Bps",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 3\r
+      },\r
+      "id": 84,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", destination_service=~\"$service\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "TCP Received Bytes",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 7\r
+      },\r
+      "id": 97,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{reporter=\"destination\",destination_service=~\"$service\"}[5m])), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "Server Request Volume",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "current"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(50, 172, 45, 0.97)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(245, 54, 54, 0.9)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "decimals": null,\r
+      "format": "percentunit",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 80,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": false\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 7\r
+      },\r
+      "id": 98,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\",destination_service=~\"$service\",response_code!~\"5.*\"}[5m])) / sum(irate(istio_requests_total{reporter=\"destination\",destination_service=~\"$service\"}[5m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": "95, 99, 99.5",\r
+      "title": "Server Success Rate (non-5xx responses)",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 7\r
+      },\r
+      "id": 99,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": false,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": true,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "interval": "",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P50",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P90",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_service=~\"$service\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P99",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Server Request Duration",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "#299c46",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "#d44a3a"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "Bps",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 7\r
+      },\r
+      "id": 100,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_tcp_sent_bytes_total{reporter=\"source\", destination_service=~\"$service\"}[1m])) ",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "TCP Sent Bytes",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>CLIENT WORKLOADS</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 11\r
+      },\r
+      "id": 45,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 0,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 14\r
+      },\r
+      "id": 25,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null as zero",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy=\"mutual_tls\",destination_service=~\"$service\",reporter=\"source\",source_workload=~\"$srcwl\",source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} : {{ response_code }} (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", reporter=\"source\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} : {{ response_code }}",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Requests by Source And Response Code",\r
+      "tooltip": {\r
+        "shared": false,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": [\r
+          "total"\r
+        ]\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 14\r
+      },\r
+      "id": 26,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\",response_code!~\"5.*\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace) / sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\",response_code!~\"5.*\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace) / sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Success Rate (non-5xx responses) By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "percentunit",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": "1.01",\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "description": "",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 20\r
+      },\r
+      "id": 27,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Duration by Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 20\r
+      },\r
+      "id": 28,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 20\r
+      },\r
+      "id": 68,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Response Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 26\r
+      },\r
+      "id": 80,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Received from Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 26\r
+      },\r
+      "id": 82,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy=\"mutual_tls\", reporter=\"source\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy!=\"mutual_tls\", reporter=\"source\", destination_service=~\"$service\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Sent to Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>SERVICE WORKLOADS</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 32\r
+      },\r
+      "id": 69,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 0,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 35\r
+      },\r
+      "id": 90,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null as zero",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy=\"mutual_tls\",destination_service=~\"$service\",reporter=\"destination\",destination_workload=~\"$dstwl\",destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} : {{ response_code }} (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", reporter=\"destination\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} : {{ response_code }}",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Requests by Destination And Response Code",\r
+      "tooltip": {\r
+        "shared": false,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": [\r
+          "total"\r
+        ]\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 35\r
+      },\r
+      "id": 91,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\",response_code!~\"5.*\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace) / sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\",response_code!~\"5.*\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace) / sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[5m])) by (destination_workload, destination_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Success Rate (non-5xx responses) By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "percentunit",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": "1.01",\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "description": "",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 41\r
+      },\r
+      "id": 94,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Duration by Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 41\r
+      },\r
+      "id": 95,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 41\r
+      },\r
+      "id": 96,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Response Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 47\r
+      },\r
+      "id": 92,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace}} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{ destination_workload_namespace}}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Received from Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 47\r
+      },\r
+      "id": 93,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy=\"mutual_tls\", reporter=\"source\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{destination_workload_namespace }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy!=\"mutual_tls\", reporter=\"source\", destination_service=~\"$service\", destination_workload=~\"$dstwl\", destination_workload_namespace=~\"$dstns\"}[1m])) by (destination_workload, destination_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_workload }}.{{destination_workload_namespace }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Sent to Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "10s",\r
+  "schemaVersion": 16,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": [\r
+      {\r
+        "allValue": null,\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": false,\r
+        "label": "Service",\r
+        "multi": false,\r
+        "name": "service",\r
+        "options": [],\r
+        "query": "label_values(destination_service)",\r
+        "refresh": 1,\r
+        "regex": "",\r
+        "sort": 0,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {\r
+          "text": "All",\r
+          "value": "$__all"\r
+        },\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Client Workload Namespace",\r
+        "multi": true,\r
+        "name": "srcns",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_service=\"$service\"}) by (source_workload_namespace) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_service=~\"$service\"}) by (source_workload_namespace))",\r
+        "refresh": 1,\r
+        "regex": "/.*namespace=\"([^\"]*).*/",\r
+        "sort": 2,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {\r
+          "text": "All",\r
+          "value": "$__all"\r
+        },\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Client Workload",\r
+        "multi": true,\r
+        "name": "srcwl",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_service=~\"$service\", source_workload_namespace=~\"$srcns\"}) by (source_workload) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_service=~\"$service\", source_workload_namespace=~\"$srcns\"}) by (source_workload))",\r
+        "refresh": 1,\r
+        "regex": "/.*workload=\"([^\"]*).*/",\r
+        "sort": 3,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {\r
+          "text": "All",\r
+          "value": "$__all"\r
+        },\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Service Workload Namespace",\r
+        "multi": true,\r
+        "name": "dstns",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_service=\"$service\"}) by (destination_workload_namespace) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_service=~\"$service\"}) by (destination_workload_namespace))",\r
+        "refresh": 1,\r
+        "regex": "/.*namespace=\"([^\"]*).*/",\r
+        "sort": 2,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {\r
+          "text": "All",\r
+          "value": "$__all"\r
+        },\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Service Workload",\r
+        "multi": true,\r
+        "name": "dstwl",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_service=~\"$service\", destination_workload_namespace=~\"$dstns\"}) by (destination_workload) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_service=~\"$service\", destination_workload_namespace=~\"$dstns\"}) by (destination_workload))",\r
+        "refresh": 1,\r
+        "regex": "/.*workload=\"([^\"]*).*/",\r
+        "sort": 3,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      }\r
+    ]\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "",\r
+  "title": "Istio Service Dashboard",\r
+  "uid": "LJ_uJAvmk",\r
+  "version": 1\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-istio-workload-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  istio-workload-dashboard.json: '{\r
+  "__inputs": [\r
+    {\r
+      "name": "DS_PROMETHEUS",\r
+      "label": "Prometheus",\r
+      "description": "",\r
+      "type": "datasource",\r
+      "pluginId": "prometheus",\r
+      "pluginName": "Prometheus"\r
+    }\r
+  ],\r
+  "__requires": [\r
+    {\r
+      "type": "grafana",\r
+      "id": "grafana",\r
+      "name": "Grafana",\r
+      "version": "5.0.4"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "graph",\r
+      "name": "Graph",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "datasource",\r
+      "id": "prometheus",\r
+      "name": "Prometheus",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "singlestat",\r
+      "name": "Singlestat",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "text",\r
+      "name": "Text",\r
+      "version": "5.0.0"\r
+    }\r
+  ],\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 0,\r
+  "id": null,\r
+  "iteration": 1531345461465,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>WORKLOAD: $workload.$namespace</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "id": 89,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(245, 54, 54, 0.9)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(50, 172, 45, 0.97)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "ops",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 3\r
+      },\r
+      "id": 12,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{reporter=\"destination\",destination_workload_namespace=~\"$namespace\",destination_workload=~\"$workload\"}[5m])), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "A",\r
+          "step": 4\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "Incoming Request Volume",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "current"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "rgba(50, 172, 45, 0.97)",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "rgba(245, 54, 54, 0.9)"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "decimals": null,\r
+      "format": "percentunit",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 80,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": false\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 3\r
+      },\r
+      "id": 14,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\",destination_workload_namespace=~\"$namespace\",destination_workload=~\"$workload\",response_code!~\"5.*\"}[5m])) / sum(irate(istio_requests_total{reporter=\"destination\",destination_workload_namespace=~\"$namespace\",destination_workload=~\"$workload\"}[5m]))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": "95, 99, 99.5",\r
+      "title": "Incoming Success Rate (non-5xx responses)",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 3\r
+      },\r
+      "id": 87,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": false,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": true,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "interval": "",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P50",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P90",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\",destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\"}[1m])) by (le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "P99",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Request Duration",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "#299c46",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "#d44a3a"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "Bps",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 7\r
+      },\r
+      "id": 84,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\"}[1m])) + sum(irate(istio_tcp_received_bytes_total{reporter=\"destination\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "TCP Server Traffic",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "cacheTimeout": null,\r
+      "colorBackground": false,\r
+      "colorValue": false,\r
+      "colors": [\r
+        "#299c46",\r
+        "rgba(237, 129, 40, 0.89)",\r
+        "#d44a3a"\r
+      ],\r
+      "datasource": "Prometheus",\r
+      "format": "Bps",\r
+      "gauge": {\r
+        "maxValue": 100,\r
+        "minValue": 0,\r
+        "show": false,\r
+        "thresholdLabels": false,\r
+        "thresholdMarkers": true\r
+      },\r
+      "gridPos": {\r
+        "h": 4,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 7\r
+      },\r
+      "id": 85,\r
+      "interval": null,\r
+      "links": [],\r
+      "mappingType": 1,\r
+      "mappingTypes": [\r
+        {\r
+          "name": "value to text",\r
+          "value": 1\r
+        },\r
+        {\r
+          "name": "range to text",\r
+          "value": 2\r
+        }\r
+      ],\r
+      "maxDataPoints": 100,\r
+      "nullPointMode": "connected",\r
+      "nullText": null,\r
+      "postfix": "",\r
+      "postfixFontSize": "50%",\r
+      "prefix": "",\r
+      "prefixFontSize": "50%",\r
+      "rangeMaps": [\r
+        {\r
+          "from": "null",\r
+          "text": "N/A",\r
+          "to": "null"\r
+        }\r
+      ],\r
+      "sparkline": {\r
+        "fillColor": "rgba(31, 118, 189, 0.18)",\r
+        "full": true,\r
+        "lineColor": "rgb(31, 120, 193)",\r
+        "show": true\r
+      },\r
+      "tableColumn": "",\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_tcp_sent_bytes_total{reporter=\"source\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\"}[1m])) + sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": "",\r
+      "title": "TCP Client Traffic",\r
+      "transparent": false,\r
+      "type": "singlestat",\r
+      "valueFontSize": "80%",\r
+      "valueMaps": [\r
+        {\r
+          "op": "=",\r
+          "text": "N/A",\r
+          "value": "null"\r
+        }\r
+      ],\r
+      "valueName": "avg"\r
+    },\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>INBOUND WORKLOADS</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 11\r
+      },\r
+      "id": 45,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 0,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 14\r
+      },\r
+      "id": 25,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null as zero",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", reporter=\"destination\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} : {{ response_code }} (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy!=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", reporter=\"destination\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} : {{ response_code }}",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Requests by Source And Response Code",\r
+      "tooltip": {\r
+        "shared": false,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": [\r
+          "total"\r
+        ]\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 14\r
+      },\r
+      "id": 26,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\",response_code!~\"5.*\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace) / sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\",response_code!~\"5.*\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace) / sum(irate(istio_requests_total{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[5m])) by (source_workload, source_workload_namespace)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Success Rate (non-5xx responses) By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "percentunit",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": "1.01",\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "description": "",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 20\r
+      },\r
+      "id": 27,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Duration by Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 20\r
+      },\r
+      "id": 28,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Request Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 20\r
+      },\r
+      "id": 68,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload=~\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{source_workload}}.{{source_workload_namespace}} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Response Size By Source",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 26\r
+      },\r
+      "id": 80,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"destination\", connection_security_policy=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"destination\", connection_security_policy!=\"mutual_tls\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Received from Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 26\r
+      },\r
+      "id": 82,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy=\"mutual_tls\", reporter=\"destination\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy!=\"mutual_tls\", reporter=\"destination\", destination_workload_namespace=~\"$namespace\", destination_workload=~\"$workload\", source_workload=~\"$srcwl\", source_workload_namespace=~\"$srcns\"}[1m])) by (source_workload, source_workload_namespace), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ source_workload }}.{{ source_workload_namespace}}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Sent to Incoming TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "content": "<div class=\"dashboard-header text-center\">\n<span>OUTBOUND SERVICES</span>\n</div>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 32\r
+      },\r
+      "id": 69,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 0,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 35\r
+      },\r
+      "id": 70,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null as zero",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", reporter=\"source\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} : {{ response_code }} (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_requests_total{connection_security_policy!=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", reporter=\"source\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service, response_code), 0.001)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} : {{ response_code }}",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Outgoing Requests by Destination And Response Code",\r
+      "tooltip": {\r
+        "shared": false,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": [\r
+          "total"\r
+        ]\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 35\r
+      },\r
+      "id": 71,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\",response_code!~\"5.*\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service) / sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\",response_code!~\"5.*\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service) / sum(irate(istio_requests_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[5m])) by (destination_service)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{destination_service }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Outgoing Success Rate (non-5xx responses) By Destination",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "percentunit",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": "1.01",\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "description": "",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 41\r
+      },\r
+      "id": 72,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "hideZero": false,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_duration_seconds_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Outgoing Request Duration by Destination",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 41\r
+      },\r
+      "id": 73,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_request_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Outgoing Request Size By Destination",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 41\r
+      },\r
+      "id": 74,\r
+      "legend": {\r
+        "alignAsTable": false,\r
+        "avg": false,\r
+        "current": false,\r
+        "hideEmpty": true,\r
+        "max": false,\r
+        "min": false,\r
+        "rightSide": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50 (🔐mTLS)",\r
+          "refId": "D",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90 (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95 (🔐mTLS)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }}  P99 (🔐mTLS)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.50, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P50",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.90, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P90",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.95, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P95",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(istio_response_bytes_bucket{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service, le))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} P99",\r
+          "refId": "H",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Response Size By Destination",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "decbytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 47\r
+      },\r
+      "id": 76,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy=\"mutual_tls\", reporter=\"source\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_sent_bytes_total{connection_security_policy!=\"mutual_tls\", reporter=\"source\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Sent on Outgoing TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ]\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 47\r
+      },\r
+      "id": 78,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }} (🔐mTLS)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "round(sum(irate(istio_tcp_received_bytes_total{reporter=\"source\", connection_security_policy!=\"mutual_tls\", source_workload_namespace=~\"$namespace\", source_workload=~\"$workload\", destination_service=~\"$dstsvc\"}[1m])) by (destination_service), 0.001)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ destination_service }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Bytes Received from Outgoing TCP Connection",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "Bps",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ]\r
+    }\r
+  ],\r
+  "refresh": "10s",\r
+  "schemaVersion": 16,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": [\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": false,\r
+        "label": "Namespace",\r
+        "multi": false,\r
+        "name": "namespace",\r
+        "options": [],\r
+        "query": "query_result(sum(istio_requests_total) by (destination_workload_namespace) or sum(istio_tcp_sent_bytes_total) by (destination_workload_namespace))",\r
+        "refresh": 1,\r
+        "regex": "/.*_namespace=\"([^\"]*).*/",\r
+        "sort": 0,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": false,\r
+        "label": "Workload",\r
+        "multi": false,\r
+        "name": "workload",\r
+        "options": [],\r
+        "query": "query_result((sum(istio_requests_total{destination_workload_namespace=~\"$namespace\"}) by (destination_workload) or sum(istio_requests_total{source_workload_namespace=~\"$namespace\"}) by (source_workload)) or (sum(istio_tcp_sent_bytes_total{destination_workload_namespace=~\"$namespace\"}) by (destination_workload) or sum(istio_tcp_sent_bytes_total{source_workload_namespace=~\"$namespace\"}) by (source_workload)))",\r
+        "refresh": 1,\r
+        "regex": "/.*workload=\"([^\"]*).*/",\r
+        "sort": 1,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Inbound Workload Namespace",\r
+        "multi": true,\r
+        "name": "srcns",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_workload=\"$workload\", destination_workload_namespace=~\"$namespace\"}) by (source_workload_namespace) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_workload=\"$workload\", destination_workload_namespace=~\"$namespace\"}) by (source_workload_namespace))",\r
+        "refresh": 1,\r
+        "regex": "/.*namespace=\"([^\"]*).*/",\r
+        "sort": 2,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Inbound Workload",\r
+        "multi": true,\r
+        "name": "srcwl",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"destination\", destination_workload=\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload_namespace=~\"$srcns\"}) by (source_workload) or sum(istio_tcp_sent_bytes_total{reporter=\"destination\", destination_workload=\"$workload\", destination_workload_namespace=~\"$namespace\", source_workload_namespace=~\"$srcns\"}) by (source_workload))",\r
+        "refresh": 1,\r
+        "regex": "/.*workload=\"([^\"]*).*/",\r
+        "sort": 3,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      },\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Destination Service",\r
+        "multi": true,\r
+        "name": "dstsvc",\r
+        "options": [],\r
+        "query": "query_result( sum(istio_requests_total{reporter=\"source\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\"}) by (destination_service) or sum(istio_tcp_sent_bytes_total{reporter=\"source\", source_workload=~\"$workload\", source_workload_namespace=~\"$namespace\"}) by (destination_service))",\r
+        "refresh": 1,\r
+        "regex": "/.*destination_service=\"([^\"]*).*/",\r
+        "sort": 4,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      }\r
+    ]\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "",\r
+  "title": "Istio Workload Dashboard",\r
+  "uid": "UbsSZTDik",\r
+  "version": 1\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-mixer-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  mixer-dashboard.json: '{\r
+  "__inputs": [\r
+    {\r
+      "name": "DS_PROMETHEUS",\r
+      "label": "Prometheus",\r
+      "description": "",\r
+      "type": "datasource",\r
+      "pluginId": "prometheus",\r
+      "pluginName": "Prometheus"\r
+    }\r
+  ],\r
+  "__requires": [\r
+    {\r
+      "type": "grafana",\r
+      "id": "grafana",\r
+      "name": "Grafana",\r
+      "version": "5.2.3"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "graph",\r
+      "name": "Graph",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "datasource",\r
+      "id": "prometheus",\r
+      "name": "Prometheus",\r
+      "version": "5.0.0"\r
+    },\r
+    {\r
+      "type": "panel",\r
+      "id": "text",\r
+      "name": "Text",\r
+      "version": "5.0.0"\r
+    }\r
+  ],\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "limit": 100,\r
+        "name": "Annotations & Alerts",\r
+        "showIn": 0,\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 1,\r
+  "id": null,\r
+  "iteration": 1543881232533,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "content": "<center><h2>Deployed Versions</h2></center>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "height": "40",\r
+      "id": 62,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 5,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 3\r
+      },\r
+      "id": 64,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(istio_build{component=\"mixer\"}) by (tag)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ tag }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Mixer Versions",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "content": "<center><h2>Resource Usage</h2></center>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 8\r
+      },\r
+      "height": "40",\r
+      "id": 29,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 11\r
+      },\r
+      "id": 5,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(process_virtual_memory_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Virtual Memory ({{ job }})",\r
+          "refId": "I"\r
+        },\r
+        {\r
+          "expr": "sum(process_resident_memory_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Resident Memory ({{ job }})",\r
+          "refId": "H"\r
+        },\r
+        {\r
+          "expr": "sum(go_memstats_heap_sys_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap sys ({{ job }})",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(go_memstats_heap_alloc_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap alloc ({{ job }})",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "sum(go_memstats_alloc_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Alloc ({{ job }})",\r
+          "refId": "F"\r
+        },\r
+        {\r
+          "expr": "sum(go_memstats_heap_inuse_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Heap in-use ({{ job }})",\r
+          "refId": "E"\r
+        },\r
+        {\r
+          "expr": "sum(go_memstats_stack_inuse_bytes{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Stack in-use ({{ job }})",\r
+          "refId": "G"\r
+        },\r
+        {\r
+          "expr": "sum(label_replace(container_memory_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*|istio-policy-.*\"}, \"service\", \"$1\" , \"pod_name\", \"(istio-telemetry|istio-policy)-.*\")) by (service)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ service }} total (k8s)",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "sum(label_replace(container_memory_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*|istio-policy-.*\"}, \"service\", \"$1\" , \"pod_name\", \"(istio-telemetry|istio-policy)-.*\")) by (container_name, service)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ service }} - {{ container_name }} (k8s)",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 11\r
+      },\r
+      "id": 6,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*|istio-policy-.*\"}[1m])) by (pod_name), \"service\", \"$1\" , \"pod_name\", \"(istio-telemetry|istio-policy)-.*\")",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ service }} total (k8s)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "label_replace(sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*|istio-policy-.*\"}[1m])) by (container_name, pod_name), \"service\", \"$1\" , \"pod_name\", \"(istio-telemetry|istio-policy)-.*\")",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ service }} - {{ container_name }} (k8s)",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(irate(process_cpu_seconds_total{job=~\"istio-telemetry|istio-policy\"}[1m])) by (job)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ job }} (self-reported)",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "CPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 11\r
+      },\r
+      "id": 7,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(process_open_fds{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "instant": false,\r
+          "interval": "",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Open FDs ({{ job }})",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "sum(label_replace(container_fs_usage_bytes{container_name=~\"mixer|istio-proxy\", pod_name=~\"istio-telemetry-.*|istio-policy-.*\"}, \"service\", \"$1\" , \"pod_name\", \"(istio-telemetry|istio-policy)-.*\")) by (container_name, service)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ service }} - {{ container_name }}",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "decimals": null,\r
+          "format": "none",\r
+          "label": "",\r
+          "logBase": 1024,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 11\r
+      },\r
+      "id": 4,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": false,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(go_goroutines{job=~\"istio-telemetry|istio-policy\"}) by (job)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Number of Goroutines ({{ job }})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Goroutines",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "content": "<center><h2>Mixer Overview</h2></center>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 18\r
+      },\r
+      "height": "40px",\r
+      "id": 30,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 21\r
+      },\r
+      "id": 9,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(grpc_io_server_completed_rpcs[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "mixer (Total)",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "sum(rate(grpc_io_server_completed_rpcs[1m])) by (grpc_server_method)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "mixer ({{ grpc_server_method }})",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Incoming Requests",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 21\r
+      },\r
+      "id": 8,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [\r
+        {\r
+          "alias": "{}",\r
+          "yaxis": 1\r
+        }\r
+      ],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.5, sum(rate(grpc_io_server_server_latency_bucket{}[1m])) by (grpc_server_method, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ grpc_server_method }} 0.5",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.9, sum(rate(grpc_io_server_server_latency_bucket{}[1m])) by (grpc_server_method, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ grpc_server_method }} 0.9",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(rate(grpc_io_server_server_latency_bucket{}[1m])) by (grpc_server_method, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ grpc_server_method }} 0.99",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Response Durations",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ms",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 21\r
+      },\r
+      "id": 11,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(grpc_server_handled_total{grpc_code=~\"Unknown|Unimplemented|Internal|DataLoss\"}[1m])) by (grpc_method)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Mixer {{ grpc_method }}",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Server Error Rate (5xx responses)",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 21\r
+      },\r
+      "id": 12,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(grpc_server_handled_total{grpc_code!=\"OK\",grpc_service=~\".*Mixer\"}[1m])) by (grpc_method)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Mixer {{ grpc_method }}",\r
+          "refId": "B"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Non-successes (4xxs)",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "content": "<center><h2>Adapters and Config</h2></center>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 27\r
+      },\r
+      "id": 28,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 30\r
+      },\r
+      "id": 13,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(mixer_runtime_dispatches_total{adapter=~\"$adapter\"}[1m])) by (adapter)",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ adapter }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Adapter Dispatch Count",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 30\r
+      },\r
+      "id": 14,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "histogram_quantile(0.5, sum(irate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (adapter, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ adapter }} - p50",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.9, sum(irate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (adapter, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ adapter }} - p90 ",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "histogram_quantile(0.99, sum(irate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (adapter, le))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ adapter }} - p99",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Adapter Dispatch Duration",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 1,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 37\r
+      },\r
+      "id": 60,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_rule_config_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Rules",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_rule_config_error_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Config Errors",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_rule_config_match_error_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Match Errors",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_unsatisfied_action_handler_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Unsatisfied Actions",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Rules",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 37\r
+      },\r
+      "id": 56,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_instance_config_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Instances",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Instances in Latest Config",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 37\r
+      },\r
+      "id": 54,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_handler_config_count) by (configID)))",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Handlers",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Handlers in Latest Config",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 37\r
+      },\r
+      "id": 58,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "scalar(topk(1, max(mixer_config_attribute_count) by (configID)))",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Attributes",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Attributes in Latest Config",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "content": "<center><h2>Individual Adapters</h2></center>",\r
+      "gridPos": {\r
+        "h": 3,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 44\r
+      },\r
+      "id": 23,\r
+      "links": [],\r
+      "mode": "html",\r
+      "title": "",\r
+      "transparent": true,\r
+      "type": "text"\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 47\r
+      },\r
+      "id": 46,\r
+      "panels": [],\r
+      "repeat": "adapter",\r
+      "title": "$adapter Adapter",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 48\r
+      },\r
+      "id": 17,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(irate(mixer_runtime_dispatches_total{adapter=~\"$adapter\"}[1m]),\"handler\", \"$1 ($3)\", \"handler\", \"(.*)\\\\.(.*)\\\\.(.*)\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ handler }} (error: {{  error }})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Dispatch Count By Handler",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 48\r
+      },\r
+      "id": 18,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(histogram_quantile(0.5, sum(rate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (handler, error, le)),  \"handler_short\", \"$1 ($3)\", \"handler\", \"(.*)\\\\.(.*)\\\\.(.*)\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "p50 - {{ handler_short }} (error: {{ error }})",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "label_replace(histogram_quantile(0.9, sum(irate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (handler, error, le)),  \"handler_short\", \"$1 ($3)\", \"handler\", \"(.*)\\\\.(.*)\\\\.(.*)\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "p90 - {{ handler_short }} (error: {{ error }})",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "label_replace(histogram_quantile(0.99, sum(irate(mixer_runtime_dispatch_duration_seconds_bucket{adapter=~\"$adapter\"}[1m])) by (handler, error, le)),  \"handler_short\", \"$1 ($3)\", \"handler\", \"(.*)\\\\.(.*)\\\\.(.*)\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "p99 - {{ handler_short }} (error: {{ error }})",\r
+          "refId": "E"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeShift": null,\r
+      "title": "Dispatch Duration By Handler",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "s",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "5s",\r
+  "schemaVersion": 16,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": [\r
+      {\r
+        "allValue": null,\r
+        "current": {},\r
+        "datasource": "Prometheus",\r
+        "hide": 0,\r
+        "includeAll": true,\r
+        "label": "Adapter",\r
+        "multi": true,\r
+        "name": "adapter",\r
+        "options": [],\r
+        "query": "label_values(adapter)",\r
+        "refresh": 2,\r
+        "regex": "",\r
+        "sort": 1,\r
+        "tagValuesQuery": "",\r
+        "tags": [],\r
+        "tagsQuery": "",\r
+        "type": "query",\r
+        "useTags": false\r
+      }\r
+    ]\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "",\r
+  "title": "Istio Mixer Dashboard",\r
+  "version": 4\r
+}\r
+'\r
+---\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana-configuration-dashboards-pilot-dashboard\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  pilot-dashboard.json: '{\r
+  "annotations": {\r
+    "list": [\r
+      {\r
+        "builtIn": 1,\r
+        "datasource": "-- Grafana --",\r
+        "enable": true,\r
+        "hide": true,\r
+        "iconColor": "rgba(0, 211, 255, 1)",\r
+        "name": "Annotations & Alerts",\r
+        "type": "dashboard"\r
+      }\r
+    ]\r
+  },\r
+  "editable": false,\r
+  "gnetId": null,\r
+  "graphTooltip": 1,\r
+  "id": 6,\r
+  "links": [],\r
+  "panels": [\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 0\r
+      },\r
+      "id": 60,\r
+      "panels": [],\r
+      "title": "Deployed Versions",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 5,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 1\r
+      },\r
+      "id": 56,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(istio_build{component=\"pilot\"}) by (tag)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ tag }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Pilot Versions",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 6\r
+      },\r
+      "id": 62,\r
+      "panels": [],\r
+      "title": "Resource Usage",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 0,\r
+        "y": 7\r
+      },\r
+      "id": 5,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_virtual_memory_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Virtual Memory",\r
+          "refId": "I",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "process_resident_memory_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Resident Memory",\r
+          "refId": "H",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_sys_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap sys",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_alloc_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "heap alloc",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "go_memstats_alloc_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Alloc",\r
+          "refId": "F",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_heap_inuse_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Heap in-use",\r
+          "refId": "E",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "go_memstats_stack_inuse_bytes{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Stack in-use",\r
+          "refId": "G",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(container_memory_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"})",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "C",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "container_memory_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Memory",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 6,\r
+        "y": 7\r
+      },\r
+      "id": 6,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Total (k8s)",\r
+          "refId": "A",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "sum(rate(container_cpu_usage_seconds_total{job=\"kubernetes-cadvisor\",container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}[1m])) by (container_name)",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }} (k8s)",\r
+          "refId": "B",\r
+          "step": 2\r
+        },\r
+        {\r
+          "expr": "irate(process_cpu_seconds_total{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 2,\r
+          "legendFormat": "pilot (self-reported)",\r
+          "refId": "C",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "CPU",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 12,\r
+        "y": 7\r
+      },\r
+      "id": 7,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "process_open_fds{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "instant": false,\r
+          "interval": "",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Open FDs (pilot)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "container_fs_usage_bytes{container_name=~\"discovery|istio-proxy\", pod_name=~\"istio-pilot-.*\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "{{ container_name }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Disk",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "bytes",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "decimals": null,\r
+          "format": "none",\r
+          "label": "",\r
+          "logBase": 1024,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 6,\r
+        "x": 18,\r
+        "y": 7\r
+      },\r
+      "id": 4,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": false,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "go_goroutines{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Number of Goroutines",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Goroutines",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": "",\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 14\r
+      },\r
+      "id": 58,\r
+      "panels": [],\r
+      "title": "Pilot Push Information",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": true,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "description": "Shows pilot pushes",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 0,\r
+        "y": 15\r
+      },\r
+      "id": 622,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": false,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null as zero",\r
+      "paceLength": 10,\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": true,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(rate(pilot_xds_pushes{type!~\".*_senderr\"}[1m])) by (type)",\r
+          "format": "time_series",\r
+          "instant": false,\r
+          "interval": "",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ type }}",\r
+          "refId": "B",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Pilot Pushes",\r
+      "tooltip": {\r
+        "shared": false,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": [\r
+          "total"\r
+        ]\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": "0",\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "description": "Captures a variety of pilot errors",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 12,\r
+        "x": 12,\r
+        "y": 15\r
+      },\r
+      "id": 67,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_cds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Rejected CDS Configs - {{ node }}: {{ err }}",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "pilot_xds_eds_reject{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Rejected EDS Configs",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_write_timeout{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Write Timeouts",\r
+          "refId": "F"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_push_timeout{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Push Timeouts",\r
+          "refId": "G"\r
+        },\r
+        {\r
+          "expr": "sum(rate(pilot_xds_push_errors{job=\"pilot\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Push Errors ({{ type }})",\r
+          "refId": "I"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Pilot Errors",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "collapsed": false,\r
+      "gridPos": {\r
+        "h": 1,\r
+        "w": 24,\r
+        "x": 0,\r
+        "y": 23\r
+      },\r
+      "id": 64,\r
+      "panels": [],\r
+      "title": "xDS",\r
+      "type": "row"\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 24\r
+      },\r
+      "id": 40,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(irate(envoy_cluster_update_success{cluster_name=\"xds-grpc\"}[1m]))",\r
+          "format": "time_series",\r
+          "hide": false,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "XDS GRPC Successes",\r
+          "refId": "C"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Updates",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 24\r
+      },\r
+      "id": 42,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "round(sum(rate(envoy_cluster_update_attempt{cluster_name=\"xds-grpc\"}[1m])) - sum(rate(envoy_cluster_update_success{cluster_name=\"xds-grpc\"}[1m])))",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "XDS GRPC ",\r
+          "refId": "A",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Failures",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "ops",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": false\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 6,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 24\r
+      },\r
+      "id": 41,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(envoy_cluster_upstream_cx_active{cluster_name=\"xds-grpc\"})",\r
+          "format": "time_series",\r
+          "intervalFactor": 2,\r
+          "legendFormat": "Pilot (XDS GRPC)",\r
+          "refId": "C",\r
+          "step": 2\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Active Connections",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 30\r
+      },\r
+      "id": 45,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "pilot_conflict_inbound_listener{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Inbound Listeners",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "pilot_conflict_outbound_listener_http_over_current_tcp{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Outbound Listeners (http over current tcp)",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "pilot_conflict_outbound_listener_tcp_over_current_tcp{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Outbound Listeners (tcp over current tcp)",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "pilot_conflict_outbound_listener_tcp_over_current_http{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Outbound Listeners (tcp over current http)",\r
+          "refId": "D"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Conflicts",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 30\r
+      },\r
+      "id": 47,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "pilot_virt_services{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Virtual Services",\r
+          "refId": "A"\r
+        },\r
+        {\r
+          "expr": "pilot_services{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Services",\r
+          "refId": "B"\r
+        },\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_cds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Rejected CDS Configs - {{ node }}: {{ err }}",\r
+          "refId": "C"\r
+        },\r
+        {\r
+          "expr": "pilot_xds_eds_reject{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "hide": true,\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Rejected EDS Configs",\r
+          "refId": "D"\r
+        },\r
+        {\r
+          "expr": "pilot_xds{job=\"pilot\"}",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Connected Endpoints",\r
+          "refId": "E"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_write_timeout{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Write Timeouts",\r
+          "refId": "F"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_push_timeout{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Push Timeouts",\r
+          "refId": "G"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_pushes{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Pushes ({{ type }})",\r
+          "refId": "H"\r
+        },\r
+        {\r
+          "expr": "rate(pilot_xds_push_errors{job=\"pilot\"}[1m])",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "Push Errors ({{ type }})",\r
+          "refId": "I"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "ADS Monitoring",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 8,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 30\r
+      },\r
+      "id": 49,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_cds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ node }}  ({{ err }})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Rejected CDS Configs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 38\r
+      },\r
+      "id": 52,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_eds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ node }} ({{err}})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Rejected EDS Configs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 8,\r
+        "x": 8,\r
+        "y": 38\r
+      },\r
+      "id": 54,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_lds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ node }} ({{err}})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Rejected LDS Configs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {},\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 8,\r
+        "x": 16,\r
+        "y": 38\r
+      },\r
+      "id": 53,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "label_replace(sum(pilot_xds_rds_reject{job=\"pilot\"}) by (node, err), \"node\", \"$1\", \"node\", \".*~.*~(.*)~.*\")",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ node }} ({{err}})",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "Rejected RDS Configs",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    },\r
+    {\r
+      "aliasColors": {\r
+        "outbound|80||default-http-backend.kube-system.svc.cluster.local": "rgba(255, 255, 255, 0.97)"\r
+      },\r
+      "bars": false,\r
+      "dashLength": 10,\r
+      "dashes": false,\r
+      "datasource": "Prometheus",\r
+      "fill": 1,\r
+      "gridPos": {\r
+        "h": 7,\r
+        "w": 8,\r
+        "x": 0,\r
+        "y": 45\r
+      },\r
+      "id": 51,\r
+      "legend": {\r
+        "avg": false,\r
+        "current": false,\r
+        "max": false,\r
+        "min": false,\r
+        "show": true,\r
+        "total": false,\r
+        "values": false\r
+      },\r
+      "lines": true,\r
+      "linewidth": 1,\r
+      "links": [],\r
+      "nullPointMode": "null",\r
+      "percentage": false,\r
+      "pointradius": 5,\r
+      "points": false,\r
+      "renderer": "flot",\r
+      "seriesOverrides": [\r
+        {\r
+          "alias": "outbound|80||default-http-backend.kube-system.svc.cluster.local",\r
+          "yaxis": 1\r
+        }\r
+      ],\r
+      "spaceLength": 10,\r
+      "stack": false,\r
+      "steppedLine": false,\r
+      "targets": [\r
+        {\r
+          "expr": "sum(pilot_xds_eds_instances{job=\"pilot\"}) by (cluster)",\r
+          "format": "time_series",\r
+          "intervalFactor": 1,\r
+          "legendFormat": "{{ cluster }}",\r
+          "refId": "A"\r
+        }\r
+      ],\r
+      "thresholds": [],\r
+      "timeFrom": null,\r
+      "timeRegions": [],\r
+      "timeShift": null,\r
+      "title": "EDS Instances",\r
+      "tooltip": {\r
+        "shared": true,\r
+        "sort": 0,\r
+        "value_type": "individual"\r
+      },\r
+      "type": "graph",\r
+      "xaxis": {\r
+        "buckets": null,\r
+        "mode": "time",\r
+        "name": null,\r
+        "show": true,\r
+        "values": []\r
+      },\r
+      "yaxes": [\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        },\r
+        {\r
+          "format": "short",\r
+          "label": null,\r
+          "logBase": 1,\r
+          "max": null,\r
+          "min": null,\r
+          "show": true\r
+        }\r
+      ],\r
+      "yaxis": {\r
+        "align": false,\r
+        "alignLevel": null\r
+      }\r
+    }\r
+  ],\r
+  "refresh": "5s",\r
+  "schemaVersion": 18,\r
+  "style": "dark",\r
+  "tags": [],\r
+  "templating": {\r
+    "list": []\r
+  },\r
+  "time": {\r
+    "from": "now-5m",\r
+    "to": "now"\r
+  },\r
+  "timepicker": {\r
+    "refresh_intervals": [\r
+      "5s",\r
+      "10s",\r
+      "30s",\r
+      "1m",\r
+      "5m",\r
+      "15m",\r
+      "30m",\r
+      "1h",\r
+      "2h",\r
+      "1d"\r
+    ],\r
+    "time_options": [\r
+      "5m",\r
+      "15m",\r
+      "1h",\r
+      "6h",\r
+      "12h",\r
+      "24h",\r
+      "2d",\r
+      "7d",\r
+      "30d"\r
+    ]\r
+  },\r
+  "timezone": "browser",\r
+  "title": "Istio Pilot Dashboard",\r
+  "uid": "3--MLVZZk",\r
+  "version": 1\r
+}\r
+'\r
+---\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/configmap.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-grafana\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: grafana\r
+data:\r
+  datasources.yaml: |\r
+    apiVersion: 1\r
+    datasources:\r
+    - access: proxy\r
+      editable: true\r
+      isDefault: true\r
+      jsonData:\r
+        timeInterval: 5s\r
+      name: Prometheus\r
+      orgId: 1\r
+      type: prometheus\r
+      url: http://prometheus:9090\r
+    \r
+  dashboardproviders.yaml: |\r
+    apiVersion: 1\r
+    providers:\r
+    - disableDeletion: false\r
+      folder: istio\r
+      name: istio\r
+      options:\r
+        path: /var/lib/grafana/dashboards/istio\r
+      orgId: 1\r
+      type: file\r
+    \r
+---\r
+# Source: istio/charts/kiali/templates/configmap.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: kiali\r
+  namespace: istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+data:\r
+  config.yaml: |\r
+    istio_namespace: istio-system\r
+    auth:\r
+      strategy: "login"\r
+    server:\r
+      port: 20001\r
+      web_root: /kiali\r
+    external_services:\r
+      tracing:\r
+        url: \r
+      grafana:\r
+        url: \r
+      prometheus:\r
+        url: http://prometheus:9090\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/configmap.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: prometheus\r
+  namespace: istio-system\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+data:\r
+  prometheus.yml: |-\r
+    global:\r
+      scrape_interval: 15s\r
+    scrape_configs:\r
+\r
+    - job_name: 'istio-mesh'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-telemetry;prometheus\r
+\r
+    # Scrape config for envoy stats\r
+    - job_name: 'envoy-stats'\r
+      metrics_path: /stats/prometheus\r
+      kubernetes_sd_configs:\r
+      - role: pod\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_pod_container_port_name]\r
+        action: keep\r
+        regex: '.*-envoy-prom'\r
+      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]\r
+        action: replace\r
+        regex: ([^:]+)(?::\d+)?;(\d+)\r
+        replacement: $1:15090\r
+        target_label: __address__\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_pod_label_(.+)\r
+      - source_labels: [__meta_kubernetes_namespace]\r
+        action: replace\r
+        target_label: namespace\r
+      - source_labels: [__meta_kubernetes_pod_name]\r
+        action: replace\r
+        target_label: pod_name\r
+\r
+    - job_name: 'istio-policy'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-policy;http-monitoring\r
+\r
+    - job_name: 'istio-telemetry'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-telemetry;http-monitoring\r
+\r
+    - job_name: 'pilot'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-pilot;http-monitoring\r
+\r
+    - job_name: 'galley'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-galley;http-monitoring\r
+\r
+    - job_name: 'citadel'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - istio-system\r
+\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: istio-citadel;http-monitoring\r
+\r
+    # scrape config for API servers\r
+    - job_name: 'kubernetes-apiservers'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+        namespaces:\r
+          names:\r
+          - default\r
+      scheme: https\r
+      tls_config:\r
+        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\r
+      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]\r
+        action: keep\r
+        regex: kubernetes;https\r
+\r
+    # scrape config for nodes (kubelet)\r
+    - job_name: 'kubernetes-nodes'\r
+      scheme: https\r
+      tls_config:\r
+        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\r
+      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\r
+      kubernetes_sd_configs:\r
+      - role: node\r
+      relabel_configs:\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_node_label_(.+)\r
+      - target_label: __address__\r
+        replacement: kubernetes.default.svc:443\r
+      - source_labels: [__meta_kubernetes_node_name]\r
+        regex: (.+)\r
+        target_label: __metrics_path__\r
+        replacement: /api/v1/nodes/${1}/proxy/metrics\r
+\r
+    # Scrape config for Kubelet cAdvisor.\r
+    #\r
+    # This is required for Kubernetes 1.7.3 and later, where cAdvisor metrics\r
+    # (those whose names begin with 'container_') have been removed from the\r
+    # Kubelet metrics endpoint.  This job scrapes the cAdvisor endpoint to\r
+    # retrieve those metrics.\r
+    #\r
+    # In Kubernetes 1.7.0-1.7.2, these metrics are only exposed on the cAdvisor\r
+    # HTTP endpoint; use "replacement: /api/v1/nodes/${1}:4194/proxy/metrics"\r
+    # in that case (and ensure cAdvisor's HTTP server hasn't been disabled with\r
+    # the --cadvisor-port=0 Kubelet flag).\r
+    #\r
+    # This job is not necessary and should be removed in Kubernetes 1.6 and\r
+    # earlier versions, or it will cause the metrics to be scraped twice.\r
+    - job_name: 'kubernetes-cadvisor'\r
+      scheme: https\r
+      tls_config:\r
+        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\r
+      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\r
+      kubernetes_sd_configs:\r
+      - role: node\r
+      relabel_configs:\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_node_label_(.+)\r
+      - target_label: __address__\r
+        replacement: kubernetes.default.svc:443\r
+      - source_labels: [__meta_kubernetes_node_name]\r
+        regex: (.+)\r
+        target_label: __metrics_path__\r
+        replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor\r
+\r
+    # scrape config for service endpoints.\r
+    - job_name: 'kubernetes-service-endpoints'\r
+      kubernetes_sd_configs:\r
+      - role: endpoints\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]\r
+        action: keep\r
+        regex: true\r
+      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]\r
+        action: replace\r
+        target_label: __scheme__\r
+        regex: (https?)\r
+      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]\r
+        action: replace\r
+        target_label: __metrics_path__\r
+        regex: (.+)\r
+      - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]\r
+        action: replace\r
+        target_label: __address__\r
+        regex: ([^:]+)(?::\d+)?;(\d+)\r
+        replacement: $1:$2\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_service_label_(.+)\r
+      - source_labels: [__meta_kubernetes_namespace]\r
+        action: replace\r
+        target_label: kubernetes_namespace\r
+      - source_labels: [__meta_kubernetes_service_name]\r
+        action: replace\r
+        target_label: kubernetes_name\r
+\r
+    - job_name: 'kubernetes-pods'\r
+      kubernetes_sd_configs:\r
+      - role: pod\r
+      relabel_configs:  # If first two labels are present, pod should be scraped  by the istio-secure job.\r
+      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]\r
+        action: keep\r
+        regex: true\r
+      # Keep target if there's no sidecar or if prometheus.io/scheme is explicitly set to "http"\r
+      - source_labels: [__meta_kubernetes_pod_annotation_sidecar_istio_io_status, __meta_kubernetes_pod_annotation_prometheus_io_scheme]\r
+        action: keep\r
+        regex: ((;.*)|(.*;http))\r
+      - source_labels: [__meta_kubernetes_pod_annotation_istio_mtls]\r
+        action: drop\r
+        regex: (true)\r
+      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]\r
+        action: replace\r
+        target_label: __metrics_path__\r
+        regex: (.+)\r
+      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]\r
+        action: replace\r
+        regex: ([^:]+)(?::\d+)?;(\d+)\r
+        replacement: $1:$2\r
+        target_label: __address__\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_pod_label_(.+)\r
+      - source_labels: [__meta_kubernetes_namespace]\r
+        action: replace\r
+        target_label: namespace\r
+      - source_labels: [__meta_kubernetes_pod_name]\r
+        action: replace\r
+        target_label: pod_name\r
+\r
+    - job_name: 'kubernetes-pods-istio-secure'\r
+      scheme: https\r
+      tls_config:\r
+        ca_file: /etc/istio-certs/root-cert.pem\r
+        cert_file: /etc/istio-certs/cert-chain.pem\r
+        key_file: /etc/istio-certs/key.pem\r
+        insecure_skip_verify: true  # prometheus does not support secure naming.\r
+      kubernetes_sd_configs:\r
+      - role: pod\r
+      relabel_configs:\r
+      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]\r
+        action: keep\r
+        regex: true\r
+      # sidecar status annotation is added by sidecar injector and\r
+      # istio_workload_mtls_ability can be specifically placed on a pod to indicate its ability to receive mtls traffic.\r
+      - source_labels: [__meta_kubernetes_pod_annotation_sidecar_istio_io_status, __meta_kubernetes_pod_annotation_istio_mtls]\r
+        action: keep\r
+        regex: (([^;]+);([^;]*))|(([^;]*);(true))\r
+      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]\r
+        action: drop\r
+        regex: (http)\r
+      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]\r
+        action: replace\r
+        target_label: __metrics_path__\r
+        regex: (.+)\r
+      - source_labels: [__address__]  # Only keep address that is host:port\r
+        action: keep    # otherwise an extra target with ':443' is added for https scheme\r
+        regex: ([^:]+):(\d+)\r
+      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]\r
+        action: replace\r
+        regex: ([^:]+)(?::\d+)?;(\d+)\r
+        replacement: $1:$2\r
+        target_label: __address__\r
+      - action: labelmap\r
+        regex: __meta_kubernetes_pod_label_(.+)\r
+      - source_labels: [__meta_kubernetes_namespace]\r
+        action: replace\r
+        target_label: namespace\r
+      - source_labels: [__meta_kubernetes_pod_name]\r
+        action: replace\r
+        target_label: pod_name\r
+---\r
+# Source: istio/charts/security/templates/configmap.yaml\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-security-custom-resources\r
+  namespace: istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: citadel\r
+data:\r
+  custom-resources.yaml: |-\r
+    # Authentication policy to enable permissive mode for all services (that have sidecar) in the mesh.\r
+    apiVersion: "authentication.istio.io/v1alpha1"\r
+    kind: "MeshPolicy"\r
+    metadata:\r
+      name: "default"\r
+      labels:\r
+        app: security\r
+        chart: security\r
+        heritage: Tiller\r
+        release: istio\r
+    spec:\r
+      peers:\r
+      - mtls:\r
+          mode: PERMISSIVE\r
+  run.sh: |-\r
+    #!/bin/sh\r
+\r
+    set -x\r
+\r
+    if [ "$#" -ne "1" ]; then\r
+        echo "first argument should be path to custom resource yaml"\r
+        exit 1\r
+    fi\r
+\r
+    pathToResourceYAML=${1}\r
+\r
+    kubectl get validatingwebhookconfiguration istio-galley 2>/dev/null\r
+    if [ "$?" -eq 0 ]; then\r
+        echo "istio-galley validatingwebhookconfiguration found - waiting for istio-galley deployment to be ready"\r
+        while true; do\r
+            kubectl -n istio-system get deployment istio-galley 2>/dev/null\r
+            if [ "$?" -eq 0 ]; then\r
+                break\r
+            fi\r
+            sleep 1\r
+        done\r
+        kubectl -n istio-system rollout status deployment istio-galley\r
+        if [ "$?" -ne 0 ]; then\r
+            echo "istio-galley deployment rollout status check failed"\r
+            exit 1\r
+        fi\r
+        echo "istio-galley deployment ready for configuration validation"\r
+    fi\r
+    sleep 5\r
+    kubectl apply -f ${pathToResourceYAML}\r
+\r
+\r
+---\r
+# Source: istio/templates/configmap.yaml\r
+\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+data:\r
+  mesh: |-\r
+    # Set the following variable to true to disable policy checks by the Mixer.\r
+    # Note that metrics will still be reported to the Mixer.\r
+    disablePolicyChecks: false\r
+\r
+    # Set enableTracing to false to disable request tracing.\r
+    enableTracing: true\r
+\r
+    # Set accessLogFile to empty string to disable access log.\r
+    accessLogFile: "/dev/stdout"\r
+\r
+    # If accessLogEncoding is TEXT, value will be used directly as the log format\r
+    # example: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\n"\r
+    # If AccessLogEncoding is JSON, value will be parsed as map[string]string\r
+    # example: '{"start_time": "%START_TIME%", "req_method": "%REQ(:METHOD)%"}'\r
+    # Leave empty to use default log format\r
+    accessLogFormat: ""\r
+\r
+    # Set accessLogEncoding to JSON or TEXT to configure sidecar access log\r
+    accessLogEncoding: 'TEXT'\r
+    mixerCheckServer: istio-policy.istio-system.svc.cluster.local:9091\r
+    mixerReportServer: istio-telemetry.istio-system.svc.cluster.local:9091\r
+    # policyCheckFailOpen allows traffic in cases when the mixer policy service cannot be reached.\r
+    # Default is false which means the traffic is denied when the client is unable to connect to Mixer.\r
+    policyCheckFailOpen: false\r
+    # Let Pilot give ingresses the public IP of the Istio ingressgateway\r
+    ingressService: istio-ingressgateway\r
+\r
+    # Default connect timeout for dynamic clusters generated by Pilot and returned via XDS\r
+    connectTimeout: 10s\r
+\r
+    # DNS refresh rate for Envoy clusters of type STRICT_DNS\r
+    dnsRefreshRate: 300s\r
+\r
+    # Unix Domain Socket through which envoy communicates with NodeAgent SDS to get\r
+    # key/cert for mTLS. Use secret-mount files instead of SDS if set to empty.\r
+    sdsUdsPath:\r
+\r
+    # This flag is used by secret discovery service(SDS).\r
+    # If set to true(prerequisite: https://kubernetes.io/docs/concepts/storage/volumes/#projected), Istio will inject volumes mount \r
+    # for k8s service account JWT, so that K8s API server mounts k8s service account JWT to envoy container, which \r
+    # will be used to generate key/cert eventually. This isn't supported for non-k8s case.\r
+    enableSdsTokenMount: false\r
+\r
+    # This flag is used by secret discovery service(SDS).\r
+    # If set to true, envoy will fetch normal k8s service account JWT from '/var/run/secrets/kubernetes.io/serviceaccount/token' \r
+    # (https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) \r
+    # and pass to sds server, which will be used to request key/cert eventually.\r
+    # this flag is ignored if enableSdsTokenMount is set.\r
+    # This isn't supported for non-k8s case.\r
+    sdsUseK8sSaJwt: false\r
+\r
+    # The trust domain corresponds to the trust root of a system.\r
+    # Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain\r
+    trustDomain:\r
+\r
+    # Set the default behavior of the sidecar for handling outbound traffic from the application:\r
+    # ALLOW_ANY - outbound traffic to unknown destinations will be allowed, in case there are no\r
+    #   services or ServiceEntries for the destination port\r
+    # REGISTRY_ONLY - restrict outbound traffic to services defined in the service registry as well\r
+    #   as those defined through ServiceEntries\r
+    outboundTrafficPolicy:\r
+      mode: ALLOW_ANY\r
+\r
+    localityLbSetting:\r
+      {}\r
+\r
+\r
+    # The namespace to treat as the administrative root namespace for istio\r
+    # configuration.\r
+    rootNamespace: istio-system\r
+    configSources:\r
+    - address: istio-galley.istio-system.svc:9901\r
+\r
+    defaultConfig:\r
+      #\r
+      # TCP connection timeout between Envoy & the application, and between Envoys.  Used for static clusters\r
+      # defined in Envoy's configuration file\r
+      connectTimeout: 10s\r
+      #\r
+      ### ADVANCED SETTINGS #############\r
+      # Where should envoy's configuration be stored in the istio-proxy container\r
+      configPath: "/etc/istio/proxy"\r
+      binaryPath: "/usr/local/bin/envoy"\r
+      # The pseudo service name used for Envoy.\r
+      serviceCluster: istio-proxy\r
+      # These settings that determine how long an old Envoy\r
+      # process should be kept alive after an occasional reload.\r
+      drainDuration: 45s\r
+      parentShutdownDuration: 1m0s\r
+      #\r
+      # The mode used to redirect inbound connections to Envoy. This setting\r
+      # has no effect on outbound traffic: iptables REDIRECT is always used for\r
+      # outbound connections.\r
+      # If "REDIRECT", use iptables REDIRECT to NAT and redirect to Envoy.\r
+      # The "REDIRECT" mode loses source addresses during redirection.\r
+      # If "TPROXY", use iptables TPROXY to redirect to Envoy.\r
+      # The "TPROXY" mode preserves both the source and destination IP\r
+      # addresses and ports, so that they can be used for advanced filtering\r
+      # and manipulation.\r
+      # The "TPROXY" mode also configures the sidecar to run with the\r
+      # CAP_NET_ADMIN capability, which is required to use TPROXY.\r
+      #interceptionMode: REDIRECT\r
+      #\r
+      # Port where Envoy listens (on local host) for admin commands\r
+      # You can exec into the istio-proxy container in a pod and\r
+      # curl the admin port (curl http://localhost:15000/) to obtain\r
+      # diagnostic information from Envoy. See\r
+      # https://lyft.github.io/envoy/docs/operations/admin.html\r
+      # for more details\r
+      proxyAdminPort: 15000\r
+      #\r
+      # Set concurrency to a specific number to control the number of Proxy worker threads.\r
+      # If set to 0 (default), then start worker thread for each CPU thread/core.\r
+      concurrency: 2\r
+      #\r
+      tracing:\r
+        zipkin:\r
+          # Address of the Zipkin collector\r
+          address: zipkin.istio-system:9411\r
+      #\r
+      # Mutual TLS authentication between sidecars and istio control plane.\r
+      controlPlaneAuthPolicy: NONE\r
+      #\r
+      # Address where istio Pilot service is running\r
+      discoveryAddress: istio-pilot.istio-system:15010\r
+\r
+  # Configuration file for the mesh networks to be used by the Split Horizon EDS.\r
+  meshNetworks: |-\r
+    networks: {}\r
+\r
+---\r
+# Source: istio/templates/sidecar-injector-configmap.yaml\r
+\r
+apiVersion: v1\r
+kind: ConfigMap\r
+metadata:\r
+  name: istio-sidecar-injector\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio\r
+    chart: istio\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+data:\r
+  values: |-\r
+    {"certmanager":{"enabled":false},"galley":{"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":"galley","nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"replicaCount":1,"tolerations":[]},"gateways":{"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"istio-egressgateway":{"autoscaleEnabled":false,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":true,"env":{"ISTIO_META_ROUTER_MODE":"sni-dnat"},"labels":{"app":"istio-egressgateway","iecedge":"egressgateway"},"nodeSelector":{},"podAnnotations":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"ports":[{"name":"http2","port":80},{"name":"https","port":443},{"name":"tls","port":15443,"targetPort":15443}],"resources":{"limits":{"cpu":"2000m","memory":"256Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"secretVolumes":[{"mountPath":"/etc/istio/egressgateway-certs","name":"egressgateway-certs","secretName":"istio-egressgateway-certs"},{"mountPath":"/etc/istio/egressgateway-ca-certs","name":"egressgateway-ca-certs","secretName":"istio-egressgateway-ca-certs"}],"serviceAnnotations":{},"tolerations":[],"type":"ClusterIP"},"istio-ilbgateway":{"autoscaleEnabled":true,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":false,"labels":{"app":"istio-ilbgateway","iecedge":"ilbgateway"},"loadBalancerIP":"","nodeSelector":{},"podAnnotations":{},"ports":[{"name":"grpc-pilot-mtls","port":15011},{"name":"grpc-pilot","port":15010},{"name":"tcp-citadel-grpc-tls","port":8060,"targetPort":8060},{"name":"tcp-dns","port":5353}],"resources":{"requests":{"cpu":"800m","memory":"512Mi"}},"secretVolumes":[{"mountPath":"/etc/istio/ilbgateway-certs","name":"ilbgateway-certs","secretName":"istio-ilbgateway-certs"},{"mountPath":"/etc/istio/ilbgateway-ca-certs","name":"ilbgateway-ca-certs","secretName":"istio-ilbgateway-ca-certs"}],"serviceAnnotations":{"cloud.google.com/load-balancer-type":"internal"},"tolerations":[],"type":"LoadBalancer"},"istio-ingressgateway":{"applicationPorts":"","autoscaleEnabled":false,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":true,"env":{"ISTIO_META_ROUTER_MODE":"sni-dnat"},"externalIPs":[],"labels":{"app":"istio-ingressgateway","iecedge":"ingressgateway"},"loadBalancerIP":"","loadBalancerSourceRanges":[],"meshExpansionPorts":[{"name":"tcp-pilot-grpc-tls","port":15011,"targetPort":15011},{"name":"tcp-mixer-grpc-tls","port":15004,"targetPort":15004},{"name":"tcp-citadel-grpc-tls","port":8060,"targetPort":8060},{"name":"tcp-dns-tls","port":853,"targetPort":853}],"nodeSelector":{},"podAnnotations":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"ports":[{"name":"status-port","port":15020,"targetPort":15020},{"name":"http2","nodePort":31380,"port":80,"targetPort":80},{"name":"https","nodePort":31390,"port":443},{"name":"tcp","nodePort":31400,"port":31400},{"name":"https-kiali","port":15029,"targetPort":15029},{"name":"https-prometheus","port":15030,"targetPort":15030},{"name":"https-grafana","port":15031,"targetPort":15031},{"name":"https-tracing","port":15032,"targetPort":15032},{"name":"tls","port":15443,"targetPort":15443}],"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"sds":{"enabled":false,"image":"node-agent-k8s","resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}},"secretVolumes":[{"mountPath":"/etc/istio/ingressgateway-certs","name":"ingressgateway-certs","secretName":"istio-ingressgateway-certs"},{"mountPath":"/etc/istio/ingressgateway-ca-certs","name":"ingressgateway-ca-certs","secretName":"istio-ingressgateway-ca-certs"}],"serviceAnnotations":{},"tolerations":[],"type":"LoadBalancer"}},"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"grafana":{"accessMode":"ReadWriteMany","contextPath":"/grafana","dashboardProviders":{"dashboardproviders.yaml":{"apiVersion":1,"providers":[{"disableDeletion":false,"folder":"istio","name":"istio","options":{"path":"/var/lib/grafana/dashboards/istio"},"orgId":1,"type":"file"}]}},"datasources":{"datasources.yaml":{"apiVersion":1,"datasources":[{"access":"proxy","editable":true,"isDefault":true,"jsonData":{"timeInterval":"5s"},"name":"Prometheus","orgId":1,"type":"prometheus","url":"http://prometheus:9090"}]}},"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":{"repository":"grafana/grafana","tag":"6.1.6"},"ingress":{"annotations":null,"enabled":false,"hosts":["grafana.local"],"tls":null},"nodeSelector":{},"persist":false,"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"replicaCount":1,"security":{"enabled":false,"passphraseKey":"passphrase","secretName":"grafana","usernameKey":"username"},"service":{"annotations":{},"externalPort":3000,"loadBalancerIP":null,"loadBalancerSourceRanges":null,"name":"http","type":"ClusterIP"},"storageClassName":"","tolerations":[]},"istio_cni":{"enabled":false},"istiocoredns":{"enabled":false},"kiali":{"contextPath":"/kiali","createDemoSecret":true,"dashboard":{"grafanaURL":null,"jaegerURL":null,"secretName":"kiali","viewOnlyMode":false},"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"hub":"quay.io/kiali","ingress":{"annotations":null,"enabled":false,"hosts":["kiali.local"],"tls":null},"nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"prometheusAddr":"http://prometheus:9090","replicaCount":1,"tag":"v0.20","tolerations":[]},"mixer":{"adapters":{"kubernetesenv":{"enabled":true},"prometheus":{"enabled":true,"metricsExpiryDuration":"10m"},"stdio":{"enabled":true,"outputAsJson":true},"useAdapterCRDs":false},"env":{"GODEBUG":"gctrace=1","GOMAXPROCS":"6"},"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":"mixer","nodeSelector":{},"podAnnotations":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"policy":{"autoscaleEnabled":false,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":true,"replicaCount":1,"resources":{"requests":{"cpu":"10m","memory":"100Mi"}}},"telemetry":{"autoscaleEnabled":false,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":true,"loadshedding":{"latencyThreshold":"100ms","mode":"enforce"},"replicaCount":1,"resources":{"limits":{"cpu":"4800m","memory":"4G"},"requests":{"cpu":"50m","memory":"100Mi"}},"sessionAffinityEnabled":false},"templates":{"useTemplateCRDs":false},"tolerations":[]},"nodeagent":{"enabled":false},"pilot":{"autoscaleEnabled":false,"autoscaleMax":5,"autoscaleMin":1,"cpu":{"targetAverageUtilization":80},"enabled":true,"env":{"GODEBUG":"gctrace=1","PILOT_PUSH_THROTTLE":100},"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":"pilot","keepaliveMaxServerConnectionAge":"30m","nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"resources":{"requests":{"cpu":"10m","memory":"100Mi"}},"sidecar":true,"tolerations":[],"traceSampling":100},"prometheus":{"contextPath":"/prometheus","enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"hub":"docker.io/prom","ingress":{"annotations":null,"enabled":false,"hosts":["prometheus.local"],"tls":null},"nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"replicaCount":1,"retention":"6h","scrapeInterval":"15s","security":{"enabled":true},"service":{"annotations":{},"nodePort":{"enabled":false,"port":32090}},"tag":"v2.8.0","tolerations":[]},"security":{"citadelHealthCheck":false,"createMeshPolicy":true,"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":"citadel","nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"replicaCount":1,"selfSigned":true,"tolerations":[],"workloadCertTtl":"2160h"},"sidecarInjectorWebhook":{"alwaysInjectSelector":[],"enableNamespacesByDefault":false,"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"image":"sidecar_injector","neverInjectSelector":[],"nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"replicaCount":1,"rewriteAppHTTPProbe":false,"tolerations":[]},"tracing":{"enabled":true,"global":{"arch":{"amd64":2,"ppc64le":2,"arm64":2},"configValidation":true,"controlPlaneSecurityEnabled":false,"defaultNodeSelector":{},"defaultPodDisruptionBudget":{"enabled":true},"defaultResources":{"requests":{"cpu":"10m"}},"defaultTolerations":[],"disablePolicyChecks":false,"enableHelmTest":false,"enableTracing":true,"hub":"iecedge","imagePullPolicy":"IfNotPresent","imagePullSecrets":null,"k8sIngress":{"enableHttps":false,"enabled":false,"gatewayName":"ingressgateway"},"localityLbSetting":{},"logging":{"level":"default:info"},"meshExpansion":{"enabled":false,"useILB":false},"meshNetworks":{},"monitoringPort":15014,"mtls":{"enabled":false},"multiCluster":{"enabled":false},"oneNamespace":false,"outboundTrafficPolicy":{"mode":"ALLOW_ANY"},"policyCheckFailOpen":false,"priorityClassName":"","proxy":{"accessLogEncoding":"TEXT","accessLogFile":"/dev/stdout","accessLogFormat":"","autoInject":"enabled","clusterDomain":"cluster.local","componentLogLevel":"","concurrency":2,"dnsRefreshRate":"300s","enableCoreDump":false,"envoyMetricsService":{"enabled":false,"host":null,"port":null},"envoyStatsd":{"enabled":false,"host":null,"port":null},"excludeIPRanges":"","excludeInboundPorts":"","excludeOutboundPorts":"","image":"proxyv2-arm64","includeIPRanges":"*","includeInboundPorts":"*","kubevirtInterfaces":"","logLevel":"","privileged":false,"readinessFailureThreshold":30,"readinessInitialDelaySeconds":1,"readinessPeriodSeconds":2,"resources":{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"10m","memory":"40Mi"}},"statusPort":15020,"tracer":"zipkin"},"proxy_init":{"image":"proxy_init-arm64"},"sds":{"enabled":false,"udsPath":"","useNormalJwt":false,"useTrustworthyJwt":false},"tag":"1.2.3","tracer":{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":"","cacertPath":"","secure":true},"zipkin":{"address":""}},"trustDomain":"","useMCP":true},"ingress":{"annotations":null,"enabled":false,"hosts":null,"tls":null},"jaeger":{"hub":"docker.io/jaegertracing","memory":{"max_traces":50000},"tag":1.9},"nodeSelector":{},"podAntiAffinityLabelSelector":[],"podAntiAffinityTermLabelSelector":[],"provider":"jaeger","service":{"annotations":{},"externalPort":9411,"name":"http","type":"ClusterIP"},"tolerations":[],"zipkin":{"hub":"docker.io/openzipkin","javaOptsHeap":700,"maxSpans":500000,"node":{"cpus":2},"probeStartupDelay":200,"queryPort":9411,"resources":{"limits":{"cpu":"300m","memory":"900Mi"},"requests":{"cpu":"150m","memory":"900Mi"}},"tag":2}}}\r
+\r
+  config: |-\r
+    policy: enabled\r
+    alwaysInjectSelector:\r
+      []\r
+\r
+    neverInjectSelector:\r
+      []\r
+\r
+    template: |-\r
+      rewriteAppHTTPProbe: {{ valueOrDefault .Values.sidecarInjectorWebhook.rewriteAppHTTPProbe false }}\r
+      {{- if or (not .Values.istio_cni.enabled) .Values.global.proxy.enableCoreDump }}\r
+      initContainers:\r
+      {{ if ne (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `NONE` }}\r
+      {{- if not .Values.istio_cni.enabled }}\r
+      - name: istio-init\r
+      {{- if contains "/" .Values.global.proxy_init.image }}\r
+        image: "{{ .Values.global.proxy_init.image }}"\r
+      {{- else }}\r
+        image: "{{ .Values.global.hub }}/{{ .Values.global.proxy_init.image }}:{{ .Values.global.tag }}"\r
+      {{- end }}\r
+        args:\r
+        - "-p"\r
+        - "15001"\r
+        - "-u"\r
+        - 1337\r
+        - "-m"\r
+        - "{{ annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode }}"\r
+        - "-i"\r
+        - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges` .Values.global.proxy.includeIPRanges }}"\r
+        - "-x"\r
+        - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges` .Values.global.proxy.excludeIPRanges }}"\r
+        - "-b"\r
+        - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` (includeInboundPorts .Spec.Containers) }}"\r
+        - "-d"\r
+        - "{{ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}"\r
+        {{ if or (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/excludeOutboundPorts`) (ne .Values.global.proxy.excludeOutboundPorts "") -}}\r
+        - "-o"\r
+        - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundPorts` .Values.global.proxy.excludeOutboundPorts }}"\r
+        {{ end -}}\r
+        {{ if (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces`) -}}\r
+        - "-k"\r
+        - "{{ index .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces` }}"\r
+        {{ end -}}\r
+        imagePullPolicy: "{{ .Values.global.imagePullPolicy }}"\r
+        resources:\r
+          requests:\r
+            cpu: 10m\r
+            memory: 10Mi\r
+          limits:\r
+            cpu: 100m\r
+            memory: 50Mi\r
+        securityContext:\r
+          runAsUser: 0\r
+          runAsNonRoot: false\r
+          capabilities:\r
+            add:\r
+            - NET_ADMIN\r
+          {{- if .Values.global.proxy.privileged }}\r
+          privileged: true\r
+          {{- end }}\r
+        restartPolicy: Always\r
+        env:\r
+        {{- if contains "*" (annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` "") }}\r
+        - name: INBOUND_CAPTURE_PORT\r
+          value: 15006\r
+        {{- end }}\r
+      {{- end }}\r
+      {{  end -}}\r
+      {{- if eq .Values.global.proxy.enableCoreDump true }}\r
+      - name: enable-core-dump\r
+        args:\r
+        - -c\r
+        - sysctl -w kernel.core_pattern=/var/lib/istio/core.proxy && ulimit -c unlimited\r
+        command:\r
+          - /bin/sh\r
+      {{- if contains "/" .Values.global.proxy_init.image }}\r
+        image: "{{ .Values.global.proxy_init.image }}"\r
+      {{- else }}\r
+        image: "{{ .Values.global.hub }}/{{ .Values.global.proxy_init.image }}:{{ .Values.global.tag }}"\r
+      {{- end }}\r
+        imagePullPolicy: IfNotPresent\r
+        resources: {}\r
+        securityContext:\r
+          runAsUser: 0\r
+          runAsNonRoot: false\r
+          privileged: true\r
+      {{ end }}\r
+      {{- end }}\r
+      containers:\r
+      - name: istio-proxy\r
+      {{- if contains "/" .Values.global.proxy.image }}\r
+        image: "{{ annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.proxy.image }}"\r
+      {{- else }}\r
+        image: "{{ annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.hub }}/{{ .Values.global.proxy.image }}:{{ .Values.global.tag }}"\r
+      {{- end }}\r
+        ports:\r
+        - containerPort: 15090\r
+          protocol: TCP\r
+          name: http-envoy-prom\r
+        args:\r
+        - proxy\r
+        - sidecar\r
+        - --domain\r
+        - $(POD_NAMESPACE).svc.{{ .Values.global.proxy.clusterDomain }}\r
+        - --configPath\r
+        - "{{ .ProxyConfig.ConfigPath }}"\r
+        - --binaryPath\r
+        - "{{ .ProxyConfig.BinaryPath }}"\r
+        - --serviceCluster\r
+        {{ if ne "" (index .ObjectMeta.Labels "app") -}}\r
+        - "{{ index .ObjectMeta.Labels `app` }}.$(POD_NAMESPACE)"\r
+        {{ else -}}\r
+        - "{{ valueOrDefault .DeploymentMeta.Name `istio-proxy` }}.{{ valueOrDefault .DeploymentMeta.Namespace `default` }}"\r
+        {{ end -}}\r
+        - --drainDuration\r
+        - "{{ formatDuration .ProxyConfig.DrainDuration }}"\r
+        - --parentShutdownDuration\r
+        - "{{ formatDuration .ProxyConfig.ParentShutdownDuration }}"\r
+        - --discoveryAddress\r
+        - "{{ annotation .ObjectMeta `sidecar.istio.io/discoveryAddress` .ProxyConfig.DiscoveryAddress }}"\r
+      {{- if eq .Values.global.proxy.tracer "lightstep" }}\r
+        - --lightstepAddress\r
+        - "{{ .ProxyConfig.GetTracing.GetLightstep.GetAddress }}"\r
+        - --lightstepAccessToken\r
+        - "{{ .ProxyConfig.GetTracing.GetLightstep.GetAccessToken }}"\r
+        - --lightstepSecure={{ .ProxyConfig.GetTracing.GetLightstep.GetSecure }}\r
+        - --lightstepCacertPath\r
+        - "{{ .ProxyConfig.GetTracing.GetLightstep.GetCacertPath }}"\r
+      {{- else if eq .Values.global.proxy.tracer "zipkin" }}\r
+        - --zipkinAddress\r
+        - "{{ .ProxyConfig.GetTracing.GetZipkin.GetAddress }}"\r
+      {{- else if eq .Values.global.proxy.tracer "datadog" }}\r
+        - --datadogAgentAddress\r
+        - "{{ .ProxyConfig.GetTracing.GetDatadog.GetAddress }}"\r
+      {{- end }}\r
+      {{- if .Values.global.proxy.logLevel }}\r
+        - --proxyLogLevel={{ .Values.global.proxy.logLevel }}\r
+      {{- end}}\r
+      {{- if .Values.global.proxy.componentLogLevel }}\r
+        - --proxyComponentLogLevel={{ .Values.global.proxy.componentLogLevel }}\r
+      {{- end}}\r
+        - --dnsRefreshRate\r
+        - {{ .Values.global.proxy.dnsRefreshRate }}\r
+        - --connectTimeout\r
+        - "{{ formatDuration .ProxyConfig.ConnectTimeout }}"\r
+      {{- if .Values.global.proxy.envoyStatsd.enabled }}\r
+        - --statsdUdpAddress\r
+        - "{{ .ProxyConfig.StatsdUdpAddress }}"\r
+      {{- end }}\r
+      {{- if .Values.global.proxy.envoyMetricsService.enabled }}\r
+        - --envoyMetricsServiceAddress\r
+        - "{{ .ProxyConfig.EnvoyMetricsServiceAddress }}"\r
+      {{- end }}\r
+        - --proxyAdminPort\r
+        - "{{ .ProxyConfig.ProxyAdminPort }}"\r
+        {{ if gt .ProxyConfig.Concurrency 0 -}}\r
+        - --concurrency\r
+        - "{{ .ProxyConfig.Concurrency }}"\r
+        {{ end -}}\r
+        - --controlPlaneAuthPolicy\r
+        - "{{ annotation .ObjectMeta `sidecar.istio.io/controlPlaneAuthPolicy` .ProxyConfig.ControlPlaneAuthPolicy }}"\r
+      {{- if (ne (annotation .ObjectMeta "status.sidecar.istio.io/port" .Values.global.proxy.statusPort) "0") }}\r
+        - --statusPort\r
+        - "{{ annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort }}"\r
+        - --applicationPorts\r
+        - "{{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/applicationPorts` (applicationPorts .Spec.Containers) }}"\r
+      {{- end }}\r
+      {{- if .Values.global.trustDomain }}\r
+        - --trust-domain={{ .Values.global.trustDomain }}\r
+      {{- end }}\r
+        env:\r
+        - name: POD_NAME\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: metadata.name\r
+        - name: POD_NAMESPACE\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: metadata.namespace\r
+        - name: INSTANCE_IP\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: status.podIP\r
+      {{ if eq .Values.global.proxy.tracer "datadog" }}\r
+        - name: HOST_IP\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: status.hostIP\r
+      {{ end }}\r
+        - name: ISTIO_META_POD_NAME\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: metadata.name\r
+        - name: ISTIO_META_CONFIG_NAMESPACE\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: metadata.namespace\r
+        - name: ISTIO_META_INTERCEPTION_MODE\r
+          value: "{{ or (index .ObjectMeta.Annotations `sidecar.istio.io/interceptionMode`) .ProxyConfig.InterceptionMode.String }}"\r
+        - name: ISTIO_META_INCLUDE_INBOUND_PORTS\r
+          value: "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` (applicationPorts .Spec.Containers) }}"\r
+        {{- if .Values.global.network }}\r
+        - name: ISTIO_META_NETWORK\r
+          value: "{{ .Values.global.network }}"\r
+        {{- end }}\r
+        {{ if .ObjectMeta.Annotations }}\r
+        - name: ISTIO_METAJSON_ANNOTATIONS\r
+          value: |\r
+                 {{ toJSON .ObjectMeta.Annotations }}\r
+        {{ end }}\r
+        {{ if .ObjectMeta.Labels }}\r
+        - name: ISTIO_METAJSON_LABELS\r
+          value: |\r
+                 {{ toJSON .ObjectMeta.Labels }}\r
+        {{ end }}\r
+        {{- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}\r
+        - name: ISTIO_BOOTSTRAP_OVERRIDE\r
+          value: "/etc/istio/custom-bootstrap/custom_bootstrap.json"\r
+        {{- end }}\r
+        {{- if .Values.global.sds.customTokenDirectory }}\r
+        - name: ISTIO_META_SDS_TOKEN_PATH\r
+          value: "{{ .Values.global.sds.customTokenDirectory -}}/sdstoken"\r
+        {{- end }}\r
+        imagePullPolicy: {{ .Values.global.imagePullPolicy }}\r
+        {{ if ne (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) `0` }}\r
+        readinessProbe:\r
+          httpGet:\r
+            path: /healthz/ready\r
+            port: {{ annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort }}\r
+          initialDelaySeconds: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/initialDelaySeconds` .Values.global.proxy.readinessInitialDelaySeconds }}\r
+          periodSeconds: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/periodSeconds` .Values.global.proxy.readinessPeriodSeconds }}\r
+          failureThreshold: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/failureThreshold` .Values.global.proxy.readinessFailureThreshold }}\r
+        {{ end -}}\r
+        securityContext:\r
+          {{- if .Values.global.proxy.privileged }}\r
+          privileged: true\r
+          {{- end }}\r
+          {{- if ne .Values.global.proxy.enableCoreDump true }}\r
+          readOnlyRootFilesystem: true\r
+          {{- end }}\r
+          {{ if eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `TPROXY` -}}\r
+          capabilities:\r
+            add:\r
+            - NET_ADMIN\r
+          runAsGroup: 1337\r
+          {{ else -}}\r
+          {{ if and .Values.global.sds.enabled .Values.global.sds.useTrustworthyJwt }}\r
+          runAsGroup: 1337\r
+          {{- end }}\r
+          runAsUser: 1337\r
+          {{- end }}\r
+        resources:\r
+          {{ if or (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -}}\r
+          requests:\r
+            {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) -}}\r
+            cpu: "{{ index .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU` }}"\r
+            {{ end}}\r
+            {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -}}\r
+            memory: "{{ index .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory` }}"\r
+            {{ end }}\r
+        {{ else -}}\r
+      {{- if .Values.global.proxy.resources }}\r
+          {{ toYaml .Values.global.proxy.resources | indent 4 }}\r
+      {{- end }}\r
+        {{  end -}}\r
+        volumeMounts:\r
+        {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}\r
+        - mountPath: /etc/istio/custom-bootstrap\r
+          name: custom-bootstrap-volume\r
+        {{- end }}\r
+        - mountPath: /etc/istio/proxy\r
+          name: istio-envoy\r
+        {{- if .Values.global.sds.enabled }}\r
+        - mountPath: /var/run/sds\r
+          name: sds-uds-path\r
+          readOnly: true\r
+        {{- if .Values.global.sds.useTrustworthyJwt }}\r
+        - mountPath: /var/run/secrets/tokens\r
+          name: istio-token\r
+        {{- end }}\r
+        {{- if .Values.global.sds.customTokenDirectory }}\r
+        - mountPath: "{{ .Values.global.sds.customTokenDirectory -}}"\r
+          name: custom-sds-token\r
+          readOnly: true\r
+        {{- end }}\r
+        {{- else }}\r
+        - mountPath: /etc/certs/\r
+          name: istio-certs\r
+          readOnly: true\r
+        {{- end }}\r
+        {{- if and (eq .Values.global.proxy.tracer "lightstep") .Values.global.tracer.lightstep.cacertPath }}\r
+        - mountPath: {{ directory .ProxyConfig.GetTracing.GetLightstep.GetCacertPath }}\r
+          name: lightstep-certs\r
+          readOnly: true\r
+        {{- end }}\r
+          {{- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount` }}\r
+          {{ range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount`) }}\r
+        - name: "{{  $index }}"\r
+          {{ toYaml $value | indent 4 }}\r
+          {{ end }}\r
+          {{- end }}\r
+      volumes:\r
+      {{- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}\r
+      - name: custom-bootstrap-volume\r
+        configMap:\r
+          name: {{ annotation .ObjectMeta `sidecar.istio.io/bootstrapOverride` "" }}\r
+      {{- end }}\r
+      - emptyDir:\r
+          medium: Memory\r
+        name: istio-envoy\r
+      {{- if .Values.global.sds.enabled }}\r
+      - name: sds-uds-path\r
+        hostPath:\r
+          path: /var/run/sds\r
+      {{- if .Values.global.sds.customTokenDirectory }}\r
+      - name: custom-sds-token\r
+        secret:\r
+          secretName: sdstokensecret\r
+      {{- end }}\r
+      {{- if .Values.global.sds.useTrustworthyJwt }}\r
+      - name: istio-token\r
+        projected:\r
+          sources:\r
+          - serviceAccountToken:\r
+              path: istio-token\r
+              expirationSeconds: 43200\r
+              audience: {{ .Values.global.trustDomain }}\r
+      {{- end }}\r
+      {{- else }}\r
+      - name: istio-certs\r
+        secret:\r
+          optional: true\r
+          {{ if eq .Spec.ServiceAccountName "" }}\r
+          secretName: istio.default\r
+          {{ else -}}\r
+          secretName: {{  printf "istio.%s" .Spec.ServiceAccountName }}\r
+          {{  end -}}\r
+        {{- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolume` }}\r
+        {{range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolume`) }}\r
+      - name: "{{ $index }}"\r
+        {{ toYaml $value | indent 2 }}\r
+        {{ end }}\r
+        {{ end }}\r
+      {{- end }}\r
+      {{- if and (eq .Values.global.proxy.tracer "lightstep") .Values.global.tracer.lightstep.cacertPath }}\r
+      - name: lightstep-certs\r
+        secret:\r
+          optional: true\r
+          secretName: lightstep.cacert\r
+      {{- end }}\r
+      {{- if .Values.global.podDNSSearchNamespaces }}\r
+      dnsConfig:\r
+        searches:\r
+          {{- range .Values.global.podDNSSearchNamespaces }}\r
+          - {{ render . }}\r
+          {{- end }}\r
+      {{- end }}\r
+\r
+---\r
+# Source: istio/charts/galley/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-galley-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/serviceaccount.yaml\r
+\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-egressgateway-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio-egressgateway\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+---\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-ingressgateway-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio-ingressgateway\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+---\r
+\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/create-custom-resources-job.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-grafana-post-install-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-grafana-post-install-istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: ["authentication.istio.io"] # needed to create default authn policy\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-grafana-post-install-role-binding-istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-grafana-post-install-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-grafana-post-install-account\r
+    namespace: istio-system\r
+---\r
+apiVersion: batch/v1\r
+kind: Job\r
+metadata:\r
+  name: istio-grafana-post-install-1.2.3\r
+  namespace: istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-install\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  template:\r
+    metadata:\r
+      name: istio-grafana-post-install\r
+      labels:\r
+        app: istio-grafana\r
+        chart: grafana\r
+        heritage: Tiller\r
+        release: istio\r
+    spec:\r
+      serviceAccountName: istio-grafana-post-install-account\r
+      containers:\r
+        - name: kubectl\r
+          image: "iecedge/kubectl-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          command: [ "/bin/bash", "/tmp/grafana/run.sh", "/tmp/grafana/custom-resources.yaml" ]\r
+          volumeMounts:\r
+            - mountPath: "/tmp/grafana"\r
+              name: tmp-configmap-grafana\r
+      volumes:\r
+        - name: tmp-configmap-grafana\r
+          configMap:\r
+            name: istio-grafana-custom-resources\r
+      restartPolicy: OnFailure\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: kiali-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/serviceaccount.yaml\r
+\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-mixer-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-pilot-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: prometheus\r
+  namespace: istio-system\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/security/templates/cleanup-secrets.yaml\r
+# The reason for creating a ServiceAccount and ClusterRole specifically for this\r
+# post-delete hooked job is because the citadel ServiceAccount is being deleted\r
+# before this hook is launched. On the other hand, running this hook before the\r
+# deletion of the citadel (e.g. pre-delete) won't delete the secrets because they\r
+# will be re-created immediately by the to-be-deleted citadel.\r
+#\r
+# It's also important that the ServiceAccount, ClusterRole and ClusterRoleBinding\r
+# will be ready before running the hooked Job therefore the hook weights.\r
+\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-cleanup-secrets-service-account\r
+  namespace: istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-delete\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+    "helm.sh/hook-weight": "1"\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-cleanup-secrets-istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-delete\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+    "helm.sh/hook-weight": "1"\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: [""]\r
+  resources: ["secrets"]\r
+  verbs: ["list", "delete"]\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-cleanup-secrets-istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-delete\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+    "helm.sh/hook-weight": "2"\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-cleanup-secrets-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-cleanup-secrets-service-account\r
+    namespace: istio-system\r
+---\r
+apiVersion: batch/v1\r
+kind: Job\r
+metadata:\r
+  name: istio-cleanup-secrets-1.2.3\r
+  namespace: istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-delete\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+    "helm.sh/hook-weight": "3"\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  template:\r
+    metadata:\r
+      name: istio-cleanup-secrets\r
+      labels:\r
+        app: security\r
+        chart: security\r
+        heritage: Tiller\r
+        release: istio\r
+    spec:\r
+      serviceAccountName: istio-cleanup-secrets-service-account\r
+      containers:\r
+        - name: kubectl\r
+          image: "iecedge/kubectl-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          command:\r
+          - /bin/bash\r
+          - -c\r
+          - >\r
+              kubectl get secret --all-namespaces | grep "istio.io/key-and-cert" |  while read -r entry; do\r
+                ns=$(echo $entry | awk '{print $1}');\r
+                name=$(echo $entry | awk '{print $2}');\r
+                kubectl delete secret $name -n $ns;\r
+              done\r
+      restartPolicy: OnFailure\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/security/templates/create-custom-resources-job.yaml\r
+\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-security-post-install-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1beta1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-security-post-install-istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: ["authentication.istio.io"] # needed to create default authn policy\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+- apiGroups: ["networking.istio.io"] # needed to create security destination rules\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+- apiGroups: ["admissionregistration.k8s.io"]\r
+  resources: ["validatingwebhookconfigurations"]\r
+  verbs: ["get"]\r
+- apiGroups: ["extensions", "apps"]\r
+  resources: ["deployments", "replicasets"]\r
+  verbs: ["get", "list", "watch"]\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1beta1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-security-post-install-role-binding-istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-security-post-install-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-security-post-install-account\r
+    namespace: istio-system\r
+---\r
+apiVersion: batch/v1\r
+kind: Job\r
+metadata:\r
+  name: istio-security-post-install-1.2.3\r
+  namespace: istio-system\r
+  annotations:\r
+    "helm.sh/hook": post-install\r
+    "helm.sh/hook-delete-policy": hook-succeeded\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  template:\r
+    metadata:\r
+      name: istio-security-post-install\r
+      labels:\r
+        app: security\r
+        chart: security\r
+        heritage: Tiller\r
+        release: istio\r
+    spec:\r
+      serviceAccountName: istio-security-post-install-account\r
+      containers:\r
+        - name: kubectl\r
+          image: "iecedge/kubectl-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          command: [ "/bin/bash", "/tmp/security/run.sh", "/tmp/security/custom-resources.yaml" ]\r
+          volumeMounts:\r
+            - mountPath: "/tmp/security"\r
+              name: tmp-configmap-security\r
+      volumes:\r
+        - name: tmp-configmap-security\r
+          configMap:\r
+            name: istio-security-custom-resources\r
+      restartPolicy: OnFailure\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/security/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-citadel-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-sidecar-injector-service-account\r
+  namespace: istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+\r
+---\r
+# Source: istio/templates/serviceaccount.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+  name: istio-multi\r
+  namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/galley/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-galley-istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: ["admissionregistration.k8s.io"]\r
+  resources: ["validatingwebhookconfigurations"]\r
+  verbs: ["*"]\r
+- apiGroups: ["config.istio.io"] # istio mixer CRD watcher\r
+  resources: ["*"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["networking.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["authentication.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["rbac.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["extensions","apps"]\r
+  resources: ["deployments"]\r
+  resourceNames: ["istio-galley"]\r
+  verbs: ["get"]\r
+- apiGroups: [""]\r
+  resources: ["pods", "nodes", "services", "endpoints"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["extensions"]\r
+  resources: ["ingresses"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["extensions"]\r
+  resources: ["deployments/finalizers"]\r
+  resourceNames: ["istio-galley"]\r
+  verbs: ["update"]\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: kiali\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: [""]\r
+  resources:\r
+  - configmaps\r
+  - endpoints\r
+  - namespaces\r
+  - nodes\r
+  - pods\r
+  - pods/log\r
+  - replicationcontrollers\r
+  - services\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["extensions", "apps"]\r
+  resources:\r
+  - deployments\r
+  - replicasets\r
+  - statefulsets\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["autoscaling"]\r
+  resources:\r
+  - horizontalpodautoscalers\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["batch"]\r
+  resources:\r
+  - cronjobs\r
+  - jobs\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["config.istio.io"]\r
+  resources:\r
+  - adapters\r
+  - apikeys\r
+  - bypasses\r
+  - authorizations\r
+  - checknothings\r
+  - circonuses\r
+  - cloudwatches\r
+  - deniers\r
+  - dogstatsds\r
+  - edges\r
+  - fluentds\r
+  - handlers\r
+  - instances\r
+  - kubernetesenvs\r
+  - kuberneteses\r
+  - listcheckers\r
+  - listentries\r
+  - logentries\r
+  - memquotas\r
+  - metrics\r
+  - noops\r
+  - opas\r
+  - prometheuses\r
+  - quotas\r
+  - quotaspecbindings\r
+  - quotaspecs\r
+  - rbacs\r
+  - redisquotas\r
+  - reportnothings\r
+  - rules\r
+  - signalfxs\r
+  - solarwindses\r
+  - stackdrivers\r
+  - statsds\r
+  - stdios\r
+  - templates\r
+  - tracespans\r
+  - zipkins\r
+  verbs:\r
+  - create\r
+  - delete\r
+  - get\r
+  - list\r
+  - patch\r
+  - watch\r
+- apiGroups: ["networking.istio.io"]\r
+  resources:\r
+  - destinationrules\r
+  - gateways\r
+  - serviceentries\r
+  - virtualservices\r
+  verbs:\r
+  - create\r
+  - delete\r
+  - get\r
+  - list\r
+  - patch\r
+  - watch\r
+- apiGroups: ["authentication.istio.io"]\r
+  resources:\r
+  - meshpolicies\r
+  - policies\r
+  verbs:\r
+  - create\r
+  - delete\r
+  - get\r
+  - list\r
+  - patch\r
+  - watch\r
+- apiGroups: ["rbac.istio.io"]\r
+  resources:\r
+  - clusterrbacconfigs\r
+  - rbacconfigs\r
+  - servicerolebindings\r
+  - serviceroles\r
+  verbs:\r
+  - create\r
+  - delete\r
+  - get\r
+  - list\r
+  - patch\r
+  - watch\r
+- apiGroups: ["monitoring.kiali.io"]\r
+  resources:\r
+  - monitoringdashboards\r
+  verbs:\r
+  - get\r
+  - list\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: kiali-viewer\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: [""]\r
+  resources:\r
+  - configmaps\r
+  - endpoints\r
+  - namespaces\r
+  - nodes\r
+  - pods\r
+  - pods/log\r
+  - replicationcontrollers\r
+  - services\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["extensions", "apps"]\r
+  resources:\r
+  - deployments\r
+  - replicasets\r
+  - statefulsets\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["autoscaling"]\r
+  resources:\r
+  - horizontalpodautoscalers\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["batch"]\r
+  resources:\r
+  - cronjobs\r
+  - jobs\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["config.istio.io"]\r
+  resources:\r
+  - adapters\r
+  - apikeys\r
+  - bypasses\r
+  - authorizations\r
+  - checknothings\r
+  - circonuses\r
+  - cloudwatches\r
+  - deniers\r
+  - dogstatsds\r
+  - edges\r
+  - fluentds\r
+  - handlers\r
+  - instances\r
+  - kubernetesenvs\r
+  - kuberneteses\r
+  - listcheckers\r
+  - listentries\r
+  - logentries\r
+  - memquotas\r
+  - metrics\r
+  - noops\r
+  - opas\r
+  - prometheuses\r
+  - quotas\r
+  - quotaspecbindings\r
+  - quotaspecs\r
+  - rbacs\r
+  - redisquotas\r
+  - reportnothings\r
+  - rules\r
+  - signalfxs\r
+  - solarwindses\r
+  - stackdrivers\r
+  - statsds\r
+  - stdios\r
+  - templates\r
+  - tracespans\r
+  - zipkins\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["networking.istio.io"]\r
+  resources:\r
+  - destinationrules\r
+  - gateways\r
+  - serviceentries\r
+  - virtualservices\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["authentication.istio.io"]\r
+  resources:\r
+  - meshpolicies\r
+  - policies\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["rbac.istio.io"]\r
+  resources:\r
+  - clusterrbacconfigs\r
+  - rbacconfigs\r
+  - servicerolebindings\r
+  - serviceroles\r
+  verbs:\r
+  - get\r
+  - list\r
+  - watch\r
+- apiGroups: ["monitoring.kiali.io"]\r
+  resources:\r
+  - monitoringdashboards\r
+  verbs:\r
+  - get\r
+  - list\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/clusterrole.yaml\r
+\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-mixer-istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: ["config.istio.io"] # istio CRD watcher\r
+  resources: ["*"]\r
+  verbs: ["create", "get", "list", "watch", "patch"]\r
+- apiGroups: ["apiextensions.k8s.io"]\r
+  resources: ["customresourcedefinitions"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: [""]\r
+  resources: ["configmaps", "endpoints", "pods", "services", "namespaces", "secrets", "replicationcontrollers"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["extensions", "apps"]\r
+  resources: ["replicasets"]\r
+  verbs: ["get", "list", "watch"]\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-pilot-istio-system\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: ["config.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+- apiGroups: ["rbac.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["get", "watch", "list"]\r
+- apiGroups: ["networking.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+- apiGroups: ["authentication.istio.io"]\r
+  resources: ["*"]\r
+  verbs: ["*"]\r
+- apiGroups: ["apiextensions.k8s.io"]\r
+  resources: ["customresourcedefinitions"]\r
+  verbs: ["*"]\r
+- apiGroups: ["extensions"]\r
+  resources: ["ingresses", "ingresses/status"]\r
+  verbs: ["*"]\r
+- apiGroups: [""]\r
+  resources: ["configmaps"]\r
+  verbs: ["create", "get", "list", "watch", "update"]\r
+- apiGroups: [""]\r
+  resources: ["endpoints", "pods", "services", "namespaces", "nodes", "secrets"]\r
+  verbs: ["get", "list", "watch"]\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: prometheus-istio-system\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: [""]\r
+  resources:\r
+  - nodes\r
+  - services\r
+  - endpoints\r
+  - pods\r
+  - nodes/proxy\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: [""]\r
+  resources:\r
+  - configmaps\r
+  verbs: ["get"]\r
+- nonResourceURLs: ["/metrics"]\r
+  verbs: ["get"]\r
+\r
+---\r
+# Source: istio/charts/security/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-citadel-istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+rules:\r
+- apiGroups: [""]\r
+  resources: ["configmaps"]\r
+  verbs: ["create", "get", "update"]\r
+- apiGroups: [""]\r
+  resources: ["secrets"]\r
+  verbs: ["create", "get", "watch", "list", "update", "delete"]\r
+- apiGroups: [""]\r
+  resources: ["serviceaccounts", "services"]\r
+  verbs: ["get", "watch", "list"]\r
+- apiGroups: ["authentication.k8s.io"]\r
+  resources: ["tokenreviews"]\r
+  verbs: ["create"]\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/clusterrole.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRole\r
+metadata:\r
+  name: istio-sidecar-injector-istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+rules:\r
+- apiGroups: [""]\r
+  resources: ["configmaps"]\r
+  verbs: ["get", "list", "watch"]\r
+- apiGroups: ["admissionregistration.k8s.io"]\r
+  resources: ["mutatingwebhookconfigurations"]\r
+  verbs: ["get", "list", "watch", "patch"]\r
+\r
+---\r
+# Source: istio/templates/clusterrole.yaml\r
+kind: ClusterRole\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+metadata:\r
+  name: istio-reader\r
+rules:\r
+  - apiGroups: ['']\r
+    resources: ['nodes', 'pods', 'services', 'endpoints', "replicationcontrollers"]\r
+    verbs: ['get', 'watch', 'list']\r
+  - apiGroups: ["extensions", "apps"]\r
+    resources: ["replicasets"]\r
+    verbs: ["get", "list", "watch"]\r
+\r
+---\r
+# Source: istio/charts/galley/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-galley-admin-role-binding-istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-galley-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-galley-service-account\r
+    namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-kiali-admin-role-binding-istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: kiali\r
+subjects:\r
+- kind: ServiceAccount\r
+  name: kiali-service-account\r
+  namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/clusterrolebinding.yaml\r
+\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-mixer-admin-role-binding-istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-mixer-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-mixer-service-account\r
+    namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-pilot-istio-system\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-pilot-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-pilot-service-account\r
+    namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/clusterrolebindings.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: prometheus-istio-system\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: prometheus-istio-system\r
+subjects:\r
+- kind: ServiceAccount\r
+  name: prometheus\r
+  namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/security/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-citadel-istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-citadel-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-citadel-service-account\r
+    namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-sidecar-injector-admin-role-binding-istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-sidecar-injector-istio-system\r
+subjects:\r
+  - kind: ServiceAccount\r
+    name: istio-sidecar-injector-service-account\r
+    namespace: istio-system\r
+\r
+---\r
+# Source: istio/templates/clusterrolebinding.yaml\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+  name: istio-multi\r
+  labels:\r
+    chart: istio-1.1.0\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: ClusterRole\r
+  name: istio-reader\r
+subjects:\r
+- kind: ServiceAccount\r
+  name: istio-multi\r
+  namespace: istio-system\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/role.yaml\r
+\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: Role\r
+metadata:\r
+  name: istio-ingressgateway-sds\r
+  namespace: istio-system\r
+rules:\r
+- apiGroups: [""]\r
+  resources: ["secrets"]\r
+  verbs: ["get", "watch", "list"]\r
+---\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/rolebindings.yaml\r
+\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: RoleBinding\r
+metadata:\r
+  name: istio-ingressgateway-sds\r
+  namespace: istio-system\r
+roleRef:\r
+  apiGroup: rbac.authorization.k8s.io\r
+  kind: Role\r
+  name: istio-ingressgateway-sds\r
+subjects:\r
+- kind: ServiceAccount\r
+  name: istio-ingressgateway-service-account\r
+---\r
+\r
+---\r
+# Source: istio/charts/galley/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-galley\r
+  namespace: istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: galley\r
+spec:\r
+  ports:\r
+  - port: 443\r
+    name: https-validation\r
+  - port: 15014\r
+    name: http-monitoring\r
+  - port: 9901\r
+    name: grpc-mcp\r
+  selector:\r
+    istio: galley\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/service.yaml\r
+\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-egressgateway\r
+  namespace: istio-system\r
+  annotations:\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-egressgateway\r
+    istio: egressgateway\r
+spec:\r
+  type: ClusterIP\r
+  selector:\r
+    release: istio\r
+    app: istio-egressgateway\r
+    istio: egressgateway\r
+  ports:\r
+    -\r
+      name: http2\r
+      port: 80\r
+    -\r
+      name: https\r
+      port: 443\r
+    -\r
+      name: tls\r
+      port: 15443\r
+      targetPort: 15443\r
+---\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-ingressgateway\r
+  namespace: istio-system\r
+  annotations:\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-ingressgateway\r
+    istio: ingressgateway\r
+spec:\r
+  type: LoadBalancer\r
+  selector:\r
+    release: istio\r
+    app: istio-ingressgateway\r
+    istio: ingressgateway\r
+  ports:\r
+    -\r
+      name: status-port\r
+      port: 15020\r
+      targetPort: 15020\r
+    -\r
+      name: http2\r
+      nodePort: 31380\r
+      port: 80\r
+      targetPort: 80\r
+    -\r
+      name: https\r
+      nodePort: 31390\r
+      port: 443\r
+    -\r
+      name: tcp\r
+      nodePort: 31400\r
+      port: 31400\r
+    -\r
+      name: https-kiali\r
+      port: 15029\r
+      targetPort: 15029\r
+    -\r
+      name: https-prometheus\r
+      port: 15030\r
+      targetPort: 15030\r
+    -\r
+      name: https-grafana\r
+      port: 15031\r
+      targetPort: 15031\r
+    -\r
+      name: https-tracing\r
+      port: 15032\r
+      targetPort: 15032\r
+    -\r
+      name: tls\r
+      port: 15443\r
+      targetPort: 15443\r
+---\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: grafana\r
+  namespace: istio-system\r
+  annotations:\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  type: ClusterIP\r
+  ports:\r
+    - port: 3000\r
+      targetPort: 3000\r
+      protocol: TCP\r
+      name: http\r
+  selector:\r
+    app: grafana\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: kiali\r
+  namespace: istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  ports:\r
+  - name: http-kiali\r
+    protocol: TCP\r
+    port: 20001\r
+  selector:\r
+    app: kiali\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/service.yaml\r
+\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-policy\r
+  namespace: istio-system\r
+  annotations:\r
+   networking.istio.io/exportTo: "*"\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: mixer\r
+spec:\r
+  ports:\r
+  - name: grpc-mixer\r
+    port: 9091\r
+  - name: grpc-mixer-mtls\r
+    port: 15004\r
+  - name: http-monitoring\r
+    port: 15014\r
+  selector:\r
+    istio: mixer\r
+    istio-mixer-type: policy\r
+---\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-telemetry\r
+  namespace: istio-system\r
+  annotations:\r
+   networking.istio.io/exportTo: "*"\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: mixer\r
+spec:\r
+  ports:\r
+  - name: grpc-mixer\r
+    port: 9091\r
+  - name: grpc-mixer-mtls\r
+    port: 15004\r
+  - name: http-monitoring\r
+    port: 15014\r
+  - name: prometheus\r
+    port: 42422\r
+  selector:\r
+    istio: mixer\r
+    istio-mixer-type: telemetry\r
+---\r
+\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-pilot\r
+  namespace: istio-system\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: pilot\r
+spec:\r
+  ports:\r
+  - port: 15010\r
+    name: grpc-xds # direct\r
+  - port: 15011\r
+    name: https-xds # mTLS\r
+  - port: 8080\r
+    name: http-legacy-discovery # direct\r
+  - port: 15014\r
+    name: http-monitoring\r
+  selector:\r
+    istio: pilot\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: prometheus\r
+  namespace: istio-system\r
+  annotations:\r
+    prometheus.io/scrape: 'true'\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  selector:\r
+    app: prometheus\r
+  ports:\r
+  - name: http-prometheus\r
+    protocol: TCP\r
+    port: 9090\r
+\r
+---\r
+# Source: istio/charts/security/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  # we use the normal name here (e.g. 'prometheus')\r
+  # as grafana is configured to use this as a data source\r
+  name: istio-citadel\r
+  namespace: istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: citadel\r
+spec:\r
+  ports:\r
+    - name: grpc-citadel\r
+      port: 8060\r
+      targetPort: 8060\r
+      protocol: TCP\r
+    - name: http-monitoring\r
+      port: 15014\r
+  selector:\r
+    istio: citadel\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/service.yaml\r
+apiVersion: v1\r
+kind: Service\r
+metadata:\r
+  name: istio-sidecar-injector\r
+  namespace: istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+spec:\r
+  ports:\r
+  - port: 443\r
+  selector:\r
+    istio: sidecar-injector\r
+\r
+---\r
+# Source: istio/charts/galley/templates/deployment.yaml\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-galley\r
+  namespace: istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: galley\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      istio: galley\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: galley\r
+        chart: galley\r
+        heritage: Tiller\r
+        release:   istio\r
+        istio: galley\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-galley-service-account\r
+      containers:\r
+        - name: galley\r
+          image: "iecedge/galley-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+          - containerPort: 443\r
+          - containerPort: 15014\r
+          - containerPort: 9901\r
+          command:\r
+          - /usr/local/bin/galley\r
+          - server\r
+          - --meshConfigFile=/etc/mesh-config/mesh\r
+          - --livenessProbeInterval=1s\r
+          - --livenessProbePath=/healthliveness\r
+          - --readinessProbePath=/healthready\r
+          - --readinessProbeInterval=1s\r
+          - --deployment-namespace=istio-system\r
+          - --insecure=true\r
+          - --validation-webhook-config-file\r
+          - /etc/config/validatingwebhookconfiguration.yaml\r
+          - --monitoringPort=15014\r
+          - --log_output_level=default:info\r
+          volumeMounts:\r
+          - name: certs\r
+            mountPath: /etc/certs\r
+            readOnly: true\r
+          - name: config\r
+            mountPath: /etc/config\r
+            readOnly: true\r
+          - name: mesh-config\r
+            mountPath: /etc/mesh-config\r
+            readOnly: true\r
+          livenessProbe:\r
+            exec:\r
+              command:\r
+                - /usr/local/bin/galley\r
+                - probe\r
+                - --probe-path=/healthliveness\r
+                - --interval=10s\r
+            initialDelaySeconds: 5\r
+            periodSeconds: 5\r
+          readinessProbe:\r
+            exec:\r
+              command:\r
+                - /usr/local/bin/galley\r
+                - probe\r
+                - --probe-path=/healthready\r
+                - --interval=10s\r
+            initialDelaySeconds: 5\r
+            periodSeconds: 5\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+            \r
+      volumes:\r
+      - name: certs\r
+        secret:\r
+          secretName: istio.istio-galley-service-account\r
+      - name: config\r
+        configMap:\r
+          name: istio-galley-configuration\r
+      - name: mesh-config\r
+        configMap:\r
+          name: istio\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/deployment.yaml\r
+\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-egressgateway\r
+  namespace: istio-system\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-egressgateway\r
+    istio: egressgateway\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      app: istio-egressgateway\r
+      istio: egressgateway\r
+  template:\r
+    metadata:\r
+      labels:\r
+        chart: gateways\r
+        heritage: Tiller\r
+        release: istio\r
+        app: istio-egressgateway\r
+        istio: egressgateway\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-egressgateway-service-account\r
+      containers:\r
+        - name: istio-proxy\r
+          image: "iecedge/proxyv2-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+            - containerPort: 80\r
+            - containerPort: 443\r
+            - containerPort: 15443\r
+            - containerPort: 15090\r
+              protocol: TCP\r
+              name: http-envoy-prom\r
+          args:\r
+          - proxy\r
+          - router\r
+          - --domain\r
+          - $(POD_NAMESPACE).svc.cluster.local\r
+          - --log_output_level=default:info\r
+          - --drainDuration\r
+          - '45s' #drainDuration\r
+          - --parentShutdownDuration\r
+          - '1m0s' #parentShutdownDuration\r
+          - --connectTimeout\r
+          - '10s' #connectTimeout\r
+          - --serviceCluster\r
+          - istio-egressgateway\r
+          - --zipkinAddress\r
+          - zipkin:9411\r
+          - --proxyAdminPort\r
+          - "15000"\r
+          - --statusPort\r
+          - "15020"\r
+          - --controlPlaneAuthPolicy\r
+          - NONE\r
+          - --discoveryAddress\r
+          - istio-pilot:15010\r
+          readinessProbe:\r
+            failureThreshold: 30\r
+            httpGet:\r
+              path: /healthz/ready\r
+              port: 15020\r
+              scheme: HTTP\r
+            initialDelaySeconds: 1\r
+            periodSeconds: 2\r
+            successThreshold: 1\r
+            timeoutSeconds: 1\r
+          resources:\r
+            limits:\r
+              cpu: 2000m\r
+              memory: 256Mi\r
+            requests:\r
+              cpu: 10m\r
+              memory: 40Mi\r
+\r
+          env:\r
+          - name: NODE_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: spec.nodeName\r
+          - name: POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: POD_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.namespace\r
+          - name: INSTANCE_IP\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: status.podIP\r
+          - name: HOST_IP\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: status.hostIP\r
+          - name: ISTIO_META_POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: ISTIO_META_CONFIG_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                fieldPath: metadata.namespace\r
+          - name: ISTIO_META_ROUTER_MODE\r
+            value: sni-dnat\r
+          volumeMounts:\r
+          - name: istio-certs\r
+            mountPath: /etc/certs\r
+            readOnly: true\r
+          - name: egressgateway-certs\r
+            mountPath: "/etc/istio/egressgateway-certs"\r
+            readOnly: true\r
+          - name: egressgateway-ca-certs\r
+            mountPath: "/etc/istio/egressgateway-ca-certs"\r
+            readOnly: true\r
+      volumes:\r
+      - name: istio-certs\r
+        secret:\r
+          secretName: istio.istio-egressgateway-service-account\r
+          optional: true\r
+      - name: egressgateway-certs\r
+        secret:\r
+          secretName: "istio-egressgateway-certs"\r
+          optional: true\r
+      - name: egressgateway-ca-certs\r
+        secret:\r
+          secretName: "istio-egressgateway-ca-certs"\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+---\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-ingressgateway\r
+  namespace: istio-system\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-ingressgateway\r
+    istio: ingressgateway\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      app: istio-ingressgateway\r
+      istio: ingressgateway\r
+  template:\r
+    metadata:\r
+      labels:\r
+        chart: gateways\r
+        heritage: Tiller\r
+        release: istio\r
+        app: istio-ingressgateway\r
+        istio: ingressgateway\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-ingressgateway-service-account\r
+      containers:\r
+        - name: istio-proxy\r
+          image: "iecedge/proxyv2-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+            - containerPort: 15020\r
+            - containerPort: 80\r
+            - containerPort: 443\r
+            - containerPort: 31400\r
+            - containerPort: 15029\r
+            - containerPort: 15030\r
+            - containerPort: 15031\r
+            - containerPort: 15032\r
+            - containerPort: 15443\r
+            - containerPort: 15090\r
+              protocol: TCP\r
+              name: http-envoy-prom\r
+          args:\r
+          - proxy\r
+          - router\r
+          - --domain\r
+          - $(POD_NAMESPACE).svc.cluster.local\r
+          - --log_output_level=default:info\r
+          - --drainDuration\r
+          - '45s' #drainDuration\r
+          - --parentShutdownDuration\r
+          - '1m0s' #parentShutdownDuration\r
+          - --connectTimeout\r
+          - '10s' #connectTimeout\r
+          - --serviceCluster\r
+          - istio-ingressgateway\r
+          - --zipkinAddress\r
+          - zipkin:9411\r
+          - --proxyAdminPort\r
+          - "15000"\r
+          - --statusPort\r
+          - "15020"\r
+          - --controlPlaneAuthPolicy\r
+          - NONE\r
+          - --discoveryAddress\r
+          - istio-pilot:15010\r
+          readinessProbe:\r
+            failureThreshold: 30\r
+            httpGet:\r
+              path: /healthz/ready\r
+              port: 15020\r
+              scheme: HTTP\r
+            initialDelaySeconds: 1\r
+            periodSeconds: 2\r
+            successThreshold: 1\r
+            timeoutSeconds: 1\r
+          resources:\r
+            limits:\r
+              cpu: 2000m\r
+              memory: 1024Mi\r
+            requests:\r
+              cpu: 10m\r
+              memory: 40Mi\r
+\r
+          env:\r
+          - name: NODE_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: spec.nodeName\r
+          - name: POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: POD_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.namespace\r
+          - name: INSTANCE_IP\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: status.podIP\r
+          - name: HOST_IP\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: status.hostIP\r
+          - name: ISTIO_META_POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: ISTIO_META_CONFIG_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                fieldPath: metadata.namespace\r
+          - name: ISTIO_META_ROUTER_MODE\r
+            value: sni-dnat\r
+          volumeMounts:\r
+          - name: istio-certs\r
+            mountPath: /etc/certs\r
+            readOnly: true\r
+          - name: ingressgateway-certs\r
+            mountPath: "/etc/istio/ingressgateway-certs"\r
+            readOnly: true\r
+          - name: ingressgateway-ca-certs\r
+            mountPath: "/etc/istio/ingressgateway-ca-certs"\r
+            readOnly: true\r
+      volumes:\r
+      - name: istio-certs\r
+        secret:\r
+          secretName: istio.istio-ingressgateway-service-account\r
+          optional: true\r
+      - name: ingressgateway-certs\r
+        secret:\r
+          secretName: "istio-ingressgateway-certs"\r
+          optional: true\r
+      - name: ingressgateway-ca-certs\r
+        secret:\r
+          secretName: "istio-ingressgateway-ca-certs"\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+---\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/deployment.yaml\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: grafana\r
+  namespace: istio-system\r
+  labels:\r
+    app: grafana\r
+    chart: grafana\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      app: grafana\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: grafana\r
+        chart: grafana\r
+        heritage: Tiller\r
+        release: istio\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+        prometheus.io/scrape: "true"\r
+    spec:\r
+      securityContext:\r
+        runAsUser: 472\r
+        fsGroup: 472\r
+      containers:\r
+        - name: grafana\r
+          image: "grafana/grafana:6.1.6"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+          - containerPort: 3000\r
+          readinessProbe:\r
+            httpGet:\r
+              path: /login\r
+              port: 3000\r
+          env:\r
+          - name: GRAFANA_PORT\r
+            value: "3000"\r
+          - name: GF_AUTH_BASIC_ENABLED\r
+            value: "false"\r
+          - name: GF_AUTH_ANONYMOUS_ENABLED\r
+            value: "true"\r
+          - name: GF_AUTH_ANONYMOUS_ORG_ROLE\r
+            value: Admin\r
+          - name: GF_PATHS_DATA\r
+            value: /data/grafana\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+\r
+          volumeMounts:\r
+          - name: data\r
+            mountPath: /data/grafana\r
+          - name: dashboards-istio-galley-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/galley-dashboard.json"\r
+            subPath: galley-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-istio-mesh-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/istio-mesh-dashboard.json"\r
+            subPath: istio-mesh-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-istio-performance-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/istio-performance-dashboard.json"\r
+            subPath: istio-performance-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-istio-service-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/istio-service-dashboard.json"\r
+            subPath: istio-service-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-istio-workload-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/istio-workload-dashboard.json"\r
+            subPath: istio-workload-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-mixer-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/mixer-dashboard.json"\r
+            subPath: mixer-dashboard.json\r
+            readOnly: true\r
+          - name: dashboards-istio-pilot-dashboard\r
+            mountPath: "/var/lib/grafana/dashboards/istio/pilot-dashboard.json"\r
+            subPath: pilot-dashboard.json\r
+            readOnly: true\r
+          - name: config\r
+            mountPath: "/etc/grafana/provisioning/datasources/datasources.yaml"\r
+            subPath: datasources.yaml\r
+          - name: config\r
+            mountPath: "/etc/grafana/provisioning/dashboards/dashboardproviders.yaml"\r
+            subPath: dashboardproviders.yaml\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+      volumes:\r
+      - name: config\r
+        configMap:\r
+          name: istio-grafana\r
+      - name: data\r
+        emptyDir: {}\r
+      - name: dashboards-istio-galley-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-galley-dashboard\r
+      - name: dashboards-istio-istio-mesh-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-istio-mesh-dashboard\r
+      - name: dashboards-istio-istio-performance-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-istio-performance-dashboard\r
+      - name: dashboards-istio-istio-service-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-istio-service-dashboard\r
+      - name: dashboards-istio-istio-workload-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-istio-workload-dashboard\r
+      - name: dashboards-istio-mixer-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-mixer-dashboard\r
+      - name: dashboards-istio-pilot-dashboard\r
+        configMap:\r
+          name:  istio-grafana-configuration-dashboards-pilot-dashboard\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/deployment.yaml\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: kiali\r
+  namespace: istio-system\r
+  labels:\r
+    app: kiali\r
+    chart: kiali\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      app: kiali\r
+  template:\r
+    metadata:\r
+      name: kiali\r
+      labels:\r
+        app: kiali\r
+        chart: kiali\r
+        heritage: Tiller\r
+        release: istio\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+        scheduler.alpha.kubernetes.io/critical-pod: ""\r
+        prometheus.io/scrape: "true"\r
+        prometheus.io/port: "9090"\r
+    spec:\r
+      serviceAccountName: kiali-service-account\r
+      containers:\r
+      - image: "lurenjia/kiali:v1.11.0"\r
+        imagePullPolicy: IfNotPresent\r
+        name: kiali\r
+        command:\r
+        - "/opt/kiali/kiali"\r
+        - "-config"\r
+        - "/kiali-configuration/config.yaml"\r
+        - "-v"\r
+        - "4"\r
+        env:\r
+        - name: ACTIVE_NAMESPACE\r
+          valueFrom:\r
+            fieldRef:\r
+              fieldPath: metadata.namespace\r
+        volumeMounts:\r
+        - name: kiali-configuration\r
+          mountPath: "/kiali-configuration"\r
+        - name: kiali-secret\r
+          mountPath: "/kiali-secret"\r
+        resources:\r
+          requests:\r
+            cpu: 10m\r
+          \r
+      volumes:\r
+      - name: kiali-configuration\r
+        configMap:\r
+          name: kiali\r
+      - name: kiali-secret\r
+        secret:\r
+          secretName: kiali\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/deployment.yaml\r
+\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-policy\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio-mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: mixer\r
+spec:\r
+  replicas: 1\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  selector:\r
+    matchLabels:\r
+      istio: mixer\r
+      istio-mixer-type: policy\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: policy\r
+        chart: mixer\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: mixer\r
+        istio-mixer-type: policy\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-mixer-service-account\r
+      volumes:\r
+      - name: istio-certs\r
+        secret:\r
+          secretName: istio.istio-mixer-service-account\r
+          optional: true\r
+      - name: uds-socket\r
+        emptyDir: {}\r
+      - name: policy-adapter-secret\r
+        secret:\r
+          secretName: policy-adapter-secret\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+      containers:\r
+      - name: mixer\r
+        image: "iecedge/mixer-arm64:1.2.3"\r
+        imagePullPolicy: IfNotPresent\r
+        ports:\r
+        - containerPort: 15014\r
+        - containerPort: 42422\r
+        args:\r
+          - --monitoringPort=15014\r
+          - --address\r
+          - unix:///sock/mixer.socket\r
+          - --log_output_level=default:info\r
+          - --configStoreURL=mcp://istio-galley.istio-system.svc:9901\r
+          - --configDefaultNamespace=istio-system\r
+          - --useAdapterCRDs=false\r
+          - --useTemplateCRDs=false\r
+          - --trace_zipkin_url=http://zipkin.istio-system:9411/api/v1/spans\r
+        env:\r
+        - name: GODEBUG\r
+          value: "gctrace=1"\r
+        - name: GOMAXPROCS\r
+          value: "6"\r
+        resources:\r
+          requests:\r
+            cpu: 10m\r
+            memory: 100Mi\r
+          \r
+        volumeMounts:\r
+        - name: istio-certs\r
+          mountPath: /etc/certs\r
+          readOnly: true\r
+        - name: uds-socket\r
+          mountPath: /sock\r
+        livenessProbe:\r
+          httpGet:\r
+            path: /version\r
+            port: 15014\r
+          initialDelaySeconds: 5\r
+          periodSeconds: 5\r
+      - name: istio-proxy\r
+        image: "iecedge/proxyv2-arm64:1.2.3"\r
+        imagePullPolicy: IfNotPresent\r
+        ports:\r
+        - containerPort: 9091\r
+        - containerPort: 15004\r
+        - containerPort: 15090\r
+          protocol: TCP\r
+          name: http-envoy-prom\r
+        args:\r
+        - proxy\r
+        - --domain\r
+        - $(POD_NAMESPACE).svc.cluster.local\r
+        - --serviceCluster\r
+        - istio-policy\r
+        - --templateFile\r
+        - /etc/istio/proxy/envoy_policy.yaml.tmpl\r
+        - --controlPlaneAuthPolicy\r
+        - NONE\r
+        env:\r
+        - name: POD_NAME\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: metadata.name\r
+        - name: POD_NAMESPACE\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: metadata.namespace\r
+        - name: INSTANCE_IP\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: status.podIP\r
+        resources:\r
+          limits:\r
+            cpu: 2000m\r
+            memory: 1024Mi\r
+          requests:\r
+            cpu: 10m\r
+            memory: 40Mi\r
+          \r
+        volumeMounts:\r
+        - name: istio-certs\r
+          mountPath: /etc/certs\r
+          readOnly: true\r
+        - name: uds-socket\r
+          mountPath: /sock\r
+        - name: policy-adapter-secret\r
+          mountPath: /var/run/secrets/istio.io/policy/adapter\r
+          readOnly: true\r
+\r
+---\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-telemetry\r
+  namespace: istio-system\r
+  labels:\r
+    app: istio-mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: mixer\r
+spec:\r
+  replicas: 1\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  selector:\r
+    matchLabels:\r
+      istio: mixer\r
+      istio-mixer-type: telemetry\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: telemetry\r
+        chart: mixer\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: mixer\r
+        istio-mixer-type: telemetry\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-mixer-service-account\r
+      volumes:\r
+      - name: istio-certs\r
+        secret:\r
+          secretName: istio.istio-mixer-service-account\r
+          optional: true\r
+      - name: uds-socket\r
+        emptyDir: {}\r
+      - name: telemetry-adapter-secret\r
+        secret:\r
+          secretName: telemetry-adapter-secret\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+      containers:\r
+      - name: mixer\r
+        image: "iecedge/mixer-arm64:1.2.3"\r
+        imagePullPolicy: IfNotPresent\r
+        ports:\r
+        - containerPort: 15014\r
+        - containerPort: 42422\r
+        args:\r
+          - --monitoringPort=15014\r
+          - --address\r
+          - unix:///sock/mixer.socket\r
+          - --log_output_level=default:info\r
+          - --configStoreURL=mcp://istio-galley.istio-system.svc:9901\r
+          - --configDefaultNamespace=istio-system\r
+          - --useAdapterCRDs=false\r
+          - --useTemplateCRDs=false\r
+          - --trace_zipkin_url=http://zipkin.istio-system:9411/api/v1/spans\r
+          - --averageLatencyThreshold\r
+          - 100ms\r
+          - --loadsheddingMode\r
+          - enforce\r
+        env:\r
+        - name: GODEBUG\r
+          value: "gctrace=1"\r
+        - name: GOMAXPROCS\r
+          value: "6"\r
+        resources:\r
+          limits:\r
+            cpu: 4800m\r
+            memory: 4G\r
+          requests:\r
+            cpu: 50m\r
+            memory: 100Mi\r
+\r
+        volumeMounts:\r
+        - name: istio-certs\r
+          mountPath: /etc/certs\r
+          readOnly: true\r
+        - name: telemetry-adapter-secret\r
+          mountPath: /var/run/secrets/istio.io/telemetry/adapter\r
+          readOnly: true\r
+        - name: uds-socket\r
+          mountPath: /sock\r
+        livenessProbe:\r
+          httpGet:\r
+            path: /version\r
+            port: 15014\r
+          initialDelaySeconds: 5\r
+          periodSeconds: 5\r
+      - name: istio-proxy\r
+        image: "iecedge/proxyv2-arm64:1.2.3"\r
+        imagePullPolicy: IfNotPresent\r
+        ports:\r
+        - containerPort: 9091\r
+        - containerPort: 15004\r
+        - containerPort: 15090\r
+          protocol: TCP\r
+          name: http-envoy-prom\r
+        args:\r
+        - proxy\r
+        - --domain\r
+        - $(POD_NAMESPACE).svc.cluster.local\r
+        - --serviceCluster\r
+        - istio-telemetry\r
+        - --templateFile\r
+        - /etc/istio/proxy/envoy_telemetry.yaml.tmpl\r
+        - --controlPlaneAuthPolicy\r
+        - NONE\r
+        env:\r
+        - name: POD_NAME\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: metadata.name\r
+        - name: POD_NAMESPACE\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: metadata.namespace\r
+        - name: INSTANCE_IP\r
+          valueFrom:\r
+            fieldRef:\r
+              apiVersion: v1\r
+              fieldPath: status.podIP\r
+        resources:\r
+          limits:\r
+            cpu: 2000m\r
+            memory: 1024Mi\r
+          requests:\r
+            cpu: 10m\r
+            memory: 40Mi\r
+          \r
+        volumeMounts:\r
+        - name: istio-certs\r
+          mountPath: /etc/certs\r
+          readOnly: true\r
+        - name: uds-socket\r
+          mountPath: /sock\r
+\r
+---\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/deployment.yaml\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-pilot\r
+  namespace: istio-system\r
+  # TODO: default template doesn't have this, which one is right ?\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: pilot\r
+  annotations:\r
+    checksum/config-volume: f8da08b6b8c170dde721efd680270b2901e750d4aa186ebb6c22bef5b78a43f9\r
+spec:\r
+  replicas: 1\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  selector:\r
+    matchLabels:\r
+      istio: pilot\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: pilot\r
+        chart: pilot\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: pilot\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-pilot-service-account\r
+      containers:\r
+        - name: discovery\r
+          image: "iecedge/pilot-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          args:\r
+          - "discovery"\r
+          - --monitoringAddr=:15014\r
+          - --log_output_level=default:info\r
+          - --domain\r
+          - cluster.local\r
+          - --secureGrpcAddr\r
+          - ""\r
+          - --keepaliveMaxServerConnectionAge\r
+          - "30m"\r
+          ports:\r
+          - containerPort: 8080\r
+          - containerPort: 15010\r
+          readinessProbe:\r
+            httpGet:\r
+              path: /ready\r
+              port: 8080\r
+            initialDelaySeconds: 5\r
+            periodSeconds: 30\r
+            timeoutSeconds: 5\r
+          env:\r
+          - name: POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: POD_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.namespace\r
+          - name: GODEBUG\r
+            value: "gctrace=1"\r
+          - name: PILOT_PUSH_THROTTLE\r
+            value: "100"\r
+          - name: PILOT_TRACE_SAMPLING\r
+            value: "100"\r
+          - name: PILOT_DISABLE_XDS_MARSHALING_TO_ANY\r
+            value: "1"\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+              memory: 100Mi\r
+            \r
+          volumeMounts:\r
+          - name: config-volume\r
+            mountPath: /etc/istio/config\r
+          - name: istio-certs\r
+            mountPath: /etc/certs\r
+            readOnly: true\r
+        - name: istio-proxy\r
+          image: "iecedge/proxyv2-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+          - containerPort: 15003\r
+          - containerPort: 15005\r
+          - containerPort: 15007\r
+          - containerPort: 15011\r
+          args:\r
+          - proxy\r
+          - --domain\r
+          - $(POD_NAMESPACE).svc.cluster.local\r
+          - --serviceCluster\r
+          - istio-pilot\r
+          - --templateFile\r
+          - /etc/istio/proxy/envoy_pilot.yaml.tmpl\r
+          - --controlPlaneAuthPolicy\r
+          - NONE\r
+          env:\r
+          - name: POD_NAME\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.name\r
+          - name: POD_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.namespace\r
+          - name: INSTANCE_IP\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: status.podIP\r
+          resources:\r
+            limits:\r
+              cpu: 2000m\r
+              memory: 1024Mi\r
+            requests:\r
+              cpu: 10m\r
+              memory: 40Mi\r
+\r
+          volumeMounts:\r
+          - name: istio-certs\r
+            mountPath: /etc/certs\r
+            readOnly: true\r
+      volumes:\r
+      - name: config-volume\r
+        configMap:\r
+          name: istio\r
+      - name: istio-certs\r
+        secret:\r
+          secretName: istio.istio-pilot-service-account\r
+          optional: true\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/deployment.yaml\r
+# TODO: the original template has service account, roles, etc\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: prometheus\r
+  namespace: istio-system\r
+  labels:\r
+    app: prometheus\r
+    chart: prometheus\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      app: prometheus\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: prometheus\r
+        chart: prometheus\r
+        heritage: Tiller\r
+        release: istio\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: prometheus\r
+      containers:\r
+        - name: prometheus\r
+          image: "carlosedp/prometheus:v2.8.0-arm64"\r
+          imagePullPolicy: IfNotPresent\r
+          args:\r
+            - '--storage.tsdb.retention=6h'\r
+            - '--config.file=/etc/prometheus/prometheus.yml'\r
+          ports:\r
+            - containerPort: 9090\r
+              name: http\r
+          livenessProbe:\r
+            httpGet:\r
+              path: /-/healthy\r
+              port: 9090\r
+          readinessProbe:\r
+            httpGet:\r
+              path: /-/ready\r
+              port: 9090\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+            \r
+          volumeMounts:\r
+          - name: config-volume\r
+            mountPath: /etc/prometheus\r
+          - mountPath: /etc/istio-certs\r
+            name: istio-certs\r
+      volumes:\r
+      - name: config-volume\r
+        configMap:\r
+          name: prometheus\r
+      - name: istio-certs\r
+        secret:\r
+          defaultMode: 420\r
+          secretName: istio.default\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/security/templates/deployment.yaml\r
+# istio CA watching all namespaces\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-citadel\r
+  namespace: istio-system\r
+  labels:\r
+    app: security\r
+    chart: security\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: citadel\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      istio: citadel\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: security\r
+        chart: security\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: citadel\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-citadel-service-account\r
+      containers:\r
+        - name: citadel\r
+          image: "iecedge/citadel-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          args:\r
+            - --append-dns-names=true\r
+            - --grpc-port=8060\r
+            - --citadel-storage-namespace=istio-system\r
+            - --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system\r
+            - --monitoring-port=15014\r
+            - --self-signed-ca=true\r
+            - --workload-cert-ttl=2160h\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+            \r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/deployment.yaml\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-sidecar-injector\r
+  namespace: istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: sidecar-injector\r
+spec:\r
+  replicas: 1\r
+  selector:\r
+    matchLabels:\r
+      istio: sidecar-injector\r
+  strategy:\r
+    rollingUpdate:\r
+      maxSurge: 1\r
+      maxUnavailable: 0\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: sidecarInjectorWebhook\r
+        chart: sidecarInjectorWebhook\r
+        heritage: Tiller\r
+        release: istio\r
+        istio: sidecar-injector\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+    spec:\r
+      serviceAccountName: istio-sidecar-injector-service-account\r
+      containers:\r
+        - name: sidecar-injector-webhook\r
+          image: "iecedge/sidecar_injector-arm64:1.2.3"\r
+          imagePullPolicy: IfNotPresent\r
+          args:\r
+            - --caCertFile=/etc/istio/certs/root-cert.pem\r
+            - --tlsCertFile=/etc/istio/certs/cert-chain.pem\r
+            - --tlsKeyFile=/etc/istio/certs/key.pem\r
+            - --injectConfig=/etc/istio/inject/config\r
+            - --meshConfig=/etc/istio/config/mesh\r
+            - --healthCheckInterval=2s\r
+            - --healthCheckFile=/health\r
+          volumeMounts:\r
+          - name: config-volume\r
+            mountPath: /etc/istio/config\r
+            readOnly: true\r
+          - name: certs\r
+            mountPath: /etc/istio/certs\r
+            readOnly: true\r
+          - name: inject-config\r
+            mountPath: /etc/istio/inject\r
+            readOnly: true\r
+          livenessProbe:\r
+            exec:\r
+              command:\r
+                - /usr/local/bin/sidecar-injector\r
+                - probe\r
+                - --probe-path=/health\r
+                - --interval=4s\r
+            initialDelaySeconds: 4\r
+            periodSeconds: 4\r
+          readinessProbe:\r
+            exec:\r
+              command:\r
+                - /usr/local/bin/sidecar-injector\r
+                - probe\r
+                - --probe-path=/health\r
+                - --interval=4s\r
+            initialDelaySeconds: 4\r
+            periodSeconds: 4\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+            \r
+      volumes:\r
+      - name: config-volume\r
+        configMap:\r
+          name: istio\r
+      - name: certs\r
+        secret:\r
+          secretName: istio.istio-sidecar-injector-service-account\r
+      - name: inject-config\r
+        configMap:\r
+          name: istio-sidecar-injector\r
+          items:\r
+          - key: config\r
+            path: config\r
+          - key: values\r
+            path: values\r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/deployment-jaeger.yaml\r
+\r
+\r
+apiVersion: apps/v1\r
+kind: Deployment\r
+metadata:\r
+  name: istio-tracing\r
+  namespace: istio-system\r
+  labels:\r
+    app: jaeger\r
+    chart: tracing\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  selector:\r
+    matchLabels:\r
+      app: jaeger\r
+  template:\r
+    metadata:\r
+      labels:\r
+        app: jaeger\r
+        chart: tracing\r
+        heritage: Tiller\r
+        release: istio\r
+      annotations:\r
+        sidecar.istio.io/inject: "false"\r
+        prometheus.io/scrape: "true"\r
+        prometheus.io/port: "16686"\r
+        prometheus.io/path: "/jaeger/metrics"\r
+    spec:\r
+      containers:\r
+        - name: jaeger\r
+          image: "desaegher/jaeger-all-in-one:arm64"\r
+          imagePullPolicy: IfNotPresent\r
+          ports:\r
+            - containerPort: 9411\r
+            - containerPort: 16686\r
+            - containerPort: 5775\r
+              protocol: UDP\r
+            - containerPort: 6831\r
+              protocol: UDP\r
+            - containerPort: 6832\r
+              protocol: UDP\r
+          env:\r
+          - name: POD_NAMESPACE\r
+            valueFrom:\r
+              fieldRef:\r
+                apiVersion: v1\r
+                fieldPath: metadata.namespace\r
+          - name: COLLECTOR_ZIPKIN_HTTP_PORT\r
+            value: "9411"\r
+          - name: MEMORY_MAX_TRACES\r
+            value: "50000"\r
+          - name: QUERY_BASE_PATH\r
+            value:  /jaeger \r
+          livenessProbe:\r
+            httpGet:\r
+              path: /\r
+              port: 16686\r
+          readinessProbe:\r
+            httpGet:\r
+              path: /\r
+              port: 16686\r
+          resources:\r
+            requests:\r
+              cpu: 10m\r
+            \r
+      affinity:\r
+        nodeAffinity:\r
+          requiredDuringSchedulingIgnoredDuringExecution:\r
+            nodeSelectorTerms:\r
+            - matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+                - ppc64le\r
+                - arm64\r
+          preferredDuringSchedulingIgnoredDuringExecution:\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - amd64\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - ppc64le\r
+          - weight: 2\r
+            preference:\r
+              matchExpressions:\r
+              - key: beta.kubernetes.io/arch\r
+                operator: In\r
+                values:\r
+                - arm64\r
+\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/service-jaeger.yaml\r
+\r
+\r
+apiVersion: v1\r
+kind: List\r
+metadata:\r
+  name: jaeger-services\r
+  namespace: istio-system\r
+  labels:\r
+    app: jaeger\r
+    chart: tracing\r
+    heritage: Tiller\r
+    release: istio\r
+items:\r
+- apiVersion: v1\r
+  kind: Service\r
+  metadata:\r
+    name: jaeger-query\r
+    namespace: istio-system\r
+    annotations:\r
+    labels:\r
+      app: jaeger\r
+      jaeger-infra: jaeger-service\r
+      chart: tracing\r
+      heritage: Tiller\r
+      release: istio\r
+  spec:\r
+    ports:\r
+      - name: query-http\r
+        port: 16686\r
+        protocol: TCP\r
+        targetPort: 16686\r
+    selector:\r
+      app: jaeger\r
+- apiVersion: v1\r
+  kind: Service\r
+  metadata:\r
+    name: jaeger-collector\r
+    namespace: istio-system\r
+    labels:\r
+      app: jaeger\r
+      jaeger-infra: collector-service\r
+      chart: tracing\r
+      heritage: Tiller\r
+      release: istio\r
+  spec:\r
+    ports:\r
+    - name: jaeger-collector-tchannel\r
+      port: 14267\r
+      protocol: TCP\r
+      targetPort: 14267\r
+    - name: jaeger-collector-http\r
+      port: 14268\r
+      targetPort: 14268\r
+      protocol: TCP\r
+    selector:\r
+      app: jaeger\r
+    type: ClusterIP\r
+- apiVersion: v1\r
+  kind: Service\r
+  metadata:\r
+    name: jaeger-agent\r
+    namespace: istio-system\r
+    labels:\r
+      app: jaeger\r
+      jaeger-infra: agent-service\r
+      chart: tracing\r
+      heritage: Tiller\r
+      release: istio\r
+  spec:\r
+    ports:\r
+    - name: agent-zipkin-thrift\r
+      port: 5775\r
+      protocol: UDP\r
+      targetPort: 5775\r
+    - name: agent-compact\r
+      port: 6831\r
+      protocol: UDP\r
+      targetPort: 6831\r
+    - name: agent-binary\r
+      port: 6832\r
+      protocol: UDP\r
+      targetPort: 6832\r
+    clusterIP: None\r
+    selector:\r
+      app: jaeger\r
+\r
+\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/service.yaml\r
+apiVersion: v1\r
+kind: List\r
+metadata:\r
+  name: tracing-services\r
+  namespace: istio-system\r
+  labels:\r
+    app: jaeger\r
+    chart: tracing\r
+    heritage: Tiller\r
+    release: istio\r
+items:\r
+- apiVersion: v1\r
+  kind: Service\r
+  metadata:\r
+    name: zipkin\r
+    namespace: istio-system\r
+    labels:\r
+      app: jaeger\r
+      chart: tracing\r
+      heritage: Tiller\r
+      release: istio\r
+  spec:\r
+    type: ClusterIP\r
+    ports:\r
+      - port: 9411\r
+        targetPort: 9411\r
+        protocol: TCP\r
+        name: http\r
+    selector:\r
+      app: jaeger\r
+- apiVersion: v1\r
+  kind: Service\r
+  metadata:\r
+    name: tracing\r
+    namespace: istio-system\r
+    annotations:\r
+    labels:\r
+      app: jaeger\r
+      chart: tracing\r
+      heritage: Tiller\r
+      release: istio\r
+  spec:\r
+    ports:\r
+      - name: http-query\r
+        port: 80\r
+        protocol: TCP\r
+\r
+        targetPort: 16686\r
+\r
+    selector:\r
+      app: jaeger\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/mutatingwebhook.yaml\r
+apiVersion: admissionregistration.k8s.io/v1beta1\r
+kind: MutatingWebhookConfiguration\r
+metadata:\r
+  name: istio-sidecar-injector\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    chart: sidecarInjectorWebhook\r
+    heritage: Tiller\r
+    release: istio\r
+webhooks:\r
+  - name: sidecar-injector.istio.io\r
+    clientConfig:\r
+      service:\r
+        name: istio-sidecar-injector\r
+        namespace: istio-system\r
+        path: "/inject"\r
+      caBundle: ""\r
+    rules:\r
+      - operations: [ "CREATE" ]\r
+        apiGroups: [""]\r
+        apiVersions: ["v1"]\r
+        resources: ["pods"]\r
+    failurePolicy: Fail\r
+    namespaceSelector:\r
+      matchLabels:\r
+        istio-injection: enabled\r
+\r
+\r
+---\r
+# Source: istio/charts/galley/templates/poddisruptionbudget.yaml\r
+\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-galley\r
+  namespace: istio-system\r
+  labels:\r
+    app: galley\r
+    chart: galley\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: galley\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      app: galley\r
+      release: istio\r
+      istio: galley\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/poddisruptionbudget.yaml\r
+\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-egressgateway\r
+  namespace: istio-system\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-egressgateway\r
+    istio: egressgateway\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      release: istio\r
+      app: istio-egressgateway\r
+      istio: egressgateway\r
+---\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-ingressgateway\r
+  namespace: istio-system\r
+  labels:\r
+    chart: gateways\r
+    heritage: Tiller\r
+    release: istio\r
+    app: istio-ingressgateway\r
+    istio: ingressgateway\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      release: istio\r
+      app: istio-ingressgateway\r
+      istio: ingressgateway\r
+---\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/poddisruptionbudget.yaml\r
+\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-policy\r
+  namespace: istio-system\r
+  labels:\r
+    app: policy\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    version: 1.2.3\r
+    istio: mixer\r
+    istio-mixer-type: policy\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      app: policy\r
+      release: istio\r
+      istio: mixer\r
+      istio-mixer-type: policy\r
+---\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-telemetry\r
+  namespace: istio-system\r
+  labels:\r
+    app: telemetry\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+    version: 1.2.3\r
+    istio: mixer\r
+    istio-mixer-type: telemetry\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      app: telemetry\r
+      release: istio\r
+      istio: mixer\r
+      istio-mixer-type: telemetry\r
+---\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/poddisruptionbudget.yaml\r
+\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-pilot\r
+  namespace: istio-system\r
+  labels:\r
+    app: pilot\r
+    chart: pilot\r
+    heritage: Tiller\r
+    release: istio\r
+    istio: pilot\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      app: pilot\r
+      release: istio\r
+      istio: pilot\r
+\r
+---\r
+# Source: istio/charts/sidecarInjectorWebhook/templates/poddisruptionbudget.yaml\r
+\r
+apiVersion: policy/v1beta1\r
+kind: PodDisruptionBudget\r
+metadata:\r
+  name: istio-sidecar-injector\r
+  namespace: istio-system\r
+  labels:\r
+    app: sidecarInjectorWebhook\r
+    release: istio\r
+    istio: sidecar-injector\r
+spec:\r
+\r
+  minAvailable: 1\r
+  selector:\r
+    matchLabels:\r
+      app: sidecarInjectorWebhook\r
+      release: istio\r
+      istio: sidecar-injector\r
+---\r
+# Source: istio/charts/galley/templates/validatingwebhookconfiguration.yaml.tpl\r
+\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/autoscale.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/gateways/templates/preconfigured.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/grafana-ports-mtls.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/ingress.yaml\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/pvc.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/grafana/templates/tests/test-grafana-connection.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/ingress.yaml\r
+\r
+---\r
+# Source: istio/charts/kiali/templates/tests/test-kiali-connection.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/autoscale.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/autoscale.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/pilot/templates/meshexpansion.yaml\r
+\r
+\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/ingress.yaml\r
+\r
+---\r
+# Source: istio/charts/prometheus/templates/tests/test-prometheus-connection.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/security/templates/enable-mesh-mtls.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/security/templates/enable-mesh-permissive.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/security/templates/meshexpansion.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/security/templates/tests/test-citadel-connection.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/deployment-zipkin.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/ingress.yaml\r
+\r
+---\r
+# Source: istio/charts/tracing/templates/tests/test-tracing-connection.yaml\r
+\r
+\r
+---\r
+# Source: istio/templates/endpoints.yaml\r
+\r
+\r
+---\r
+# Source: istio/templates/install-custom-resources.sh.tpl\r
+\r
+\r
+---\r
+# Source: istio/templates/service.yaml\r
+\r
+\r
+---\r
+# Source: istio/charts/mixer/templates/config.yaml\r
+\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: attributemanifest\r
+metadata:\r
+  name: istioproxy\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  attributes:\r
+    origin.ip:\r
+      valueType: IP_ADDRESS\r
+    origin.uid:\r
+      valueType: STRING\r
+    origin.user:\r
+      valueType: STRING\r
+    request.headers:\r
+      valueType: STRING_MAP\r
+    request.id:\r
+      valueType: STRING\r
+    request.host:\r
+      valueType: STRING\r
+    request.method:\r
+      valueType: STRING\r
+    request.path:\r
+      valueType: STRING\r
+    request.url_path:\r
+      valueType: STRING\r
+    request.query_params:\r
+      valueType: STRING_MAP\r
+    request.reason:\r
+      valueType: STRING\r
+    request.referer:\r
+      valueType: STRING\r
+    request.scheme:\r
+      valueType: STRING\r
+    request.total_size:\r
+      valueType: INT64\r
+    request.size:\r
+      valueType: INT64\r
+    request.time:\r
+      valueType: TIMESTAMP\r
+    request.useragent:\r
+      valueType: STRING\r
+    response.code:\r
+      valueType: INT64\r
+    response.duration:\r
+      valueType: DURATION\r
+    response.headers:\r
+      valueType: STRING_MAP\r
+    response.total_size:\r
+      valueType: INT64\r
+    response.size:\r
+      valueType: INT64\r
+    response.time:\r
+      valueType: TIMESTAMP\r
+    response.grpc_status:\r
+      valueType: STRING\r
+    response.grpc_message:\r
+      valueType: STRING\r
+    source.uid:\r
+      valueType: STRING\r
+    source.user: # DEPRECATED\r
+      valueType: STRING\r
+    source.principal:\r
+      valueType: STRING\r
+    destination.uid:\r
+      valueType: STRING\r
+    destination.principal:\r
+      valueType: STRING\r
+    destination.port:\r
+      valueType: INT64\r
+    connection.event:\r
+      valueType: STRING\r
+    connection.id:\r
+      valueType: STRING\r
+    connection.received.bytes:\r
+      valueType: INT64\r
+    connection.received.bytes_total:\r
+      valueType: INT64\r
+    connection.sent.bytes:\r
+      valueType: INT64\r
+    connection.sent.bytes_total:\r
+      valueType: INT64\r
+    connection.duration:\r
+      valueType: DURATION\r
+    connection.mtls:\r
+      valueType: BOOL\r
+    connection.requested_server_name:\r
+      valueType: STRING\r
+    context.protocol:\r
+      valueType: STRING\r
+    context.proxy_error_code:\r
+      valueType: STRING\r
+    context.timestamp:\r
+      valueType: TIMESTAMP\r
+    context.time:\r
+      valueType: TIMESTAMP\r
+    # Deprecated, kept for compatibility\r
+    context.reporter.local:\r
+      valueType: BOOL\r
+    context.reporter.kind:\r
+      valueType: STRING\r
+    context.reporter.uid:\r
+      valueType: STRING\r
+    api.service:\r
+      valueType: STRING\r
+    api.version:\r
+      valueType: STRING\r
+    api.operation:\r
+      valueType: STRING\r
+    api.protocol:\r
+      valueType: STRING\r
+    request.auth.principal:\r
+      valueType: STRING\r
+    request.auth.audiences:\r
+      valueType: STRING\r
+    request.auth.presenter:\r
+      valueType: STRING\r
+    request.auth.claims:\r
+      valueType: STRING_MAP\r
+    request.auth.raw_claims:\r
+      valueType: STRING\r
+    request.api_key:\r
+      valueType: STRING\r
+    rbac.permissive.response_code:\r
+      valueType: STRING\r
+    rbac.permissive.effective_policy_id:\r
+      valueType: STRING\r
+    check.error_code:\r
+      valueType: INT64\r
+    check.error_message:\r
+      valueType: STRING\r
+    check.cache_hit:\r
+      valueType: BOOL\r
+    quota.cache_hit:\r
+      valueType: BOOL\r
+\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: attributemanifest\r
+metadata:\r
+  name: kubernetes\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  attributes:\r
+    source.ip:\r
+      valueType: IP_ADDRESS\r
+    source.labels:\r
+      valueType: STRING_MAP\r
+    source.metadata:\r
+      valueType: STRING_MAP\r
+    source.name:\r
+      valueType: STRING\r
+    source.namespace:\r
+      valueType: STRING\r
+    source.owner:\r
+      valueType: STRING\r
+    source.serviceAccount:\r
+      valueType: STRING\r
+    source.services:\r
+      valueType: STRING\r
+    source.workload.uid:\r
+      valueType: STRING\r
+    source.workload.name:\r
+      valueType: STRING\r
+    source.workload.namespace:\r
+      valueType: STRING\r
+    destination.ip:\r
+      valueType: IP_ADDRESS\r
+    destination.labels:\r
+      valueType: STRING_MAP\r
+    destination.metadata:\r
+      valueType: STRING_MAP\r
+    destination.owner:\r
+      valueType: STRING\r
+    destination.name:\r
+      valueType: STRING\r
+    destination.container.name:\r
+      valueType: STRING\r
+    destination.namespace:\r
+      valueType: STRING\r
+    destination.service.uid:\r
+      valueType: STRING\r
+    destination.service.name:\r
+      valueType: STRING\r
+    destination.service.namespace:\r
+      valueType: STRING\r
+    destination.service.host:\r
+      valueType: STRING\r
+    destination.serviceAccount:\r
+      valueType: STRING\r
+    destination.workload.uid:\r
+      valueType: STRING\r
+    destination.workload.name:\r
+      valueType: STRING\r
+    destination.workload.namespace:\r
+      valueType: STRING\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: handler\r
+metadata:\r
+  name: stdio\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledAdapter: stdio\r
+  params:\r
+    outputAsJson: true\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: accesslog\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: logentry\r
+  params:\r
+    severity: '"Info"'\r
+    timestamp: request.time\r
+    variables:\r
+      sourceIp: source.ip | ip("0.0.0.0")\r
+      sourceApp: source.labels["app"] | ""\r
+      sourcePrincipal: source.principal | ""\r
+      sourceName: source.name | ""\r
+      sourceWorkload: source.workload.name | ""\r
+      sourceNamespace: source.namespace | ""\r
+      sourceOwner: source.owner | ""\r
+      destinationApp: destination.labels["app"] | ""\r
+      destinationIp: destination.ip | ip("0.0.0.0")\r
+      destinationServiceHost: destination.service.host | ""\r
+      destinationWorkload: destination.workload.name | ""\r
+      destinationName: destination.name | ""\r
+      destinationNamespace: destination.namespace | ""\r
+      destinationOwner: destination.owner | ""\r
+      destinationPrincipal: destination.principal | ""\r
+      apiClaims: request.auth.raw_claims | ""\r
+      apiKey: request.api_key | request.headers["x-api-key"] | ""\r
+      protocol: request.scheme | context.protocol | "http"\r
+      method: request.method | ""\r
+      url: request.path | ""\r
+      responseCode: response.code | 0\r
+      responseFlags: context.proxy_error_code | ""\r
+      responseSize: response.size | 0\r
+      permissiveResponseCode: rbac.permissive.response_code | "none"\r
+      permissiveResponsePolicyID: rbac.permissive.effective_policy_id | "none"\r
+      requestSize: request.size | 0\r
+      requestId: request.headers["x-request-id"] | ""\r
+      clientTraceId: request.headers["x-client-trace-id"] | ""\r
+      latency: response.duration | "0ms"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      requestedServerName: connection.requested_server_name | ""\r
+      userAgent: request.useragent | ""\r
+      responseTimestamp: response.time\r
+      receivedBytes: request.total_size | 0\r
+      sentBytes: response.total_size | 0\r
+      referer: request.referer | ""\r
+      httpAuthority: request.headers[":authority"] | request.host | ""\r
+      xForwardedFor: request.headers["x-forwarded-for"] | "0.0.0.0"\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      grpcStatus: response.grpc_status | ""\r
+      grpcMessage: response.grpc_message | ""\r
+    monitored_resource_type: '"global"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: tcpaccesslog\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: logentry\r
+  params:\r
+    severity: '"Info"'\r
+    timestamp: context.time | timestamp("2017-01-01T00:00:00Z")\r
+    variables:\r
+      connectionEvent: connection.event | ""\r
+      sourceIp: source.ip | ip("0.0.0.0")\r
+      sourceApp: source.labels["app"] | ""\r
+      sourcePrincipal: source.principal | ""\r
+      sourceName: source.name | ""\r
+      sourceWorkload: source.workload.name | ""\r
+      sourceNamespace: source.namespace | ""\r
+      sourceOwner: source.owner | ""\r
+      destinationApp: destination.labels["app"] | ""\r
+      destinationIp: destination.ip | ip("0.0.0.0")\r
+      destinationServiceHost: destination.service.host | ""\r
+      destinationWorkload: destination.workload.name | ""\r
+      destinationName: destination.name | ""\r
+      destinationNamespace: destination.namespace | ""\r
+      destinationOwner: destination.owner | ""\r
+      destinationPrincipal: destination.principal | ""\r
+      protocol: context.protocol | "tcp"\r
+      connectionDuration: connection.duration | "0ms"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      requestedServerName: connection.requested_server_name | ""\r
+      receivedBytes: connection.received.bytes | 0\r
+      sentBytes: connection.sent.bytes | 0\r
+      totalReceivedBytes: connection.received.bytes_total | 0\r
+      totalSentBytes: connection.sent.bytes_total | 0\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      responseFlags: context.proxy_error_code | ""\r
+    monitored_resource_type: '"global"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: stdio\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "http" || context.protocol == "grpc"\r
+  actions:\r
+  - handler: stdio\r
+    instances:\r
+    - accesslog\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: stdiotcp\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "tcp"\r
+  actions:\r
+  - handler: stdio\r
+    instances:\r
+    - tcpaccesslog\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: requestcount\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: "1"\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      request_protocol: api.protocol | context.protocol | "unknown"\r
+      response_code: response.code | 200\r
+      response_flags: context.proxy_error_code | "-"\r
+      permissive_response_code: rbac.permissive.response_code | "none"\r
+      permissive_response_policyid: rbac.permissive.effective_policy_id | "none"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: requestduration\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: response.duration | "0ms"\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      request_protocol: api.protocol | context.protocol | "unknown"\r
+      response_code: response.code | 200\r
+      response_flags: context.proxy_error_code | "-"\r
+      permissive_response_code: rbac.permissive.response_code | "none"\r
+      permissive_response_policyid: rbac.permissive.effective_policy_id | "none"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: requestsize\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: request.size | 0\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      request_protocol: api.protocol | context.protocol | "unknown"\r
+      response_code: response.code | 200\r
+      response_flags: context.proxy_error_code | "-"\r
+      permissive_response_code: rbac.permissive.response_code | "none"\r
+      permissive_response_policyid: rbac.permissive.effective_policy_id | "none"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: responsesize\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: response.size | 0\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      request_protocol: api.protocol | context.protocol | "unknown"\r
+      response_code: response.code | 200\r
+      response_flags: context.proxy_error_code | "-"\r
+      permissive_response_code: rbac.permissive.response_code | "none"\r
+      permissive_response_policyid: rbac.permissive.effective_policy_id | "none"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: tcpbytesent\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: connection.sent.bytes | 0\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      response_flags: context.proxy_error_code | "-"\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: tcpbytereceived\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: connection.received.bytes | 0\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.host | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      response_flags: context.proxy_error_code | "-"\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: tcpconnectionsopened\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: "1"\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.name | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      response_flags: context.proxy_error_code | "-"\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: tcpconnectionsclosed\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: metric\r
+  params:\r
+    value: "1"\r
+    dimensions:\r
+      reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination")\r
+      source_workload: source.workload.name | "unknown"\r
+      source_workload_namespace: source.workload.namespace | "unknown"\r
+      source_principal: source.principal | "unknown"\r
+      source_app: source.labels["app"] | "unknown"\r
+      source_version: source.labels["version"] | "unknown"\r
+      destination_workload: destination.workload.name | "unknown"\r
+      destination_workload_namespace: destination.workload.namespace | "unknown"\r
+      destination_principal: destination.principal | "unknown"\r
+      destination_app: destination.labels["app"] | "unknown"\r
+      destination_version: destination.labels["version"] | "unknown"\r
+      destination_service: destination.service.name | "unknown"\r
+      destination_service_name: destination.service.name | "unknown"\r
+      destination_service_namespace: destination.service.namespace | "unknown"\r
+      connection_security_policy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none"))\r
+      response_flags: context.proxy_error_code | "-"\r
+    monitored_resource_type: '"UNSPECIFIED"'\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: handler\r
+metadata:\r
+  name: prometheus\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledAdapter: prometheus\r
+  params:\r
+    metricsExpirationPolicy:\r
+      metricsExpiryDuration: "10m"\r
+    metrics:\r
+    - name: requests_total\r
+      instance_name: requestcount.instance.istio-system\r
+      kind: COUNTER\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - request_protocol\r
+      - response_code\r
+      - response_flags\r
+      - permissive_response_code\r
+      - permissive_response_policyid\r
+      - connection_security_policy\r
+    - name: request_duration_seconds\r
+      instance_name: requestduration.instance.istio-system\r
+      kind: DISTRIBUTION\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - request_protocol\r
+      - response_code\r
+      - response_flags\r
+      - permissive_response_code\r
+      - permissive_response_policyid\r
+      - connection_security_policy\r
+      buckets:\r
+        explicit_buckets:\r
+          bounds: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]\r
+    - name: request_bytes\r
+      instance_name: requestsize.instance.istio-system\r
+      kind: DISTRIBUTION\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - request_protocol\r
+      - response_code\r
+      - response_flags\r
+      - permissive_response_code\r
+      - permissive_response_policyid\r
+      - connection_security_policy\r
+      buckets:\r
+        exponentialBuckets:\r
+          numFiniteBuckets: 8\r
+          scale: 1\r
+          growthFactor: 10\r
+    - name: response_bytes\r
+      instance_name: responsesize.instance.istio-system\r
+      kind: DISTRIBUTION\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - request_protocol\r
+      - response_code\r
+      - response_flags\r
+      - permissive_response_code\r
+      - permissive_response_policyid\r
+      - connection_security_policy\r
+      buckets:\r
+        exponentialBuckets:\r
+          numFiniteBuckets: 8\r
+          scale: 1\r
+          growthFactor: 10\r
+    - name: tcp_sent_bytes_total\r
+      instance_name: tcpbytesent.instance.istio-system\r
+      kind: COUNTER\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - connection_security_policy\r
+      - response_flags\r
+    - name: tcp_received_bytes_total\r
+      instance_name: tcpbytereceived.instance.istio-system\r
+      kind: COUNTER\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - connection_security_policy\r
+      - response_flags\r
+    - name: tcp_connections_opened_total\r
+      instance_name: tcpconnectionsopened.instance.istio-system\r
+      kind: COUNTER\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - connection_security_policy\r
+      - response_flags\r
+    - name: tcp_connections_closed_total\r
+      instance_name: tcpconnectionsclosed.instance.istio-system\r
+      kind: COUNTER\r
+      label_names:\r
+      - reporter\r
+      - source_app\r
+      - source_principal\r
+      - source_workload\r
+      - source_workload_namespace\r
+      - source_version\r
+      - destination_app\r
+      - destination_principal\r
+      - destination_workload\r
+      - destination_workload_namespace\r
+      - destination_version\r
+      - destination_service\r
+      - destination_service_name\r
+      - destination_service_namespace\r
+      - connection_security_policy\r
+      - response_flags\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: promhttp\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: (context.protocol == "http" || context.protocol == "grpc") && (match((request.useragent | "-"), "kube-probe*") == false) && (match((request.useragent | "-"), "Prometheus*") == false)\r
+  actions:\r
+  - handler: prometheus\r
+    instances:\r
+    - requestcount\r
+    - requestduration\r
+    - requestsize\r
+    - responsesize\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: promtcp\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "tcp"\r
+  actions:\r
+  - handler: prometheus\r
+    instances:\r
+    - tcpbytesent\r
+    - tcpbytereceived\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: promtcpconnectionopen\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "tcp" && ((connection.event | "na") == "open")\r
+  actions:\r
+  - handler: prometheus\r
+    instances:\r
+    - tcpconnectionsopened\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: promtcpconnectionclosed\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "tcp" && ((connection.event | "na") == "close")\r
+  actions:\r
+  - handler: prometheus\r
+    instances:\r
+    - tcpconnectionsclosed\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: handler\r
+metadata:\r
+  name: kubernetesenv\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledAdapter: kubernetesenv\r
+  params:\r
+    # when running from mixer root, use the following config after adding a\r
+    # symbolic link to a kubernetes config file via:\r
+    #\r
+    # $ ln -s ~/.kube/config mixer/adapter/kubernetes/kubeconfig\r
+    #\r
+    # kubeconfig_path: "mixer/adapter/kubernetes/kubeconfig"\r
+\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: kubeattrgenrulerule\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  actions:\r
+  - handler: kubernetesenv\r
+    instances:\r
+    - attributes\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: rule\r
+metadata:\r
+  name: tcpkubeattrgenrulerule\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  match: context.protocol == "tcp"\r
+  actions:\r
+  - handler: kubernetesenv\r
+    instances:\r
+    - attributes\r
+---\r
+apiVersion: "config.istio.io/v1alpha2"\r
+kind: instance\r
+metadata:\r
+  name: attributes\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  compiledTemplate: kubernetes\r
+  params:\r
+    # Pass the required attribute data to the adapter\r
+    source_uid: source.uid | ""\r
+    source_ip: source.ip | ip("0.0.0.0") # default to unspecified ip addr\r
+    destination_uid: destination.uid | ""\r
+    destination_port: destination.port | 0\r
+  attributeBindings:\r
+    # Fill the new attributes from the adapter produced output.\r
+    # $out refers to an instance of OutputTemplate message\r
+    source.ip: $out.source_pod_ip | ip("0.0.0.0")\r
+    source.uid: $out.source_pod_uid | "unknown"\r
+    source.labels: $out.source_labels | emptyStringMap()\r
+    source.name: $out.source_pod_name | "unknown"\r
+    source.namespace: $out.source_namespace | "default"\r
+    source.owner: $out.source_owner | "unknown"\r
+    source.serviceAccount: $out.source_service_account_name | "unknown"\r
+    source.workload.uid: $out.source_workload_uid | "unknown"\r
+    source.workload.name: $out.source_workload_name | "unknown"\r
+    source.workload.namespace: $out.source_workload_namespace | "unknown"\r
+    destination.ip: $out.destination_pod_ip | ip("0.0.0.0")\r
+    destination.uid: $out.destination_pod_uid | "unknown"\r
+    destination.labels: $out.destination_labels | emptyStringMap()\r
+    destination.name: $out.destination_pod_name | "unknown"\r
+    destination.container.name: $out.destination_container_name | "unknown"\r
+    destination.namespace: $out.destination_namespace | "default"\r
+    destination.owner: $out.destination_owner | "unknown"\r
+    destination.serviceAccount: $out.destination_service_account_name | "unknown"\r
+    destination.workload.uid: $out.destination_workload_uid | "unknown"\r
+    destination.workload.name: $out.destination_workload_name | "unknown"\r
+    destination.workload.namespace: $out.destination_workload_namespace | "unknown"\r
+---\r
+# Configuration needed by Mixer.\r
+# Mixer cluster is delivered via CDS\r
+# Specify mixer cluster settings\r
+apiVersion: networking.istio.io/v1alpha3\r
+kind: DestinationRule\r
+metadata:\r
+  name: istio-policy\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  host: istio-policy.istio-system.svc.cluster.local\r
+  trafficPolicy:\r
+    connectionPool:\r
+      http:\r
+        http2MaxRequests: 10000\r
+        maxRequestsPerConnection: 10000\r
+---\r
+apiVersion: networking.istio.io/v1alpha3\r
+kind: DestinationRule\r
+metadata:\r
+  name: istio-telemetry\r
+  namespace: istio-system\r
+  labels:\r
+    app: mixer\r
+    chart: mixer\r
+    heritage: Tiller\r
+    release: istio\r
+spec:\r
+  host: istio-telemetry.istio-system.svc.cluster.local\r
+  trafficPolicy:\r
+    connectionPool:\r
+      http:\r
+        http2MaxRequests: 10000\r
+        maxRequestsPerConnection: 10000\r
+---\r
+\r
diff --git a/src/foundation/service_mesh/Istio/istio-inject-configmap-1.1.7.yaml b/src/foundation/service_mesh/Istio/istio-inject-configmap-1.1.7.yaml
new file mode 100644 (file)
index 0000000..3dd3034
--- /dev/null
@@ -0,0 +1,247 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: istio-sidecar-injector
+  namespace: istio-system
+  labels:
+    app: istio
+    chart: istio
+    heritage: Tiller
+    release: istio
+    istio: sidecar-injector
+data:
+  config: |-
+    policy: enabled
+    template: |-
+      rewriteAppHTTPProbe: false
+      initContainers:
+      [[ if ne (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) "NONE" ]]
+      - name: istio-init
+        image: "iecedge/proxy_init-arm64:1.2.3"
+        args:
+        - "-p"
+        - [[ .MeshConfig.ProxyListenPort ]]
+        - "-u"
+        - 1337
+        - "-m"
+        - [[ annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode ]]
+        - "-i"
+        - "[[ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges`  "*"  ]]"
+        - "-x"
+        - "[[ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges`  ""  ]]"
+        - "-b"
+        - "[[ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` (includeInboundPorts .Spec.Containers) ]]"
+        - "-d"
+        - "[[ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port`  15020 ) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts`  "" ) ]]"
+        [[ if (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces`) -]]
+        - "-k"
+        - "[[ index .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces` ]]"
+        [[ end -]]
+        imagePullPolicy: IfNotPresent
+        resources:
+          requests:
+            cpu: 10m
+            memory: 10Mi
+          limits:
+            cpu: 100m
+            memory: 50Mi
+        securityContext:
+          runAsUser: 0
+          runAsNonRoot: false
+          capabilities:
+            add:
+            - NET_ADMIN
+        restartPolicy: Always
+      [[ end -]]
+      containers:
+      - name: istio-proxy
+        image: [[ annotation .ObjectMeta `sidecar.istio.io/proxyImage`  "iecedge/proxyv2-arm64:1.2.3"  ]]
+        ports:
+        - containerPort: 15090
+          protocol: TCP
+          name: http-envoy-prom
+        args:
+        - proxy
+        - sidecar
+        - --domain
+        - $(POD_NAMESPACE).svc.cluster.local
+        - --configPath
+        - [[ .ProxyConfig.ConfigPath ]]
+        - --binaryPath
+        - [[ .ProxyConfig.BinaryPath ]]
+        - --serviceCluster
+        [[ if ne "" (index .ObjectMeta.Labels "app") -]]
+        - [[ index .ObjectMeta.Labels "app" ]].$(POD_NAMESPACE)
+        [[ else -]]
+        - [[ valueOrDefault .DeploymentMeta.Name "istio-proxy" ]].[[ valueOrDefault .DeploymentMeta.Namespace "default" ]]
+        [[ end -]]
+        - --drainDuration
+        - [[ formatDuration .ProxyConfig.DrainDuration ]]
+        - --parentShutdownDuration
+        - [[ formatDuration .ProxyConfig.ParentShutdownDuration ]]
+        - --discoveryAddress
+        - [[ annotation .ObjectMeta `sidecar.istio.io/discoveryAddress` .ProxyConfig.DiscoveryAddress ]]
+        - --zipkinAddress
+        - [[ .ProxyConfig.GetTracing.GetZipkin.GetAddress ]]
+        - --connectTimeout
+        - [[ formatDuration .ProxyConfig.ConnectTimeout ]]
+        - --proxyAdminPort
+        - [[ .ProxyConfig.ProxyAdminPort ]]
+        [[ if gt .ProxyConfig.Concurrency 0 -]]
+        - --concurrency
+        - [[ .ProxyConfig.Concurrency ]]
+        [[ end -]]
+        - --controlPlaneAuthPolicy
+        - [[ annotation .ObjectMeta `sidecar.istio.io/controlPlaneAuthPolicy` .ProxyConfig.ControlPlaneAuthPolicy ]]
+      [[- if (ne (annotation .ObjectMeta `status.sidecar.istio.io/port`  15020 ) "0") ]]
+        - --statusPort
+        - [[ annotation .ObjectMeta `status.sidecar.istio.io/port`  15020  ]]
+        - --applicationPorts
+        - "[[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/applicationPorts` (applicationPorts .Spec.Containers) ]]"
+      [[- end ]]
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        - name: INSTANCE_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+
+        - name: ISTIO_META_POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: ISTIO_META_CONFIG_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        - name: ISTIO_META_INTERCEPTION_MODE
+          value: [[ or (index .ObjectMeta.Annotations "sidecar.istio.io/interceptionMode") .ProxyConfig.InterceptionMode.String ]]
+        [[ if .ObjectMeta.Annotations ]]
+        - name: ISTIO_METAJSON_ANNOTATIONS
+          value: |
+                 [[ toJSON .ObjectMeta.Annotations ]]
+        [[ end ]]
+        [[ if .ObjectMeta.Labels ]]
+        - name: ISTIO_METAJSON_LABELS
+          value: |
+                 [[ toJSON .ObjectMeta.Labels ]]
+        [[ end ]]
+        [[- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) ]]
+        - name: ISTIO_BOOTSTRAP_OVERRIDE
+          value: "/etc/istio/custom-bootstrap/custom_bootstrap.json"
+        [[- end ]]
+        imagePullPolicy: IfNotPresent
+        [[ if (ne (annotation .ObjectMeta `status.sidecar.istio.io/port`  15020 ) "0") ]]
+        readinessProbe:
+          httpGet:
+            path: /healthz/ready
+            port: [[ annotation .ObjectMeta `status.sidecar.istio.io/port`  15020  ]]
+          initialDelaySeconds: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/initialDelaySeconds`  1  ]]
+          periodSeconds: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/periodSeconds`  2  ]]
+          failureThreshold: [[ annotation .ObjectMeta `readiness.status.sidecar.istio.io/failureThreshold`  30  ]]
+        [[ end -]]securityContext:
+          readOnlyRootFilesystem: true
+          [[ if eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) "TPROXY" -]]
+          capabilities:
+            add:
+            - NET_ADMIN
+          runAsGroup: 1337
+          [[ else -]]
+
+          runAsUser: 1337
+          [[- end ]]
+        resources:
+          [[ if or (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -]]
+          requests:
+            [[ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) -]]
+            cpu: "[[ index .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU` ]]"
+            [[ end ]]
+            [[ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -]]
+            memory: "[[ index .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory` ]]"
+            [[ end ]]
+        [[ else -]]
+          limits:
+            cpu: 2000m
+            memory: 1024Mi
+          requests:
+            cpu: 10m
+            memory: 40Mi
+
+        [[ end -]]
+        volumeMounts:
+        [[- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) ]]
+        - mountPath: /etc/istio/custom-bootstrap
+          name: custom-bootstrap-volume
+        [[- end ]]
+        - mountPath: /etc/istio/proxy
+          name: istio-envoy
+        - mountPath: /etc/certs/
+          name: istio-certs
+          readOnly: true
+          [[- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount` ]]
+          [[ range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount`) ]]
+        - name: "[[ $index ]]"
+          [[ toYaml $value | indent 4 ]]
+          [[ end ]]
+          [[- end ]]
+        - mountPath: /var/run/dikastes
+          name: dikastes-sock
+      - name: dikastes
+        image: calico/dikastes:v3.3.6
+        args: ["/dikastes", "server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"]
+        livenessProbe:
+          exec:
+            command:
+            - /healthz
+            - liveness
+          initialDelaySeconds: 3
+          periodSeconds: 3
+        readinessProbe:
+          exec:
+            command:
+            - /healthz
+            - readiness
+          initialDelaySeconds: 3
+          periodSeconds: 3
+        volumeMounts:
+        - mountPath: /var/run/dikastes
+          name: dikastes-sock
+        - mountPath: /var/run/felix
+          name: felix-sync
+      volumes:
+      [[- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) ]]
+      - name: custom-bootstrap-volume
+        configMap:
+          name: [[ annotation .ObjectMeta `sidecar.istio.io/bootstrapOverride` `` ]]
+      [[- end ]]
+      - emptyDir:
+          medium: Memory
+        name: istio-envoy
+      - name: istio-certs
+        secret:
+          optional: true
+          [[ if eq .Spec.ServiceAccountName "" -]]
+          secretName: istio.default
+          [[ else -]]
+          secretName: [[ printf "istio.%s" .Spec.ServiceAccountName ]]
+          [[ end -]]
+        [[- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolume` ]]
+        [[ range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolume`) ]]
+      - name: "[[ $index ]]"
+        [[ toYaml $value | indent 2 ]]
+        [[ end ]]
+        [[ end ]]
+      - name: dikastes-sock
+        emptyDir:
+          medium: Memory
+      - name: felix-sync
+        flexVolume:
+          driver: nodeagent/uds
diff --git a/src/foundation/service_mesh/README.md b/src/foundation/service_mesh/README.md
new file mode 100644 (file)
index 0000000..095564c
--- /dev/null
@@ -0,0 +1,15 @@
+# Service Mesh on IEC
+The Service Mesh, the most popular word in Cloud Native technology area, is used to
+describe the network of Micro-Services. It makes up such applications and the
+interactions between them.
+Now, the Istio--the hottest Service-Mesh software--has been supported by IEC. In
+this document, we will introduce how to deploy it. For details, please refer to
+[Istio](https://istio.io).
+
+## Deploy Service-Mesh
+
+  ./install_Istio.sh
+
+## Verify Service-Mesh
+Please refer to README.md in iec/src/use_case/service_mesh/bookinfo folder to get
+more information.
diff --git a/src/foundation/service_mesh/install_Istio.sh b/src/foundation/service_mesh/install_Istio.sh
new file mode 100755 (executable)
index 0000000..a7feb35
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/bash
+set -x
+
+function wait_for {
+  # Execute in a subshell to prevent local variable override during recursion
+  (
+    local total_attempts=$1; shift
+    local cmdstr=$*
+    local sleep_time=2
+    echo -e "\n[wait_for] Waiting for cmd to return success: ${cmdstr}"
+    # shellcheck disable=SC2034
+    for attempt in $(seq "${total_attempts}"); do
+      echo "[wait_for] Attempt ${attempt}/${total_attempts%.*} for: ${cmdstr}"
+      # shellcheck disable=SC2015
+      eval "${cmdstr}" && echo "[wait_for] OK: ${cmdstr}" && return 0 || true
+      sleep "${sleep_time}"
+    done
+    echo "[wait_for] ERROR: Failed after max attempts: ${cmdstr}"
+    return 1
+  )
+}
+
+
+for i in Istio/init/crd*yaml; do kubectl apply -f $i; done
+
+kubectl apply -f Istio/istio-demo-arm64.yaml
+
+# Waiting for Istio ready
+wait_for 100 'test $(kubectl get pods -n istio-system | grep -ce "Running") -eq 12'
+
+#Apply the following ConfigMap to enable injection of Dikastes alongside Envoy.(injection ConfigMap)
+kubectl apply -f Istio/istio-inject-configmap-1.1.7.yaml
+
+#Enable the default namespace auto inject
+kubectl label namespace default istio-injection=enabled --overwrite
+kubectl get namespace -L istio-injection
diff --git a/src/type3_AndroidCloud/README.rst b/src/type3_AndroidCloud/README.rst
new file mode 100644 (file)
index 0000000..80a46bd
--- /dev/null
@@ -0,0 +1,40 @@
+..
+      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.
+
+      Convention for heading levels in Integrated Edge Cloud documentation:
+
+      =======  Heading 0 (reserved for the title in a document)
+      -------  Heading 1
+      ~~~~~~~  Heading 2
+      +++++++  Heading 3
+      '''''''  Heading 4
+
+      Avoid deeper levels because they do not render well.
+
+
+=================================
+IEC Type3 Android Cloud Native Overview
+=================================
+
+This document provides a general description about the IEC Type3_--Android Cloud Native.
+.. _Type3: https://wiki.akraino.org/display/AK/IEC+Type+3%3A+Android+cloud+native+applications+on+Arm+servers+in+edge
+
+
+For issues or anything on the reference foundation stack of IEC, you could contact:
+
+Trevor Tao: trevor.tao@arm.com
+
+Jingzhao Ni: jingzhao.ni@arm.com
+
+Jianlin Lv:  jianlin.lv@arm.com
+
diff --git a/src/type5_SmartNIC/README.rst b/src/type5_SmartNIC/README.rst
new file mode 100644 (file)
index 0000000..8d6dca0
--- /dev/null
@@ -0,0 +1,40 @@
+..
+      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.
+
+      Convention for heading levels in Integrated Edge Cloud documentation:
+
+      =======  Heading 0 (reserved for the title in a document)
+      -------  Heading 1
+      ~~~~~~~  Heading 2
+      +++++++  Heading 3
+      '''''''  Heading 4
+
+      Avoid deeper levels because they do not render well.
+
+
+=================================
+IEC Type5 SmartNIC Overview
+=================================
+
+This document provides a general description about the IEC Type5_--SmartNIC for IEC.
+.. _Type5: https://wiki.akraino.org/display/AK/IEC+Type+5%3A+SmartNIC+for+Integrated+Edge+Cloud+%28IEC%29+Blueprint+Family
+
+
+For issues or anything on the reference foundation stack of IEC, you could contact:
+
+Trevor Tao: trevor.tao@arm.com
+
+Jingzhao Ni: jingzhao.ni@arm.com
+
+Jianlin Lv:  jianlin.lv@arm.com
+
index cf7a61b..9056387 160000 (submodule)
@@ -1 +1 @@
-Subproject commit cf7a61bc13ccd58ff9c131d46c44b85aee87bc2a
+Subproject commit 90563870dbfa8aa9636fb0c7f6c6eb7940307f70
index 6aa728b..2b4e13e 100755 (executable)
@@ -5,60 +5,32 @@ set -ex
 
 basepath=$(cd "$(dirname "$0")"; pwd)
 
-# Using opencord automation-tools from the cord-6.1 maintenance branch
-AUTO_TOOLS_GIT="https://github.com/opencord/automation-tools.git"
-AUTO_TOOLS_VER=${AUTO_TOOLS_VER:-cord-6.1}
-
 export M=/tmp/milestones
 export SEBAVALUE=
 export WORKSPACE=${HOME}
 
-mkdir -p ${M} "${WORKSPACE}/cord/test"
+# Using opencord automation-tools from the cord-6.1 maintenance branch
+AUTO_TOOLS="${WORKSPACE}/automation-tools"
+AUTO_TOOLS_REPO="https://github.com/iecedge/automation-tools.git"
+AUTO_TOOLS_REV=${AUTO_TOOLS_VER:-cord-7.0-arm64}
+
+rm -rf "${M}"
+mkdir -p "${M}" "${WORKSPACE}/cord/test"
 
 # Update helm-charts submdule needed later
 # ignore subproject commit and use latest remote version
 git submodule update --init --remote "${basepath}/../../src_repo/helm-charts"
 
-cd "${WORKSPACE}"
-test -d automation-tools || git clone "${AUTO_TOOLS_GIT}"
-cd "${WORKSPACE}/automation-tools" && git checkout "${AUTO_TOOLS_VER}"
-
-# Fake the setup phase so that portcheck.sh is not called
-# also install some required packages
-sudo apt install -y httpie jq software-properties-common bridge-utils make
-# add iptbles rule needed to forward DHCP packets comming from the RG_POD
-sudo iptables -P FORWARD ACCEPT
-touch "${M}/setup"
-
-# Skip helm installation if it already exists and fake /usr/local/bin/helm
-if xhelm=$(command -v helm)
-then
-  if [ "${xhelm}" != "/usr/local/bin/helm" ]
-  then
-     echo "helm is installed at ${xhelm}; symlinking to /usr/local/bin/helm"
-     mkdir -p /usr/local/bin/ || true
-     sudo ln -sf "${xhelm}" /usr/local/bin/helm
-  fi
-else
-  echo "helm is not installed"
-fi
+test -d "${AUTO_TOOLS}" || git clone "${AUTO_TOOLS_REPO}" "${AUTO_TOOLS}"
+(cd "${AUTO_TOOLS}"; git checkout "${AUTO_TOOLS_REV}")
 
 # Faking helm-charts repo clone to our own git submodule if not already there
 CHARTS="${WORKSPACE}/cord/helm-charts"
-test -d "${CHARTS}" || test -h "${CHARTS}" || \
+test -d "${CHARTS}" || test -L "${CHARTS}" || \
     ln -s "${basepath}/../../src_repo/helm-charts" "${CHARTS}"
 
-# Fake SiaB components setup since they are already installed
-milestones="kubeadm helm-init kafka kafka-running etcd-operator-ready voltha \
-            voltha-running nem onos siab"
-
-for m in ${milestones}
-do
-  echo "Faking SiaB milestone ${M}/${m}"
-  test -f "${M}/${m}" || touch "${M}/${m}"
-done
+cd "${AUTO_TOOLS}/seba-in-a-box"
+. env.sh
 
 # Now calling make, to install PONSim
-cd "${WORKSPACE}/automation-tools/seba-in-a-box"
 make stable
-
diff --git a/src/use_cases/service_mesh/bookinfo/README.md b/src/use_cases/service_mesh/bookinfo/README.md
new file mode 100644 (file)
index 0000000..8d4d5a7
--- /dev/null
@@ -0,0 +1,15 @@
+# Bookinfo example on Istio
+This example deploys a sample application composed of four separate microservices
+used to demonstrate various Istio features. The application displays information
+about a book, similar to a single catalog entry of an online book store. Displayed
+on the page is a description of the book, book details (ISBN, number of pages, and
+so on), and a few book reviews.
+For more detail informations, please refer to corresponding website:
+[Bookinfo](https://istio.io/docs/examples/bookinfo/).
+In this document, it will show you how to deploy it.
+
+## Deploy Bookinfo example
+Firstly, get to know the k8s master IP address.
+Secondly, run the install.sh scripts with master IP address.
+
+  ./install/install.sh $master_ip
diff --git a/src/use_cases/service_mesh/bookinfo/config/bookinfo-gateway.yaml b/src/use_cases/service_mesh/bookinfo/config/bookinfo-gateway.yaml
new file mode 100644 (file)
index 0000000..ff2800c
--- /dev/null
@@ -0,0 +1,42 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length rule:comments
+apiVersion: networking.istio.io/v1alpha3
+kind: Gateway
+metadata:
+  name: bookinfo-gateway
+spec:
+  selector:
+    istio: ingressgateway # use istio default controller
+  servers:
+  - port:
+      number: 80
+      name: http
+      protocol: HTTP
+    hosts:
+    - "*"
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: bookinfo
+spec:
+  hosts:
+  - "*"
+  gateways:
+  - bookinfo-gateway
+  http:
+  - match:
+    - uri:
+        exact: /productpage
+    - uri:
+        prefix: /static
+    - uri:
+        exact: /login
+    - uri:
+        exact: /logout
+    - uri:
+        prefix: /api/v1/products
+    route:
+    - destination:
+        host: productpage
+        port:
+          number: 9080
diff --git a/src/use_cases/service_mesh/bookinfo/config/bookinfo.yaml b/src/use_cases/service_mesh/bookinfo/config/bookinfo.yaml
new file mode 100644 (file)
index 0000000..131a8f9
--- /dev/null
@@ -0,0 +1,265 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+# Copyright 2017 Istio Authors
+#
+#   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.
+
+##################################################################################################
+# Details service
+##################################################################################################
+apiVersion: v1
+kind: Service
+metadata:
+  name: details
+  labels:
+    app: details
+    service: details
+spec:
+  ports:
+  - port: 9080
+    name: http
+  selector:
+    app: details
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: bookinfo-details
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: details-v1
+  labels:
+    app: details
+    version: v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: details
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: details
+        version: v1
+    spec:
+      serviceAccountName: bookinfo-details
+      containers:
+      - name: details
+        image: iecedge/examples-bookinfo-details-v1-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
+##################################################################################################
+# Ratings service
+##################################################################################################
+apiVersion: v1
+kind: Service
+metadata:
+  name: ratings
+  labels:
+    app: ratings
+    service: ratings
+spec:
+  ports:
+  - port: 9080
+    name: http
+  selector:
+    app: ratings
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: bookinfo-ratings
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: ratings-v1
+  labels:
+    app: ratings
+    version: v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: ratings
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: ratings
+        version: v1
+    spec:
+      serviceAccountName: bookinfo-ratings
+      containers:
+      - name: ratings
+        image: iecedge/examples-bookinfo-ratings-v1-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
+##################################################################################################
+# Reviews service
+##################################################################################################
+apiVersion: v1
+kind: Service
+metadata:
+  name: reviews
+  labels:
+    app: reviews
+    service: reviews
+spec:
+  ports:
+  - port: 9080
+    name: http
+  selector:
+    app: reviews
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: bookinfo-reviews
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: reviews-v1
+  labels:
+    app: reviews
+    version: v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: reviews
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: reviews
+        version: v1
+    spec:
+      serviceAccountName: bookinfo-reviews
+      containers:
+      - name: reviews
+        image: iecedge/examples-bookinfo-reviews-v1-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: reviews-v2
+  labels:
+    app: reviews
+    version: v2
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: reviews
+      version: v2
+  template:
+    metadata:
+      labels:
+        app: reviews
+        version: v2
+    spec:
+      serviceAccountName: bookinfo-reviews
+      containers:
+      - name: reviews
+        image: iecedge/examples-bookinfo-reviews-v2-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: reviews-v3
+  labels:
+    app: reviews
+    version: v3
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: reviews
+      version: v3
+  template:
+    metadata:
+      labels:
+        app: reviews
+        version: v3
+    spec:
+      serviceAccountName: bookinfo-reviews
+      containers:
+      - name: reviews
+        image: iecedge/examples-bookinfo-reviews-v3-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
+##################################################################################################
+# Productpage services
+##################################################################################################
+apiVersion: v1
+kind: Service
+metadata:
+  name: productpage
+  labels:
+    app: productpage
+    service: productpage
+spec:
+  ports:
+  - port: 9080
+    name: http
+  selector:
+    app: productpage
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: bookinfo-productpage
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: productpage-v1
+  labels:
+    app: productpage
+    version: v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: productpage
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: productpage
+        version: v1
+    spec:
+      serviceAccountName: bookinfo-productpage
+      containers:
+      - name: productpage
+        image: iecedge/examples-bookinfo-productpage-v1-arm64:1.2.3
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9080
+---
diff --git a/src/use_cases/service_mesh/bookinfo/config/destination-rule-all.yaml b/src/use_cases/service_mesh/bookinfo/config/destination-rule-all.yaml
new file mode 100644 (file)
index 0000000..68bb6f3
--- /dev/null
@@ -0,0 +1,63 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: networking.istio.io/v1alpha3
+kind: DestinationRule
+metadata:
+  name: productpage
+spec:
+  host: productpage
+  subsets:
+  - name: v1
+    labels:
+      version: v1
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: DestinationRule
+metadata:
+  name: reviews
+spec:
+  host: reviews
+  subsets:
+  - name: v1
+    labels:
+      version: v1
+  - name: v2
+    labels:
+      version: v2
+  - name: v3
+    labels:
+      version: v3
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: DestinationRule
+metadata:
+  name: ratings
+spec:
+  host: ratings
+  subsets:
+  - name: v1
+    labels:
+      version: v1
+  - name: v2
+    labels:
+      version: v2
+  - name: v2-mysql
+    labels:
+      version: v2-mysql
+  - name: v2-mysql-vm
+    labels:
+      version: v2-mysql-vm
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: DestinationRule
+metadata:
+  name: details
+spec:
+  host: details
+  subsets:
+  - name: v1
+    labels:
+      version: v1
+  - name: v2
+    labels:
+      version: v2
+---
diff --git a/src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-80-20.yaml b/src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-80-20.yaml
new file mode 100644 (file)
index 0000000..52e365c
--- /dev/null
@@ -0,0 +1,18 @@
+# yamllint disable rule:hyphens rule:commas rule:indentation rule:line-length
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: reviews
+spec:
+  hosts:
+    - reviews
+  http:
+  - route:
+    - destination:
+        host: reviews
+        subset: v1
+      weight: 80
+    - destination:
+        host: reviews
+        subset: v2
+      weight: 20
diff --git a/src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-v3.yaml b/src/use_cases/service_mesh/bookinfo/config/virtual-service-reviews-v3.yaml
new file mode 100644 (file)
index 0000000..b58d89e
--- /dev/null
@@ -0,0 +1,12 @@
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: reviews
+spec:
+  hosts:
+    - reviews
+  http:
+    - route:
+        - destination:
+          host: reviews
+          subset: v3
diff --git a/src/use_cases/service_mesh/bookinfo/install/install.sh b/src/use_cases/service_mesh/bookinfo/install/install.sh
new file mode 100755 (executable)
index 0000000..0f4d9b1
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/bash -ex
+# shellcheck disable=SC2016
+# shellcheck source=/dev/null
+
+NODE_IP=${1:-10.169.36.152}
+
+function wait_for {
+  # Execute in a subshell to prevent local variable override during recursion
+  (
+    local total_attempts=$1; shift
+    local cmdstr=$*
+    local sleep_time=2
+    echo -e "\n[wait_for] Waiting for cmd to return success: ${cmdstr}"
+    # shellcheck disable=SC2034
+    for attempt in $(seq "${total_attempts}"); do
+      echo "[wait_for] Attempt ${attempt}/${total_attempts%.*} for: ${cmdstr}"
+      # shellcheck disable=SC2015
+      eval "${cmdstr}" && echo "[wait_for] OK: ${cmdstr}" && return 0 || true
+      sleep "${sleep_time}"
+    done
+    echo "[wait_for] ERROR: Failed after max attempts: ${cmdstr}"
+    return 1
+  )
+}
+
+# Enable default namespace as auto-inject mode
+# kubectl label namespace default istio-injection=enabled
+
+basepath=$(cd "$(dirname "$0")"; pwd)
+INFO_YAML=${INFO_YAML:-${basepath}/../config}
+
+# TODO(alav): Make each step re-entrant
+
+# shellcheck source=/dev/null
+# Waiting for Istio ready
+wait_for 100 'test $(kubectl get pods -n istio-system | grep -ce "Running") -eq 12'
+
+# Start bookinfo pods
+kubectl apply -f $INFO_YAML/bookinfo.yaml
+
+# Waiting for sample case ready
+wait_for 100 'test $(kubectl get pods | grep -ce "Running") -eq 6'
+
+# Start gateway
+kubectl apply -f $INFO_YAML/bookinfo-gateway.yaml
+
+# Configure gate way
+INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
+# SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')
+export GATEWAY_URL=$NODE_IP:$INGRESS_PORT
+
+# Confirm the result
+curl -s http://${GATEWAY_URL}/productpage | grep -o "<title>.*</title>"